Repository navigation
fix(ai-harness): do not run a no-user input as the session owner #1876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e38742
a0d68a3
ebc8bc8
fd76129
693f440
c76d26d
7d0952b
228f8e8
d893b50
f0215a3
4f84cf4
5a8ee70
a6bf3e1
47ea57a
7aeef8c
15d2b03
cb42edc
9da1c07
d915356
dab3fc9
170879e
74c4c22
ed590e4
d326802
634ac2f
340cda7
a8b7bf5
0fef405
ea75cbb
3e07156
cf2d558
17394d5
f4ffb64
95ce6c0
ca945af
5e94d3a
7eea102
3f20909
59bc072
11a42e9
c2b505b
757a43d
c3d9d04
b7f6a8e
d7e490a
91c36c3
5b946ea
4f7a7a1
66dc3c2
51c7dbc
a4db92f
fdc8ad2
6731d11
eaa9853
d6a3af3
d999b17
316dbbc
aceebf0
2705ae5
0d4321c
d464fb9
a36748d
7adbb35
ba0dcb9
681a13c
74efa98
46136ef
e4ceb6d
7283725
1398980
787718a
89209cd
c0b3960
9d999eb
b2e7502
228d9c1
142b8f2
81cada2
b6ca708
79a9466
d5a047a
c121f26
d7a9520
9ba963f
88196ec
efac8a4
c918220
7d5eeb0
ff0dd8c
7f0cb11
ff9707d
2794a50
bbfd225
cf91c73
addd7b9
2e523b1
5fb7da0
840f9c9
ef1fbde
72224eb
5912890
e93a065
4e939ba
bd1b37d
3524eb2
1c9719a
617c7d5
1f9c8fc
c9067d6
80b7536
4541335
40ae662
499bea9
f9c1ff0
41aff07
0c7a0e1
2106e60
9ae0147
091b2f1
7f42c62
5a4ee08
10e8560
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@tanstack/ai-harness': patch | ||
| --- | ||
|
|
||
| An input with no user no longer runs as the user who opened the session that owns the thread. A host with access refuses that input. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -930,14 +930,34 @@ export class SessionImpl<THarness extends AnyHarness = AnyHarness> | |||||||||||||||||||||||||||||||||
| const runs = this.runStore | ||||||||||||||||||||||||||||||||||
| const maxAttempts = this.harness.durability?.maxAttempts ?? 10 | ||||||||||||||||||||||||||||||||||
| const logged = [...writer.state.inputs.values()] | ||||||||||||||||||||||||||||||||||
| // An input with no user does not take this session's user. A host | ||||||||||||||||||||||||||||||||||
| // with access refuses it. The same check runs in the loop below. | ||||||||||||||||||||||||||||||||||
| const refuseAnonymous = async (input: InputState) => { | ||||||||||||||||||||||||||||||||||
| if (!this.hasAccess || input.user) return false | ||||||||||||||||||||||||||||||||||
| if (input.status !== 'pending' && input.status !== 'applied') return true | ||||||||||||||||||||||||||||||||||
| if (isTaken(input)) return true | ||||||||||||||||||||||||||||||||||
| if (input.status === 'pending') { | ||||||||||||||||||||||||||||||||||
| this.reject(input.inputId, 'no_user') | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| await this.settle({ | ||||||||||||||||||||||||||||||||||
| inputId: input.inputId, | ||||||||||||||||||||||||||||||||||
| ...(input.operationId ? { operationId: input.operationId } : {}), | ||||||||||||||||||||||||||||||||||
| outcome: 'failed', | ||||||||||||||||||||||||||||||||||
| error: { message: 'This input has no user.' }, | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+942
to
+947
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major | ποΈ Heavy lift π Supported by static analysisπ Script executed: #!/bin/bash
# Inspect settlement behavior and the joined-input recovery contract.
ast-grep outline packages/ai-harness/src/session/inputs.ts --match 'settle|joinedInputs' --view expanded
rg -n -C 8 'joinedInputs\(|settle\(' packages/ai-harness/src/session/inputs.ts packages/ai-harness/src/session/harness-session.tsRepository: TanStack/ai Length of output: 15610 π Script executed: #!/bin/bash
set -e
printf '%s\n' '--- recovery context ---'
sed -n '900,1010p' packages/ai-harness/src/session/harness-session.ts
printf '%s\n' '--- normal applied-turn settlement ---'
sed -n '1160,1220p' packages/ai-harness/src/session/harness-session.ts
printf '%s\n' '--- settlement implementation ---'
sed -n '1195,1240p' packages/ai-harness/src/session/inputs.ts
printf '%s\n' '--- join implementation and state transitions ---'
rg -n -C 12 -F 'status: '\''joined'\''' packages/ai-harness/src/session
rg -n -C 10 -e 'joinTurn|joinedInputs|into:' packages/ai-harness/src/session/inputs.ts packages/ai-harness/src/session/harness-session.tsRepository: TanStack/ai Length of output: 8437 π Script executed: #!/bin/bash
set -e
printf '%s\n' '--- all joined references ---'
rg -n -C 10 -F 'joined' packages/ai-harness/src/session || test "$?" -eq 1
printf '%s\n' '--- input join APIs ---'
rg -n -C 12 -e 'join' -e 'turnJoins' packages/ai-harness/src/session/inputs.ts packages/ai-harness/src/session/harness-session.ts || test "$?" -eq 1Repository: TanStack/ai Length of output: 41967 Settle joined inputs when refusing an applied turn. If an applied anonymous turn has joined inputs, this branch settles only the turn input. π Suggested fix--- "a/packages/ai-harness/src/session/harness-session.ts"
+++ "b/packages/ai-harness/src/session/harness-session.ts"
@@ -938,13 +938,17 @@
if (isTaken(input)) return true
if (input.status === 'pending') {
this.reject(input.inputId, 'no_user')
} else {
- await this.settle({
- inputId: input.inputId,
- ...(input.operationId ? { operationId: input.operationId } : {}),
- outcome: 'failed',
- error: { message: 'This input has no user.' },
- })
+ const operationId = appliedOperationId(input)
+ const joined = this.joinedInputs(operationId, input.inputId)
+ await this.settle(
+ ...[input.inputId, ...joined].map((inputId) => ({
+ inputId,
+ ...(operationId ? { operationId } : {}),
+ outcome: 'failed' as const,
+ error: { message: 'This input has no user.' },
+ })),
+ )
}
return true
}π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| for (const input of logged) { | ||||||||||||||||||||||||||||||||||
| if (await refuseAnonymous(input)) continue | ||||||||||||||||||||||||||||||||||
| const isControl = | ||||||||||||||||||||||||||||||||||
| input.status === 'pending' && CONTROL_OPS.has(input.input.op) | ||||||||||||||||||||||||||||||||||
| if (isControl && !isTaken(input)) await this.recoverControl(writer, input) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| // Read again: the pass above changed the inputs it applied to. | ||||||||||||||||||||||||||||||||||
| const inputs = [...writer.state.inputs.values()] | ||||||||||||||||||||||||||||||||||
| for (const input of inputs) { | ||||||||||||||||||||||||||||||||||
| if (await refuseAnonymous(input)) continue | ||||||||||||||||||||||||||||||||||
| // A reset that did not settle applies now. Its marker goes in once. | ||||||||||||||||||||||||||||||||||
| if (input.input.op === 'reset') { | ||||||||||||||||||||||||||||||||||
| const isOpen = | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π Security & Privacy | π Major | β‘ Quick win
actor()falls back to the session user when the caller passesundefinedas the fallback.The
fallbackparameter has a default value ofthis.user. JavaScript applies a default whenever the argument isundefined, including an explicitundefined. The effects are:runAgentinagents.tscallsthis.actor(options.user, parent ? parent.current.user : this.user). Whenparent.current.userisundefined(an anonymous chain), the default replaces it withthis.user. A child that agent code starts from an anonymous run then runs as the session user.anonymousOfferisfalseonce the synchronous dispatch ends. It does not protect later starts.turnChildAgents,binding.start, andpluginAgentsomituserfor an anonymous turn.runAgentthen callsactor(undefined, this.user), so those runs also take the session user's credentials and keys.This is the same inheritance that the PR aims to remove.
Make the fallback explicit. Do not use a default parameter. In
runAgent, resolve the turn user for turn children.Proposed fix
In
packages/ai-harness/src/session/agents.ts,runAgent:π Committable suggestion
π€ Prompt for AI Agents