@namzu/sdk 62.15.0 → 63.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/plugin/hook-context.d.ts +30 -0
- package/dist/plugin/hook-context.d.ts.map +1 -0
- package/dist/plugin/hook-context.js +51 -0
- package/dist/plugin/hook-context.js.map +1 -0
- package/dist/plugin/index.d.ts +2 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1 -0
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/lifecycle.d.ts +9 -0
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +13 -0
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/shell-hook.d.ts +87 -12
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +222 -31
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +2 -3
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +10 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +25 -5
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +3 -2
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +2 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.d.ts +4 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.js +2 -1
- package/dist/runtime/query/turn/outcome-ledger.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +10 -2
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +1 -0
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/plugin/index.d.ts +37 -10
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/session/events.d.ts +2 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/tool/index.d.ts +13 -1
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/plugin/hook-context.ts +58 -0
- package/src/plugin/index.ts +9 -0
- package/src/plugin/lifecycle.ts +13 -0
- package/src/plugin/shell-hook.ts +288 -42
- package/src/public-runtime.ts +6 -0
- package/src/public-types.ts +1 -0
- package/src/runtime/query/executor/tool-call-admission.ts +2 -4
- package/src/runtime/query/executor.ts +43 -9
- package/src/runtime/query/index.ts +3 -3
- package/src/runtime/query/iteration/index.ts +2 -0
- package/src/runtime/query/turn/outcome-ledger.ts +6 -1
- package/src/tools/builtins/bash.ts +16 -3
- package/src/tools/coordinator/index.ts +1 -0
- package/src/types/plugin/index.ts +48 -16
- package/src/types/session/events.ts +2 -0
- package/src/types/tool/index.ts +14 -1
|
@@ -63,6 +63,7 @@ import type {
|
|
|
63
63
|
ToolDispatchOptions,
|
|
64
64
|
ToolFacts,
|
|
65
65
|
ToolHandoff,
|
|
66
|
+
ToolProgressDetails,
|
|
66
67
|
ToolResult,
|
|
67
68
|
} from '../../types/tool/index.js'
|
|
68
69
|
import type { RepairToolCall } from '../../types/tool/repair.js'
|
|
@@ -272,7 +273,9 @@ function boundedToolProgress(message: string): string {
|
|
|
272
273
|
* complete useful working set; intermediate states may be replaced.
|
|
273
274
|
*/
|
|
274
275
|
class ToolProgressPublisher {
|
|
275
|
-
private pending:
|
|
276
|
+
private pending:
|
|
277
|
+
| { readonly message: string; readonly fraction?: number; readonly outputLines?: number }
|
|
278
|
+
| undefined
|
|
276
279
|
private draining: Promise<void> | null = null
|
|
277
280
|
private accepting = true
|
|
278
281
|
|
|
@@ -280,15 +283,18 @@ class ToolProgressPublisher {
|
|
|
280
283
|
private readonly emitEvent: EmitEvent,
|
|
281
284
|
private readonly base: Omit<
|
|
282
285
|
Extract<SessionEventDraft, { type: 'tool_progress' }>,
|
|
283
|
-
'message' | 'fraction'
|
|
286
|
+
'message' | 'fraction' | 'outputLines'
|
|
284
287
|
>,
|
|
285
288
|
) {}
|
|
286
289
|
|
|
287
|
-
report(message: string, fraction?: number): void {
|
|
290
|
+
report(message: string, fraction?: number, details?: ToolProgressDetails): void {
|
|
288
291
|
if (!this.accepting) return
|
|
289
292
|
this.pending = {
|
|
290
293
|
message,
|
|
291
294
|
...(fraction !== undefined ? { fraction: Math.min(1, Math.max(0, fraction)) } : {}),
|
|
295
|
+
...(details?.outputLines !== undefined && Number.isFinite(details.outputLines)
|
|
296
|
+
? { outputLines: Math.max(0, Math.floor(details.outputLines)) }
|
|
297
|
+
: {}),
|
|
292
298
|
}
|
|
293
299
|
this.startDrain()
|
|
294
300
|
}
|
|
@@ -323,6 +329,7 @@ class ToolProgressPublisher {
|
|
|
323
329
|
...this.base,
|
|
324
330
|
message: boundedToolProgress(update.message),
|
|
325
331
|
...(update.fraction !== undefined ? { fraction: update.fraction } : {}),
|
|
332
|
+
...(update.outputLines !== undefined ? { outputLines: update.outputLines } : {}),
|
|
326
333
|
}).catch(() => {})
|
|
327
334
|
}
|
|
328
335
|
}
|
|
@@ -564,8 +571,14 @@ interface PostToolOverride {
|
|
|
564
571
|
readonly output: string
|
|
565
572
|
readonly isError: boolean
|
|
566
573
|
readonly content?: ToolResultContent
|
|
574
|
+
/** A hook replaced a result that stood; the record says so beside it. */
|
|
575
|
+
readonly replacedByHook?: true
|
|
567
576
|
}
|
|
568
577
|
|
|
578
|
+
/** What the record and the model are told beside a result a hook replaced. */
|
|
579
|
+
export const HOOK_REPLACED_RESULT_NOTE =
|
|
580
|
+
"A hook replaced this tool result: the text above is the hook's, not the tool's own output."
|
|
581
|
+
|
|
569
582
|
/** What `pre_tool_use` hooks said about a call beyond running it or not. */
|
|
570
583
|
export interface PreToolHookNotes {
|
|
571
584
|
/** The strictest permission answer among the hooks: `ask` outranks `allow`. */
|
|
@@ -599,6 +612,14 @@ export interface ToolCallOutcome {
|
|
|
599
612
|
inputFailure?: CompletedToolRecord['inputFailure']
|
|
600
613
|
/** A pre-tool hook deliberately skipped execution; its receipt is not an output candidate. */
|
|
601
614
|
skipped?: true
|
|
615
|
+
/**
|
|
616
|
+
* The call never ran: the person declined it, a rule refused it, its input
|
|
617
|
+
* was refused before it started, or the turn ended first. The outcome record
|
|
618
|
+
* does not count it as a call that may have changed files.
|
|
619
|
+
*/
|
|
620
|
+
notRun?: true
|
|
621
|
+
/** The call only started work that goes on after it returned; see `ToolResult.launched`. */
|
|
622
|
+
launched?: true
|
|
602
623
|
/** Opt-in execution evidence, never provider content or accepted output. */
|
|
603
624
|
structuredResultJson?: string
|
|
604
625
|
/** The tool asked for a person; see `ToolResult.handoff`. */
|
|
@@ -1848,7 +1869,8 @@ export class ToolExecutor {
|
|
|
1848
1869
|
// Per-call for the same reason: a pause has to be routed back
|
|
1849
1870
|
// to the call that raised it, and a batch can raise several.
|
|
1850
1871
|
...(this.config.toolPause ? { requestPause: this.config.toolPause(toolCall.id) } : {}),
|
|
1851
|
-
report: (message: string, fraction?: number) =>
|
|
1872
|
+
report: (message: string, fraction?: number, details?: ToolProgressDetails) =>
|
|
1873
|
+
progress.report(message, fraction, details),
|
|
1852
1874
|
}
|
|
1853
1875
|
if (preparedCall) {
|
|
1854
1876
|
const cwd = this.preparedBashDirectories.get(preparedCall)
|
|
@@ -2341,7 +2363,8 @@ export class ToolExecutor {
|
|
|
2341
2363
|
// ephemeral child id; minting a pause route for `nestedId` makes the
|
|
2342
2364
|
// answer impossible to match after process restart. `...context`
|
|
2343
2365
|
// intentionally preserves the ancestor route here.
|
|
2344
|
-
report: (message: string, fraction?: number) =>
|
|
2366
|
+
report: (message: string, fraction?: number, details?: ToolProgressDetails) =>
|
|
2367
|
+
progress.report(message, fraction, details),
|
|
2345
2368
|
}
|
|
2346
2369
|
|
|
2347
2370
|
const nestedCwd = this.preparedBashDirectories.get(preparedCall)
|
|
@@ -2944,9 +2967,12 @@ export class ToolExecutor {
|
|
|
2944
2967
|
// receipt and the host's alike, framed as a hook's words. Added after
|
|
2945
2968
|
// the output budget so a bounded result is never trimmed to make room
|
|
2946
2969
|
// for it; the note has its own bound (`hookNote`).
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2970
|
+
// The mark comes first so the bound on hook text never cuts it off.
|
|
2971
|
+
const hookTexts = [
|
|
2972
|
+
postOverride?.replacedByHook === true ? HOOK_REPLACED_RESULT_NOTE : undefined,
|
|
2973
|
+
preparedHookContext,
|
|
2974
|
+
post.context,
|
|
2975
|
+
].filter((text): text is string => text !== undefined && text.length > 0)
|
|
2950
2976
|
if (hookTexts.length > 0) {
|
|
2951
2977
|
const note = hookNote(hookTexts)
|
|
2952
2978
|
output = `${output}\n\n${note}`
|
|
@@ -3104,6 +3130,7 @@ export class ToolExecutor {
|
|
|
3104
3130
|
...(result.handoff !== undefined && !this.config.abortSignal.aborted
|
|
3105
3131
|
? { handoff: result.handoff }
|
|
3106
3132
|
: {}),
|
|
3133
|
+
...(result.launched === true && !effectiveIsError ? { launched: true as const } : {}),
|
|
3107
3134
|
...(revealedTools !== undefined ? { revealedTools } : {}),
|
|
3108
3135
|
...(result.facts !== undefined && !postOverride ? { facts: result.facts } : {}),
|
|
3109
3136
|
}
|
|
@@ -3562,8 +3589,11 @@ export class ToolExecutor {
|
|
|
3562
3589
|
override = {
|
|
3563
3590
|
output: result.output,
|
|
3564
3591
|
isError: false,
|
|
3592
|
+
...(result.disclose === true ? { replacedByHook: true as const } : {}),
|
|
3565
3593
|
...(result.content !== undefined ? { content: result.content } : {}),
|
|
3566
3594
|
}
|
|
3595
|
+
if (result.context) contexts.push(result.context)
|
|
3596
|
+
if (result.halt) this.requestHalt(result.halt.reason)
|
|
3567
3597
|
continue
|
|
3568
3598
|
// `retry` was a declared variant with no implementation: every
|
|
3569
3599
|
// site that consumed it threw. Here it finally means something
|
|
@@ -3659,7 +3689,7 @@ export class ToolExecutor {
|
|
|
3659
3689
|
isError: true,
|
|
3660
3690
|
})
|
|
3661
3691
|
|
|
3662
|
-
return { toolCallId: toolCall.id, toolName, output, isError: true }
|
|
3692
|
+
return { toolCallId: toolCall.id, toolName, output, isError: true, notRun: true }
|
|
3663
3693
|
}
|
|
3664
3694
|
|
|
3665
3695
|
/**
|
|
@@ -3710,6 +3740,7 @@ export class ToolExecutor {
|
|
|
3710
3740
|
const reason = abortReasonText(this.config.abortSignal.reason)
|
|
3711
3741
|
return this.recordSyntheticHookOutcome(toolCallId, toolName, input, {
|
|
3712
3742
|
kind: 'error',
|
|
3743
|
+
notRun: true,
|
|
3713
3744
|
output: `Tool "${toolName}" was not started because the turn was cancelled${reason ? `: ${reason}` : '.'}`,
|
|
3714
3745
|
})
|
|
3715
3746
|
}
|
|
@@ -3723,6 +3754,7 @@ export class ToolExecutor {
|
|
|
3723
3754
|
output: string
|
|
3724
3755
|
inputFailure?: ToolCallOutcome['inputFailure']
|
|
3725
3756
|
skipped?: true
|
|
3757
|
+
notRun?: true
|
|
3726
3758
|
facts?: ToolFacts
|
|
3727
3759
|
},
|
|
3728
3760
|
): Promise<ToolCallOutcome> {
|
|
@@ -3767,12 +3799,14 @@ export class ToolExecutor {
|
|
|
3767
3799
|
...(outcome.facts ? { facts: outcome.facts } : {}),
|
|
3768
3800
|
...(outcome.inputFailure ? { inputFailure: outcome.inputFailure } : {}),
|
|
3769
3801
|
...(outcome.skipped ? { skipped: true as const } : {}),
|
|
3802
|
+
...(outcome.notRun ? { notRun: true as const } : {}),
|
|
3770
3803
|
}
|
|
3771
3804
|
}
|
|
3772
3805
|
|
|
3773
3806
|
private recordSyntheticPreparation(call: Extract<PreparedDirectCall, { kind: 'synthetic' }>) {
|
|
3774
3807
|
return this.recordSyntheticHookOutcome(call.toolCall.id, call.toolName, call.input, {
|
|
3775
3808
|
kind: call.isError ? 'error' : 'skip',
|
|
3809
|
+
...(call.isError ? { notRun: true as const } : {}),
|
|
3776
3810
|
output: call.message,
|
|
3777
3811
|
...(call.facts ? { facts: call.facts } : {}),
|
|
3778
3812
|
...(call.inputFailure ? { inputFailure: call.inputFailure } : {}),
|
|
@@ -18,6 +18,7 @@ import { restoreWorkingState, snapshotWorkingState } from '../../compaction/wire
|
|
|
18
18
|
import { type CompactionConfig, CompactionConfigSchema } from '../../config/runtime.js'
|
|
19
19
|
import { childSessionLog } from '../../manager/agent/child-session.js'
|
|
20
20
|
import { type TurnRecorder, recordedTurnStart } from '../../manager/session/turn-recorder.js'
|
|
21
|
+
import { frameOperatorHookContext } from '../../plugin/hook-context.js'
|
|
21
22
|
import { PromptContributionRegistry } from '../../prompt/contributions.js'
|
|
22
23
|
import { resolveProviderCapabilities } from '../../provider/capabilities.js'
|
|
23
24
|
import type { ProviderChainMember } from '../../provider/fallback.js'
|
|
@@ -2199,8 +2200,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
2199
2200
|
promptContributions.replace({
|
|
2200
2201
|
id: 'hooks:subagent_start',
|
|
2201
2202
|
placement: 'context',
|
|
2202
|
-
render: () =>
|
|
2203
|
-
`Context from the operator's hooks:\n\n${startAnnotations.join('\n\n')}`,
|
|
2203
|
+
render: () => frameOperatorHookContext(startAnnotations),
|
|
2204
2204
|
})
|
|
2205
2205
|
}
|
|
2206
2206
|
yield* eventTranslator.drainPending()
|
|
@@ -2226,7 +2226,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
2226
2226
|
promptContributions.replace({
|
|
2227
2227
|
id: 'hooks:user_prompt_submit',
|
|
2228
2228
|
placement: 'context',
|
|
2229
|
-
render: () =>
|
|
2229
|
+
render: () => frameOperatorHookContext(annotations),
|
|
2230
2230
|
})
|
|
2231
2231
|
}
|
|
2232
2232
|
yield* eventTranslator.drainPending()
|
|
@@ -3505,6 +3505,8 @@ export class IterationOrchestrator {
|
|
|
3505
3505
|
input,
|
|
3506
3506
|
isError: result.isError,
|
|
3507
3507
|
...(result.skipped ? { skipped: true } : {}),
|
|
3508
|
+
...(result.notRun ? { notRun: true } : {}),
|
|
3509
|
+
...(result.launched ? { launched: true } : {}),
|
|
3508
3510
|
readOnly: this.lookedOnly(result.toolName, input),
|
|
3509
3511
|
...(result.facts ? { facts: result.facts } : {}),
|
|
3510
3512
|
}
|
|
@@ -36,6 +36,10 @@ export interface OutcomeCall {
|
|
|
36
36
|
readonly isError?: boolean
|
|
37
37
|
/** A hook stopped it before it ran. */
|
|
38
38
|
readonly skipped?: boolean
|
|
39
|
+
/** It never ran: declined by the person, refused by a rule or by its own input, or the turn ended first. */
|
|
40
|
+
readonly notRun?: boolean
|
|
41
|
+
/** It only started work that goes on after it returned; the call itself changed nothing. */
|
|
42
|
+
readonly launched?: boolean
|
|
39
43
|
/** The tool declared this call read-only (or it only keeps the task list). */
|
|
40
44
|
readonly readOnly?: boolean
|
|
41
45
|
/** What the call did, as typed fields. Absent when a hook replaced the output. */
|
|
@@ -393,7 +397,8 @@ export class OutcomeLedger {
|
|
|
393
397
|
// answers for exactly the state they left.
|
|
394
398
|
let pending: CheckState[] = []
|
|
395
399
|
for (const call of calls) {
|
|
396
|
-
|
|
400
|
+
// A call that did not run, or only started work elsewhere, changed nothing here.
|
|
401
|
+
if (call.skipped === true || call.notRun === true || call.launched === true) continue
|
|
397
402
|
const written = WRITING_TOOLS.has(call.toolName)
|
|
398
403
|
if (written && call.isError !== true) {
|
|
399
404
|
if (call.facts?.kind === 'write' && call.facts.operation === 'unchanged') continue
|
|
@@ -205,14 +205,26 @@ function progressLine(value: string): string {
|
|
|
205
205
|
* Reports stay synchronous: the executor owns coalescing and backpressure.
|
|
206
206
|
* This projection adds neither an output log nor a queue of pending reports.
|
|
207
207
|
*/
|
|
208
|
-
function shellProgress(
|
|
208
|
+
function shellProgress(
|
|
209
|
+
report?: (
|
|
210
|
+
message: string,
|
|
211
|
+
fraction?: number,
|
|
212
|
+
details?: { readonly outputLines?: number },
|
|
213
|
+
) => void,
|
|
214
|
+
) {
|
|
209
215
|
if (!report) return undefined
|
|
210
216
|
const partial = { stdout: '', stderr: '' }
|
|
211
217
|
let lastMessage = ''
|
|
218
|
+
// Lines of output so far, both streams: every line feed seen. Counted here, on
|
|
219
|
+
// every chunk, because the reports below are coalesced and a viewer counting
|
|
220
|
+
// the messages it received would undercount a fast command.
|
|
221
|
+
let outputLines = 0
|
|
222
|
+
let lastLines = 0
|
|
212
223
|
return ({ stream, data }: { stream: 'stdout' | 'stderr'; data: string }): void => {
|
|
213
224
|
let line = partial[stream]
|
|
214
225
|
let latest = ''
|
|
215
226
|
let start = 0
|
|
227
|
+
for (let at = data.indexOf('\n'); at >= 0; at = data.indexOf('\n', at + 1)) outputLines++
|
|
216
228
|
// Scan delimiters without allocating an array for arbitrarily chatty output.
|
|
217
229
|
for (const match of data.matchAll(/[\r\n]/g)) {
|
|
218
230
|
line = appendProgressTail(line, data, start, match.index)
|
|
@@ -223,10 +235,11 @@ function shellProgress(report?: (message: string) => void) {
|
|
|
223
235
|
line = appendProgressTail(line, data, start, data.length)
|
|
224
236
|
partial[stream] = line
|
|
225
237
|
const message = progressLine(line) || latest
|
|
226
|
-
if (!message || message === lastMessage) return
|
|
238
|
+
if (!message || (message === lastMessage && outputLines === lastLines)) return
|
|
227
239
|
lastMessage = message
|
|
240
|
+
lastLines = outputLines
|
|
228
241
|
try {
|
|
229
|
-
report(message)
|
|
242
|
+
report(message, undefined, { outputLines })
|
|
230
243
|
} catch {
|
|
231
244
|
// A diagnostic observer cannot break the process output reader.
|
|
232
245
|
}
|
|
@@ -96,7 +96,13 @@ export type PluginHookEvent =
|
|
|
96
96
|
* to what the model is told (`annotate`).
|
|
97
97
|
*/
|
|
98
98
|
| 'user_prompt_submit'
|
|
99
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* A host's session opened or closed. Carries `sessionId` and no `turnId`:
|
|
101
|
+
* these hooks run outside any turn. `session_start` carries `source`;
|
|
102
|
+
* `session_end` carries `endReason`. A `session_start` hook may answer with
|
|
103
|
+
* `context` (on `continue`) or `annotate`: text the model reads, from the
|
|
104
|
+
* first turn of the session on.
|
|
105
|
+
*/
|
|
100
106
|
| 'session_start'
|
|
101
107
|
| 'session_end'
|
|
102
108
|
/** A compaction pass is about to run / has run. Carries `compaction`. */
|
|
@@ -110,11 +116,6 @@ export type PluginHookEvent =
|
|
|
110
116
|
| 'subagent_start'
|
|
111
117
|
/** A child session's turn ended. Fired after its own `turn_end`; carries `parentSessionId`, `parentTurnId` and `agentType`. */
|
|
112
118
|
| 'subagent_stop'
|
|
113
|
-
/**
|
|
114
|
-
* A child session's turn is starting. Fired right after its own
|
|
115
|
-
* `turn_start`; carries `parentSessionId` and `parentTurnId`. Observational.
|
|
116
|
-
*/
|
|
117
|
-
| 'subagent_start'
|
|
118
119
|
| 'turn_start'
|
|
119
120
|
| 'turn_end'
|
|
120
121
|
/**
|
|
@@ -273,6 +274,21 @@ export interface PluginNotificationInfo {
|
|
|
273
274
|
readonly message: string
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Why a session started, on `session_start`: `startup` for a conversation
|
|
279
|
+
* with no earlier turn, `resume` for one that already had turns (a resumed
|
|
280
|
+
* conversation, a `--continue`, or a session rebuilt under a running one).
|
|
281
|
+
*/
|
|
282
|
+
export type PluginSessionSource = 'startup' | 'resume'
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Why a session closed, on `session_end`: `exit` when the host is finishing
|
|
286
|
+
* with it (the process ends, the person quits), `replaced` when the host
|
|
287
|
+
* builds another session in its place (a model or account switch, another
|
|
288
|
+
* conversation opened).
|
|
289
|
+
*/
|
|
290
|
+
export type PluginSessionEndReason = 'exit' | 'replaced'
|
|
291
|
+
|
|
276
292
|
export interface PluginHookContext {
|
|
277
293
|
/** The turn the hook fired in. Absent on `session_start` and `session_end`. */
|
|
278
294
|
readonly turnId?: TurnId
|
|
@@ -286,6 +302,10 @@ export interface PluginHookContext {
|
|
|
286
302
|
readonly parentTurnId?: TurnId
|
|
287
303
|
/** The name of the agent the child runs, on `subagent_start` and `subagent_stop`. */
|
|
288
304
|
readonly agentType?: string
|
|
305
|
+
/** Why the session started, on `session_start`. */
|
|
306
|
+
readonly source?: PluginSessionSource
|
|
307
|
+
/** Why the session closed, on `session_end`. */
|
|
308
|
+
readonly endReason?: PluginSessionEndReason
|
|
289
309
|
/** The operator's prompt, on `user_prompt_submit`. */
|
|
290
310
|
readonly prompt?: string
|
|
291
311
|
/** The pass, on `pre_compact` and `post_compact`. */
|
|
@@ -437,7 +457,18 @@ export type PluginHookResult =
|
|
|
437
457
|
* hook redacting a secret that also appears in an image must — this variant
|
|
438
458
|
* cannot inspect what it is preserving.
|
|
439
459
|
*/
|
|
440
|
-
| {
|
|
460
|
+
| ({
|
|
461
|
+
action: 'replace'
|
|
462
|
+
output: string
|
|
463
|
+
content?: ToolResultContent
|
|
464
|
+
/**
|
|
465
|
+
* Say, beside the result the model and the record carry, that a hook
|
|
466
|
+
* replaced it, so rewritten text is not passed off as the tool's own
|
|
467
|
+
* words. Off by default: a plugin that redacts quietly keeps doing so.
|
|
468
|
+
* A shell hook's replacement always sets it.
|
|
469
|
+
*/
|
|
470
|
+
disclose?: boolean
|
|
471
|
+
} & PluginHookNotes)
|
|
441
472
|
|
|
442
473
|
export function assertPluginHookResult(result: PluginHookResult): asserts result {
|
|
443
474
|
const action = result.action
|
|
@@ -472,15 +503,16 @@ export type PluginHookResultFor<E extends PluginHookEvent> = E extends 'user_pro
|
|
|
472
503
|
? Extract<PluginHookResult, { action: 'continue' | 'skip' }>
|
|
473
504
|
: E extends 'turn_end' | 'subagent_stop' | 'pre_compact'
|
|
474
505
|
? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'error' }>
|
|
475
|
-
: E extends
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
506
|
+
: E extends 'session_start'
|
|
507
|
+
? Extract<PluginHookResult, { action: 'continue' | 'annotate' }>
|
|
508
|
+
: E extends
|
|
509
|
+
| 'turn_interrupt'
|
|
510
|
+
| 'session_end'
|
|
511
|
+
| 'notification'
|
|
512
|
+
| 'turn_failure'
|
|
513
|
+
| 'permission_denied'
|
|
514
|
+
? Extract<PluginHookResult, { action: 'continue' }>
|
|
515
|
+
: Extract<PluginHookResult, { action: 'continue' | 'error' }>
|
|
484
516
|
|
|
485
517
|
/** The event and its handler verdict stay correlated, including in hook arrays. */
|
|
486
518
|
export type PluginHookDefinition<E extends PluginHookEvent = PluginHookEvent> =
|
|
@@ -435,6 +435,8 @@ type CoreSessionEvent =
|
|
|
435
435
|
message: string
|
|
436
436
|
/** Optional completion in [0,1] when the tool genuinely knows it. */
|
|
437
437
|
fraction?: number
|
|
438
|
+
/** Lines of output the call has produced so far, when the tool counts them (a shell command). Cumulative: the newest report wins. */
|
|
439
|
+
outputLines?: number
|
|
438
440
|
}
|
|
439
441
|
/** Provider-executed activity, retained as evidence rather than a local tool request. */
|
|
440
442
|
| {
|
package/src/types/tool/index.ts
CHANGED
|
@@ -940,7 +940,13 @@ export interface ToolContext {
|
|
|
940
940
|
*
|
|
941
941
|
* Absent when the executing surface has no event stream to write to.
|
|
942
942
|
*/
|
|
943
|
-
report?: (message: string, fraction?: number) => void
|
|
943
|
+
report?: (message: string, fraction?: number, details?: ToolProgressDetails) => void
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** Facts a tool can attach to a progress report besides its words. */
|
|
947
|
+
export interface ToolProgressDetails {
|
|
948
|
+
/** Lines of output the call has produced so far, counted by the tool itself (never by a renderer from what it was shown). */
|
|
949
|
+
readonly outputLines?: number
|
|
944
950
|
}
|
|
945
951
|
|
|
946
952
|
export interface ToolResult {
|
|
@@ -1009,6 +1015,13 @@ export interface ToolResult {
|
|
|
1009
1015
|
* the child's turn fails with {@link ToolHandoff.reason} instead.
|
|
1010
1016
|
*/
|
|
1011
1017
|
handoff?: ToolHandoff
|
|
1018
|
+
/**
|
|
1019
|
+
* This call only started work that goes on after it returned (a sub-agent
|
|
1020
|
+
* launched in the background, or one whose wait was released): the call itself
|
|
1021
|
+
* changed nothing. The turn's outcome record does not count it as a call that
|
|
1022
|
+
* may have changed files; what the started work does is its own record.
|
|
1023
|
+
*/
|
|
1024
|
+
launched?: true
|
|
1012
1025
|
/**
|
|
1013
1026
|
* What this call did, as typed fields for the PERSON's view: the exit code
|
|
1014
1027
|
* and the two streams of a command, the path and size of a read, whether a
|