@namzu/sdk 63.0.0 → 64.0.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 +60 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -1
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/compaction/hooks.d.ts +57 -0
- package/dist/compaction/hooks.d.ts.map +1 -0
- package/dist/compaction/hooks.js +44 -0
- package/dist/compaction/hooks.js.map +1 -0
- package/dist/compaction/manual.d.ts +9 -0
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +74 -5
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/model-summary.d.ts +6 -0
- package/dist/compaction/model-summary.d.ts.map +1 -1
- package/dist/compaction/model-summary.js +13 -3
- package/dist/compaction/model-summary.js.map +1 -1
- package/dist/compaction/verifier.d.ts +8 -2
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +23 -7
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/plugin/hook-context.d.ts +16 -0
- package/dist/plugin/hook-context.d.ts.map +1 -1
- package/dist/plugin/hook-context.js +40 -0
- package/dist/plugin/hook-context.js.map +1 -1
- package/dist/plugin/index.d.ts +4 -3
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +3 -2
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/shell-hook-command.d.ts +25 -0
- package/dist/plugin/shell-hook-command.d.ts.map +1 -0
- package/dist/plugin/shell-hook-command.js +218 -0
- package/dist/plugin/shell-hook-command.js.map +1 -0
- package/dist/plugin/shell-hook-tool-names.d.ts +20 -0
- package/dist/plugin/shell-hook-tool-names.d.ts.map +1 -0
- package/dist/plugin/shell-hook-tool-names.js +43 -0
- package/dist/plugin/shell-hook-tool-names.js.map +1 -0
- package/dist/plugin/shell-hook.d.ts +62 -20
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +372 -85
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/compact-session.d.ts +7 -1
- package/dist/runtime/query/compact-session.d.ts.map +1 -1
- package/dist/runtime/query/compact-session.js +8 -3
- package/dist/runtime/query/compact-session.js.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.d.ts +1 -1
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +4 -3
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +8 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +17 -7
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
- package/dist/runtime/query/finalize-turn.js +9 -2
- package/dist/runtime/query/finalize-turn.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +4 -1
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +24 -12
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +2 -0
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.d.ts +13 -0
- package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +29 -2
- package/dist/runtime/query/plugin-hooks.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/agent/query.d.ts +8 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/plugin/index.d.ts +55 -11
- 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/agents/QueryAgent.ts +2 -1
- package/src/compaction/hooks.ts +104 -0
- package/src/compaction/manual.ts +117 -4
- package/src/compaction/model-summary.ts +19 -3
- package/src/compaction/verifier.ts +33 -3
- package/src/plugin/hook-context.ts +46 -0
- package/src/plugin/index.ts +14 -0
- package/src/plugin/shell-hook-command.ts +215 -0
- package/src/plugin/shell-hook-tool-names.ts +46 -0
- package/src/plugin/shell-hook.ts +405 -79
- package/src/public-runtime.ts +12 -0
- package/src/public-types.ts +2 -0
- package/src/runtime/query/compact-session.ts +16 -4
- package/src/runtime/query/executor/tool-call-admission.ts +10 -2
- package/src/runtime/query/executor.ts +33 -8
- package/src/runtime/query/finalize-turn.ts +9 -2
- package/src/runtime/query/iteration/index.ts +4 -1
- package/src/runtime/query/iteration/phases/compaction.ts +31 -12
- package/src/runtime/query/iteration/phases/tool-review.ts +2 -0
- package/src/runtime/query/plugin-hooks.ts +32 -2
- 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/agent/query.ts +9 -0
- package/src/types/plugin/index.ts +66 -22
- package/src/types/session/events.ts +2 -0
- package/src/types/tool/index.ts +14 -1
package/src/public-runtime.ts
CHANGED
|
@@ -697,8 +697,13 @@ export {
|
|
|
697
697
|
|
|
698
698
|
export {
|
|
699
699
|
attachShellHooks,
|
|
700
|
+
boundCompactionSummary,
|
|
700
701
|
boundHookContext,
|
|
701
702
|
boundHookReplacement,
|
|
703
|
+
boundLastAssistantMessage,
|
|
704
|
+
compactionHookInstructions,
|
|
705
|
+
MAX_COMPACTION_SUMMARY_HOOK_CHARS,
|
|
706
|
+
MAX_LAST_MESSAGE_HOOK_CHARS,
|
|
702
707
|
createShellHook,
|
|
703
708
|
DEFAULT_SHELL_HOOK_TIMEOUT_MS,
|
|
704
709
|
definePlugin,
|
|
@@ -716,8 +721,13 @@ export {
|
|
|
716
721
|
PluginResolver,
|
|
717
722
|
runShellHook,
|
|
718
723
|
SHELL_HOOK_EVENTS,
|
|
724
|
+
SHELL_HOOK_PASCAL_EVENT_NAMES,
|
|
725
|
+
SHELL_HOOK_WRITTEN_TOOL_NAMES,
|
|
719
726
|
SHELL_HOOKS_PLUGIN_ID,
|
|
727
|
+
shellHookToolName,
|
|
720
728
|
shellHookIfMatches,
|
|
729
|
+
shellHookIfProblem,
|
|
730
|
+
shellHookMatcherProblem,
|
|
721
731
|
shellHookMatches,
|
|
722
732
|
shellHookVerdict,
|
|
723
733
|
} from './plugin/index.js'
|
|
@@ -1343,6 +1353,7 @@ export { assertTaskStatus, isTerminalTaskStatus } from './types/task/index.js'
|
|
|
1343
1353
|
// offer "compact this conversation", could not shrink an idle session
|
|
1344
1354
|
// between turns, and could not collapse a span it had chosen.
|
|
1345
1355
|
export { compactNow, compactRegion } from './compaction/manual.js'
|
|
1356
|
+
export { CompactionRefusedError } from './compaction/hooks.js'
|
|
1346
1357
|
// A host persisting manual compaction has to recognise the system message on
|
|
1347
1358
|
// resume. Export the identity predicate rather than making every host copy the
|
|
1348
1359
|
// marker string and eventually disagree with the writer.
|
|
@@ -1380,6 +1391,7 @@ export { isWorkingMemoryMessage } from './runtime/query/iteration/phases/working
|
|
|
1380
1391
|
// `any` — and the first host to try (`@namzu/cli`'s `/compact`) did exactly
|
|
1381
1392
|
// that before this line existed.
|
|
1382
1393
|
export type { CompactNowInput, CompactionResult } from './compaction/manual.js'
|
|
1394
|
+
export type { CompactionHookHost, ManualCompactionHooks } from './compaction/hooks.js'
|
|
1383
1395
|
export type {
|
|
1384
1396
|
CompactionSummaryRoute,
|
|
1385
1397
|
CompactionSummaryRouter,
|
package/src/public-types.ts
CHANGED
|
@@ -242,12 +242,14 @@ export type {
|
|
|
242
242
|
ShellHookHandler,
|
|
243
243
|
ShellHookJudge,
|
|
244
244
|
ShellHookOnFailure,
|
|
245
|
+
ShellHookCall,
|
|
245
246
|
ShellHookInput,
|
|
246
247
|
ShellHookOptions,
|
|
247
248
|
ShellHookPathFilter,
|
|
248
249
|
ShellHookOutcome,
|
|
249
250
|
ShellHooksConfig,
|
|
250
251
|
} from './plugin/shell-hook.js'
|
|
252
|
+
export type { ShellHookToolNames } from './plugin/shell-hook-tool-names.js'
|
|
251
253
|
|
|
252
254
|
export type {
|
|
253
255
|
PlanApprovalHandler,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ManualCompactionHooks } from '../../compaction/hooks.js'
|
|
1
2
|
import {
|
|
2
3
|
type CompactNowInput,
|
|
3
4
|
type CompactionResult,
|
|
@@ -13,8 +14,14 @@ import { generateMessageId } from '../../utils/id.js'
|
|
|
13
14
|
import { type SessionLocatorOptions, locateSessionLog, withSessionLease } from './abandon-turn.js'
|
|
14
15
|
|
|
15
16
|
/** A compaction a host asks for between turns. */
|
|
16
|
-
export interface CompactSessionParams
|
|
17
|
+
export interface CompactSessionParams
|
|
18
|
+
extends Omit<CompactNowInput, 'messages' | 'onShed' | 'hooks'> {
|
|
17
19
|
readonly sessionId: SessionId
|
|
20
|
+
/**
|
|
21
|
+
* Fire `pre_compact` and `post_compact` around the pass. The session and
|
|
22
|
+
* the trigger are this call's own. See {@link CompactNowInput.hooks}.
|
|
23
|
+
*/
|
|
24
|
+
readonly hooks?: Omit<ManualCompactionHooks, 'sessionId' | 'trigger'>
|
|
18
25
|
/**
|
|
19
26
|
* Who asked. `'manual'` (the default) is a person's request; `'idle'` is a
|
|
20
27
|
* host compacting a conversation nobody is using before its prompt cache
|
|
@@ -37,7 +44,7 @@ export interface CompactSessionParams extends Omit<CompactNowInput, 'messages' |
|
|
|
37
44
|
export async function compactSession(
|
|
38
45
|
params: CompactSessionParams,
|
|
39
46
|
): Promise<CompactionResult | null> {
|
|
40
|
-
const { sessionId, locator = {}, trigger = 'manual', ...input } = params
|
|
47
|
+
const { sessionId, locator = {}, trigger = 'manual', hooks, ...input } = params
|
|
41
48
|
const log = await locateSessionLog(sessionId, locator)
|
|
42
49
|
return withSessionLease(log, locator.lease, async (lease) => {
|
|
43
50
|
const active = await log.activeTurn({ lease })
|
|
@@ -47,7 +54,11 @@ export async function compactSession(
|
|
|
47
54
|
const history = await readFoldedHistory(log)
|
|
48
55
|
const ids = new Map<Message, MessageId>()
|
|
49
56
|
for (const entry of history) if (entry.messageId) ids.set(entry.message, entry.messageId)
|
|
50
|
-
const result = await compactNow({
|
|
57
|
+
const result = await compactNow({
|
|
58
|
+
...input,
|
|
59
|
+
messages: history.map((entry) => entry.message),
|
|
60
|
+
...(hooks ? { hooks: { ...hooks, sessionId, trigger } } : {}),
|
|
61
|
+
})
|
|
51
62
|
if (!result) return null
|
|
52
63
|
const head = await log.head()
|
|
53
64
|
if (!head) {
|
|
@@ -105,7 +116,7 @@ export interface CompactSessionRangeParams extends CompactSessionParams {
|
|
|
105
116
|
export async function compactSessionRange(
|
|
106
117
|
params: CompactSessionRangeParams,
|
|
107
118
|
): Promise<CompactionResult | null> {
|
|
108
|
-
const { sessionId, locator = {}, from, until, ...input } = params
|
|
119
|
+
const { sessionId, locator = {}, from, until, hooks, ...input } = params
|
|
109
120
|
const log = await locateSessionLog(sessionId, locator)
|
|
110
121
|
return withSessionLease(log, locator.lease, async (lease) => {
|
|
111
122
|
const active = await log.activeTurn({ lease })
|
|
@@ -134,6 +145,7 @@ export async function compactSessionRange(
|
|
|
134
145
|
messages: history.map((entry) => entry.message),
|
|
135
146
|
start,
|
|
136
147
|
end,
|
|
148
|
+
...(hooks ? { hooks: { ...hooks, sessionId, trigger: 'manual' as const } } : {}),
|
|
137
149
|
})
|
|
138
150
|
if (!result) return null
|
|
139
151
|
const head = await log.head()
|
|
@@ -78,6 +78,7 @@ export async function runPreToolHook(
|
|
|
78
78
|
toolName: string,
|
|
79
79
|
input: unknown,
|
|
80
80
|
signal: AbortSignal = host.config.abortSignal,
|
|
81
|
+
toolCallId?: string,
|
|
81
82
|
): Promise<PreToolHookOutcome> {
|
|
82
83
|
signal.throwIfAborted()
|
|
83
84
|
if (!host.config.pluginManager) return { kind: 'continue', input, modified: false }
|
|
@@ -88,6 +89,7 @@ export async function runPreToolHook(
|
|
|
88
89
|
sessionId: host.config.sessionId,
|
|
89
90
|
turnId: host.config.turnId,
|
|
90
91
|
toolName,
|
|
92
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
91
93
|
toolInput: input,
|
|
92
94
|
signal,
|
|
93
95
|
},
|
|
@@ -155,7 +157,7 @@ export async function prepareDirectCall(
|
|
|
155
157
|
: {}),
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
|
-
const preOutcome = await runPreToolHook(host, toolName, resolved.input)
|
|
160
|
+
const preOutcome = await runPreToolHook(host, toolName, resolved.input, undefined, toolCall.id)
|
|
159
161
|
if (preOutcome.kind === 'skip' || preOutcome.kind === 'error') {
|
|
160
162
|
return {
|
|
161
163
|
kind: 'synthetic',
|
|
@@ -297,7 +299,13 @@ export async function prepareDirectCall(
|
|
|
297
299
|
}
|
|
298
300
|
}
|
|
299
301
|
|
|
300
|
-
const preOutcome = await runPreToolHook(
|
|
302
|
+
const preOutcome = await runPreToolHook(
|
|
303
|
+
host,
|
|
304
|
+
toolName,
|
|
305
|
+
preparation.prepared.input,
|
|
306
|
+
undefined,
|
|
307
|
+
toolCall.id,
|
|
308
|
+
)
|
|
301
309
|
if (preOutcome.kind === 'skip' || preOutcome.kind === 'error') {
|
|
302
310
|
return {
|
|
303
311
|
kind: 'synthetic',
|
|
@@ -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
|
}
|
|
@@ -605,6 +612,14 @@ export interface ToolCallOutcome {
|
|
|
605
612
|
inputFailure?: CompletedToolRecord['inputFailure']
|
|
606
613
|
/** A pre-tool hook deliberately skipped execution; its receipt is not an output candidate. */
|
|
607
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
|
|
608
623
|
/** Opt-in execution evidence, never provider content or accepted output. */
|
|
609
624
|
structuredResultJson?: string
|
|
610
625
|
/** The tool asked for a person; see `ToolResult.handoff`. */
|
|
@@ -1854,7 +1869,8 @@ export class ToolExecutor {
|
|
|
1854
1869
|
// Per-call for the same reason: a pause has to be routed back
|
|
1855
1870
|
// to the call that raised it, and a batch can raise several.
|
|
1856
1871
|
...(this.config.toolPause ? { requestPause: this.config.toolPause(toolCall.id) } : {}),
|
|
1857
|
-
report: (message: string, fraction?: number) =>
|
|
1872
|
+
report: (message: string, fraction?: number, details?: ToolProgressDetails) =>
|
|
1873
|
+
progress.report(message, fraction, details),
|
|
1858
1874
|
}
|
|
1859
1875
|
if (preparedCall) {
|
|
1860
1876
|
const cwd = this.preparedBashDirectories.get(preparedCall)
|
|
@@ -2347,7 +2363,8 @@ export class ToolExecutor {
|
|
|
2347
2363
|
// ephemeral child id; minting a pause route for `nestedId` makes the
|
|
2348
2364
|
// answer impossible to match after process restart. `...context`
|
|
2349
2365
|
// intentionally preserves the ancestor route here.
|
|
2350
|
-
report: (message: string, fraction?: number) =>
|
|
2366
|
+
report: (message: string, fraction?: number, details?: ToolProgressDetails) =>
|
|
2367
|
+
progress.report(message, fraction, details),
|
|
2351
2368
|
}
|
|
2352
2369
|
|
|
2353
2370
|
const nestedCwd = this.preparedBashDirectories.get(preparedCall)
|
|
@@ -2773,7 +2790,7 @@ export class ToolExecutor {
|
|
|
2773
2790
|
// block had it discarded here — before the wire mapper that was
|
|
2774
2791
|
// built to carry it ever saw it.
|
|
2775
2792
|
let result: ToolResult = await this.runOnce(toolName, input, toolContext, prepared)
|
|
2776
|
-
let post = await this.runPostToolHook(toolName, input, result)
|
|
2793
|
+
let post = await this.runPostToolHook(toolName, input, result, toolCall.id)
|
|
2777
2794
|
|
|
2778
2795
|
// In-loop retry. A transient failure used to cost a full model round
|
|
2779
2796
|
// trip: the error went back as a `tool_result`, the model read it and
|
|
@@ -2856,7 +2873,7 @@ export class ToolExecutor {
|
|
|
2856
2873
|
break
|
|
2857
2874
|
}
|
|
2858
2875
|
result = await this.runOnce(toolName, input, toolContext, prepared)
|
|
2859
|
-
post = await this.runPostToolHook(toolName, input, result)
|
|
2876
|
+
post = await this.runPostToolHook(toolName, input, result, toolCall.id)
|
|
2860
2877
|
}
|
|
2861
2878
|
const durationMs = Date.now() - startMs
|
|
2862
2879
|
|
|
@@ -3113,6 +3130,7 @@ export class ToolExecutor {
|
|
|
3113
3130
|
...(result.handoff !== undefined && !this.config.abortSignal.aborted
|
|
3114
3131
|
? { handoff: result.handoff }
|
|
3115
3132
|
: {}),
|
|
3133
|
+
...(result.launched === true && !effectiveIsError ? { launched: true as const } : {}),
|
|
3116
3134
|
...(revealedTools !== undefined ? { revealedTools } : {}),
|
|
3117
3135
|
...(result.facts !== undefined && !postOverride ? { facts: result.facts } : {}),
|
|
3118
3136
|
}
|
|
@@ -3497,6 +3515,7 @@ export class ToolExecutor {
|
|
|
3497
3515
|
toolName: string,
|
|
3498
3516
|
input: unknown,
|
|
3499
3517
|
toolResult: ToolResult,
|
|
3518
|
+
toolCallId?: string,
|
|
3500
3519
|
): Promise<{
|
|
3501
3520
|
override: PostToolOverride | null
|
|
3502
3521
|
retry: boolean
|
|
@@ -3511,6 +3530,7 @@ export class ToolExecutor {
|
|
|
3511
3530
|
sessionId: this.config.sessionId,
|
|
3512
3531
|
turnId: this.config.turnId,
|
|
3513
3532
|
toolName,
|
|
3533
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
3514
3534
|
toolInput: input,
|
|
3515
3535
|
toolResult,
|
|
3516
3536
|
signal: this.config.abortSignal,
|
|
@@ -3529,6 +3549,7 @@ export class ToolExecutor {
|
|
|
3529
3549
|
sessionId: this.config.sessionId,
|
|
3530
3550
|
turnId: this.config.turnId,
|
|
3531
3551
|
toolName,
|
|
3552
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
3532
3553
|
toolInput: input,
|
|
3533
3554
|
toolResult,
|
|
3534
3555
|
signal: this.config.abortSignal,
|
|
@@ -3671,7 +3692,7 @@ export class ToolExecutor {
|
|
|
3671
3692
|
isError: true,
|
|
3672
3693
|
})
|
|
3673
3694
|
|
|
3674
|
-
return { toolCallId: toolCall.id, toolName, output, isError: true }
|
|
3695
|
+
return { toolCallId: toolCall.id, toolName, output, isError: true, notRun: true }
|
|
3675
3696
|
}
|
|
3676
3697
|
|
|
3677
3698
|
/**
|
|
@@ -3722,6 +3743,7 @@ export class ToolExecutor {
|
|
|
3722
3743
|
const reason = abortReasonText(this.config.abortSignal.reason)
|
|
3723
3744
|
return this.recordSyntheticHookOutcome(toolCallId, toolName, input, {
|
|
3724
3745
|
kind: 'error',
|
|
3746
|
+
notRun: true,
|
|
3725
3747
|
output: `Tool "${toolName}" was not started because the turn was cancelled${reason ? `: ${reason}` : '.'}`,
|
|
3726
3748
|
})
|
|
3727
3749
|
}
|
|
@@ -3735,6 +3757,7 @@ export class ToolExecutor {
|
|
|
3735
3757
|
output: string
|
|
3736
3758
|
inputFailure?: ToolCallOutcome['inputFailure']
|
|
3737
3759
|
skipped?: true
|
|
3760
|
+
notRun?: true
|
|
3738
3761
|
facts?: ToolFacts
|
|
3739
3762
|
},
|
|
3740
3763
|
): Promise<ToolCallOutcome> {
|
|
@@ -3779,12 +3802,14 @@ export class ToolExecutor {
|
|
|
3779
3802
|
...(outcome.facts ? { facts: outcome.facts } : {}),
|
|
3780
3803
|
...(outcome.inputFailure ? { inputFailure: outcome.inputFailure } : {}),
|
|
3781
3804
|
...(outcome.skipped ? { skipped: true as const } : {}),
|
|
3805
|
+
...(outcome.notRun ? { notRun: true as const } : {}),
|
|
3782
3806
|
}
|
|
3783
3807
|
}
|
|
3784
3808
|
|
|
3785
3809
|
private recordSyntheticPreparation(call: Extract<PreparedDirectCall, { kind: 'synthetic' }>) {
|
|
3786
3810
|
return this.recordSyntheticHookOutcome(call.toolCall.id, call.toolName, call.input, {
|
|
3787
3811
|
kind: call.isError ? 'error' : 'skip',
|
|
3812
|
+
...(call.isError ? { notRun: true as const } : {}),
|
|
3788
3813
|
output: call.message,
|
|
3789
3814
|
...(call.facts ? { facts: call.facts } : {}),
|
|
3790
3815
|
...(call.inputFailure ? { inputFailure: call.inputFailure } : {}),
|
|
@@ -8,7 +8,7 @@ import type { TurnContext } from './context.js'
|
|
|
8
8
|
import type { EventTranslator } from './events.js'
|
|
9
9
|
import { runOutputGuardrails } from './guardrails.js'
|
|
10
10
|
import type { QueryParams } from './index.js'
|
|
11
|
-
import { applyLifecycleHookResults } from './plugin-hooks.js'
|
|
11
|
+
import { applyLifecycleHookResults, lastAssistantMessageField } from './plugin-hooks.js'
|
|
12
12
|
import type { ResultAssembler } from './result.js'
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -86,7 +86,12 @@ export async function* finalizeTurn(
|
|
|
86
86
|
if (params.pluginManager && finalization.turnEndHooksFired?.() !== true) {
|
|
87
87
|
const hookResults = await params.pluginManager.executeHooks(
|
|
88
88
|
'turn_end',
|
|
89
|
-
{
|
|
89
|
+
{
|
|
90
|
+
sessionId: ctx.sessionId,
|
|
91
|
+
turnId: ctx.turnId,
|
|
92
|
+
...lastAssistantMessageField(ctx.recorder.messages),
|
|
93
|
+
signal: ctx.abortController.signal,
|
|
94
|
+
},
|
|
90
95
|
eventTranslator.emitEvent,
|
|
91
96
|
)
|
|
92
97
|
applyLifecycleHookResults('turn_end', hookResults)
|
|
@@ -100,8 +105,10 @@ export async function* finalizeTurn(
|
|
|
100
105
|
{
|
|
101
106
|
sessionId: ctx.sessionId,
|
|
102
107
|
turnId: ctx.turnId,
|
|
108
|
+
...lastAssistantMessageField(ctx.recorder.messages),
|
|
103
109
|
parentSessionId: params.parentSessionId,
|
|
104
110
|
...(params.parentTurnId !== undefined ? { parentTurnId: params.parentTurnId } : {}),
|
|
111
|
+
agentType: params.agentType ?? params.agentName,
|
|
105
112
|
signal: ctx.abortController.signal,
|
|
106
113
|
},
|
|
107
114
|
eventTranslator.emitEvent,
|
|
@@ -70,7 +70,7 @@ import { SandboxAcquisitionStopped } from '../deferred-sandbox.js'
|
|
|
70
70
|
import { deliverDurableInbound } from '../durable-inbound.js'
|
|
71
71
|
import type { ToolCallOutcome } from '../executor.js'
|
|
72
72
|
import { projectObservationContext } from '../observation-context.js'
|
|
73
|
-
import { applyLifecycleHookResults } from '../plugin-hooks.js'
|
|
73
|
+
import { applyLifecycleHookResults, lastAssistantMessageField } from '../plugin-hooks.js'
|
|
74
74
|
import {
|
|
75
75
|
type RequestContextSnapshot,
|
|
76
76
|
diffRequestContext,
|
|
@@ -419,6 +419,7 @@ export class IterationOrchestrator {
|
|
|
419
419
|
sessionId: recorder.sessionId,
|
|
420
420
|
turnId: recorder.turnId,
|
|
421
421
|
...extra,
|
|
422
|
+
...lastAssistantMessageField(recorder.messages),
|
|
422
423
|
...(this.stopHookActive ? { stopHookActive: true } : {}),
|
|
423
424
|
signal: this.ctx.abortController.signal,
|
|
424
425
|
},
|
|
@@ -3505,6 +3506,8 @@ export class IterationOrchestrator {
|
|
|
3505
3506
|
input,
|
|
3506
3507
|
isError: result.isError,
|
|
3507
3508
|
...(result.skipped ? { skipped: true } : {}),
|
|
3509
|
+
...(result.notRun ? { notRun: true } : {}),
|
|
3510
|
+
...(result.launched ? { launched: true } : {}),
|
|
3508
3511
|
readOnly: this.lookedOnly(result.toolName, input),
|
|
3509
3512
|
...(result.facts ? { facts: result.facts } : {}),
|
|
3510
3513
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
resolveTriggerThreshold,
|
|
4
4
|
} from '../../../../compaction/context-window.js'
|
|
5
5
|
import { findDanglingMessages } from '../../../../compaction/dangling.js'
|
|
6
|
+
import { askPreCompact, postCompactSummary } from '../../../../compaction/hooks.js'
|
|
6
7
|
import {
|
|
7
8
|
LOADED_SKILLS_ENTRY_KEY,
|
|
8
9
|
type RenderedSkills,
|
|
@@ -781,6 +782,10 @@ async function recordShed(
|
|
|
781
782
|
interface CompactionPass {
|
|
782
783
|
attempted: boolean
|
|
783
784
|
reason: 'threshold' | 'overflow'
|
|
785
|
+
/** What the `pre_compact` hooks said the summary should keep. */
|
|
786
|
+
hookInstructions?: string
|
|
787
|
+
/** The summary the pass installed, for `post_compact`. */
|
|
788
|
+
summary?: string
|
|
784
789
|
tokensBefore: number
|
|
785
790
|
contextWindowTokens: number
|
|
786
791
|
}
|
|
@@ -807,9 +812,11 @@ export async function runCompactionCheck(
|
|
|
807
812
|
iteration: ctx.recorder.currentIteration,
|
|
808
813
|
compaction: {
|
|
809
814
|
reason: pass.reason,
|
|
815
|
+
trigger: 'auto',
|
|
810
816
|
tokensBefore: pass.tokensBefore,
|
|
811
817
|
tokensAfter: estimateTokens(ctx),
|
|
812
818
|
contextWindowTokens: pass.contextWindowTokens,
|
|
819
|
+
...postCompactSummary(pass.summary),
|
|
813
820
|
},
|
|
814
821
|
signal: ctx.abortController.signal,
|
|
815
822
|
},
|
|
@@ -903,24 +910,29 @@ async function runCompactionCheckInner(
|
|
|
903
910
|
pass.tokensBefore = estimatedTokens
|
|
904
911
|
pass.contextWindowTokens = budget
|
|
905
912
|
if (ctx.pluginManager) {
|
|
906
|
-
const
|
|
907
|
-
|
|
913
|
+
const answer = await askPreCompact(
|
|
914
|
+
ctx.pluginManager,
|
|
908
915
|
{
|
|
909
916
|
sessionId: ctx.recorder.sessionId,
|
|
910
917
|
turnId: ctx.recorder.turnId,
|
|
911
918
|
iteration: ctx.recorder.currentIteration,
|
|
912
|
-
compaction: {
|
|
913
|
-
reason: pass.reason,
|
|
914
|
-
tokensBefore: estimatedTokens,
|
|
915
|
-
contextWindowTokens: budget,
|
|
916
|
-
},
|
|
917
919
|
signal: ctx.abortController.signal,
|
|
920
|
+
emitEvent: ctx.emitEvent,
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
reason: pass.reason,
|
|
924
|
+
trigger: 'auto',
|
|
925
|
+
instructions: null,
|
|
926
|
+
tokensBefore: estimatedTokens,
|
|
927
|
+
contextWindowTokens: budget,
|
|
918
928
|
},
|
|
919
|
-
ctx.emitEvent,
|
|
920
929
|
)
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
930
|
+
// What the hooks said the summary should keep is heard even when a
|
|
931
|
+
// forced pass overrides a refusal. A reducer or the extracted state
|
|
932
|
+
// alone has no place for it (see `docs/sdk/hooks.md`).
|
|
933
|
+
if (answer.instructions !== undefined) pass.hookInstructions = answer.instructions
|
|
934
|
+
const refusal = answer.refusal !== undefined ? { reason: answer.refusal } : undefined
|
|
935
|
+
if (refusal) {
|
|
924
936
|
// A threshold pass is the context's own housekeeping and a hook may
|
|
925
937
|
// postpone it. A forced pass runs because the provider rejected the
|
|
926
938
|
// prompt: a context it will reject is not something a hook may insist
|
|
@@ -1130,6 +1142,7 @@ async function runCompactionCheckInner(
|
|
|
1130
1142
|
olderMessages,
|
|
1131
1143
|
previousSummaries: replaceableSummaries(systemMessages),
|
|
1132
1144
|
config,
|
|
1145
|
+
...(pass.hookInstructions !== undefined ? { hookInstructions: pass.hookInstructions } : {}),
|
|
1133
1146
|
resolveRoute: async (signal) => {
|
|
1134
1147
|
const route = await ctx.compactionSummaryRoute?.(signal)
|
|
1135
1148
|
if (route) {
|
|
@@ -1162,7 +1175,10 @@ async function runCompactionCheckInner(
|
|
|
1162
1175
|
|
|
1163
1176
|
if (modelWritten !== undefined) {
|
|
1164
1177
|
compactedContent = modelWritten
|
|
1165
|
-
} else if (
|
|
1178
|
+
} else if (
|
|
1179
|
+
pass.hookInstructions !== undefined ||
|
|
1180
|
+
(config.llmVerification && manager.slotCount() < config.richStateThreshold)
|
|
1181
|
+
) {
|
|
1166
1182
|
// Named once and used twice on purpose. The model the summariser is
|
|
1167
1183
|
// asked for is also the model its tokens are priced at, and computing
|
|
1168
1184
|
// the two separately is how they drift — a router that sends compaction
|
|
@@ -1188,6 +1204,8 @@ async function runCompactionCheckInner(
|
|
|
1188
1204
|
// was added, and nothing ever consulted it.
|
|
1189
1205
|
compactionModel,
|
|
1190
1206
|
ctx.abortController.signal,
|
|
1207
|
+
undefined,
|
|
1208
|
+
pass.hookInstructions,
|
|
1191
1209
|
)
|
|
1192
1210
|
} catch (error) {
|
|
1193
1211
|
// The model step only ADDS to the extracted state, so a failed one (a
|
|
@@ -1308,6 +1326,7 @@ async function runCompactionCheckInner(
|
|
|
1308
1326
|
await recordShed(ctx, [...live], newMessages, options?.force ? 'overflow' : 'threshold')
|
|
1309
1327
|
|
|
1310
1328
|
installMessages(live, newMessages)
|
|
1329
|
+
pass.summary = compactedContent
|
|
1311
1330
|
|
|
1312
1331
|
const newEstimate = estimateTokens(ctx)
|
|
1313
1332
|
|
|
@@ -318,6 +318,7 @@ export async function* runToolReview(
|
|
|
318
318
|
sessionId: ctx.recorder.sessionId,
|
|
319
319
|
turnId: ctx.recorder.turnId,
|
|
320
320
|
toolName: summary.name,
|
|
321
|
+
toolCallId: summary.id,
|
|
321
322
|
toolInput: summary.input,
|
|
322
323
|
reason,
|
|
323
324
|
signal: ctx.abortController.signal,
|
|
@@ -854,6 +855,7 @@ export async function* runToolReview(
|
|
|
854
855
|
sessionId: ctx.recorder.sessionId,
|
|
855
856
|
turnId: ctx.recorder.turnId,
|
|
856
857
|
toolName: tc.name,
|
|
858
|
+
toolCallId: tc.id,
|
|
857
859
|
toolInput: tc.input,
|
|
858
860
|
signal: ctx.abortController.signal,
|
|
859
861
|
},
|
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
import { boundLastAssistantMessage } from '../../plugin/hook-context.js'
|
|
1
2
|
import { NamzuError } from '../../types/errors/index.js'
|
|
3
|
+
import type { Message } from '../../types/message/index.js'
|
|
2
4
|
import type { PluginHookEvent, PluginHookResult } from '../../types/plugin/index.js'
|
|
5
|
+
import { isOperatorUserMessage } from './steering.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The model's last message of the turn, as a `turn_end` or `subagent_stop`
|
|
9
|
+
* hook receives it: the text of the last assistant message after the
|
|
10
|
+
* operator's latest prompt, bounded. A message that carries only tool calls,
|
|
11
|
+
* a turn that stopped before the model wrote anything, and a turn with no
|
|
12
|
+
* assistant message yet all give `undefined`: the field is then absent, and a
|
|
13
|
+
* hook never gets an earlier turn's words or an earlier step's narration in
|
|
14
|
+
* its place.
|
|
15
|
+
*/
|
|
16
|
+
export function lastAssistantMessageField(messages: readonly Message[]): {
|
|
17
|
+
lastAssistantMessage?: string
|
|
18
|
+
} {
|
|
19
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
20
|
+
const message = messages[index]
|
|
21
|
+
if (!message) continue
|
|
22
|
+
if (isOperatorUserMessage(message)) return {}
|
|
23
|
+
if (message.role !== 'assistant') continue
|
|
24
|
+
const text = boundLastAssistantMessage(message.content)
|
|
25
|
+
return text === undefined ? {} : { lastAssistantMessage: text }
|
|
26
|
+
}
|
|
27
|
+
return {}
|
|
28
|
+
}
|
|
3
29
|
|
|
4
30
|
/**
|
|
5
31
|
* Interpret hook results from non-tool lifecycle events (run_start, run_end,
|
|
@@ -19,9 +45,13 @@ export function applyLifecycleHookResults(
|
|
|
19
45
|
case 'continue':
|
|
20
46
|
continue
|
|
21
47
|
case 'annotate':
|
|
22
|
-
if (
|
|
48
|
+
if (
|
|
49
|
+
event !== 'user_prompt_submit' &&
|
|
50
|
+
event !== 'subagent_start' &&
|
|
51
|
+
event !== 'pre_compact'
|
|
52
|
+
) {
|
|
23
53
|
throw new Error(
|
|
24
|
-
`Plugin hook ${event} returned 'annotate', which only user_prompt_submit and
|
|
54
|
+
`Plugin hook ${event} returned 'annotate', which only user_prompt_submit, subagent_start and pre_compact accept`,
|
|
25
55
|
)
|
|
26
56
|
}
|
|
27
57
|
if (result.text.trim().length > 0) annotations.push(result.text.trim())
|
|
@@ -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
|
}
|
package/src/types/agent/query.ts
CHANGED
|
@@ -25,6 +25,15 @@ export interface QueryAgentConfig extends BaseAgentConfig {
|
|
|
25
25
|
|
|
26
26
|
persona?: AgentPersona
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The name lifecycle hooks see for this agent (`agentType` on
|
|
30
|
+
* `subagent_start` and `subagent_stop`, `agent_type` for a shell hook).
|
|
31
|
+
* Absent, it is the agent's `type`, which for a host-defined class is the
|
|
32
|
+
* class's marker rather than the agent's name: a host that names its
|
|
33
|
+
* agents passes the name here.
|
|
34
|
+
*/
|
|
35
|
+
agentType?: string
|
|
36
|
+
|
|
28
37
|
/**
|
|
29
38
|
* Channel a host uses to hand guidance to this turn's current turn.
|
|
30
39
|
*
|