@namzu/sdk 63.1.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 +53 -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.map +1 -1
- package/dist/runtime/query/executor.js +5 -3
- 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 +2 -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/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/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 +5 -2
- package/src/runtime/query/finalize-turn.ts +9 -2
- package/src/runtime/query/iteration/index.ts +2 -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/types/agent/query.ts +9 -0
- package/src/types/plugin/index.ts +66 -22
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',
|
|
@@ -2790,7 +2790,7 @@ export class ToolExecutor {
|
|
|
2790
2790
|
// block had it discarded here — before the wire mapper that was
|
|
2791
2791
|
// built to carry it ever saw it.
|
|
2792
2792
|
let result: ToolResult = await this.runOnce(toolName, input, toolContext, prepared)
|
|
2793
|
-
let post = await this.runPostToolHook(toolName, input, result)
|
|
2793
|
+
let post = await this.runPostToolHook(toolName, input, result, toolCall.id)
|
|
2794
2794
|
|
|
2795
2795
|
// In-loop retry. A transient failure used to cost a full model round
|
|
2796
2796
|
// trip: the error went back as a `tool_result`, the model read it and
|
|
@@ -2873,7 +2873,7 @@ export class ToolExecutor {
|
|
|
2873
2873
|
break
|
|
2874
2874
|
}
|
|
2875
2875
|
result = await this.runOnce(toolName, input, toolContext, prepared)
|
|
2876
|
-
post = await this.runPostToolHook(toolName, input, result)
|
|
2876
|
+
post = await this.runPostToolHook(toolName, input, result, toolCall.id)
|
|
2877
2877
|
}
|
|
2878
2878
|
const durationMs = Date.now() - startMs
|
|
2879
2879
|
|
|
@@ -3515,6 +3515,7 @@ export class ToolExecutor {
|
|
|
3515
3515
|
toolName: string,
|
|
3516
3516
|
input: unknown,
|
|
3517
3517
|
toolResult: ToolResult,
|
|
3518
|
+
toolCallId?: string,
|
|
3518
3519
|
): Promise<{
|
|
3519
3520
|
override: PostToolOverride | null
|
|
3520
3521
|
retry: boolean
|
|
@@ -3529,6 +3530,7 @@ export class ToolExecutor {
|
|
|
3529
3530
|
sessionId: this.config.sessionId,
|
|
3530
3531
|
turnId: this.config.turnId,
|
|
3531
3532
|
toolName,
|
|
3533
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
3532
3534
|
toolInput: input,
|
|
3533
3535
|
toolResult,
|
|
3534
3536
|
signal: this.config.abortSignal,
|
|
@@ -3547,6 +3549,7 @@ export class ToolExecutor {
|
|
|
3547
3549
|
sessionId: this.config.sessionId,
|
|
3548
3550
|
turnId: this.config.turnId,
|
|
3549
3551
|
toolName,
|
|
3552
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
3550
3553
|
toolInput: input,
|
|
3551
3554
|
toolResult,
|
|
3552
3555
|
signal: this.config.abortSignal,
|
|
@@ -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
|
},
|
|
@@ -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())
|
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
|
*
|
|
@@ -246,12 +246,36 @@ export interface PluginModelResponse {
|
|
|
246
246
|
|
|
247
247
|
/** What a compaction hook is shown about the pass. */
|
|
248
248
|
export interface PluginCompactionInfo {
|
|
249
|
-
/**
|
|
250
|
-
|
|
249
|
+
/**
|
|
250
|
+
* `threshold`: the estimate crossed the line. `overflow`: the provider
|
|
251
|
+
* rejected the prompt. `manual`: a host asked for the pass outside a turn
|
|
252
|
+
* (a person's `/compact`, a summarised span, an idle pass); test `trigger`
|
|
253
|
+
* to tell a person's request from the kernel's own.
|
|
254
|
+
*/
|
|
255
|
+
readonly reason: 'threshold' | 'overflow' | 'manual'
|
|
251
256
|
readonly tokensBefore: number
|
|
252
257
|
/** Present on `post_compact`. */
|
|
253
258
|
readonly tokensAfter?: number
|
|
254
259
|
readonly contextWindowTokens: number
|
|
260
|
+
/**
|
|
261
|
+
* Who started the pass: `manual` for one a person asked for (`/compact`,
|
|
262
|
+
* summarising a span), `auto` for every pass the kernel or a host started
|
|
263
|
+
* on its own (the threshold, a provider overflow, an idle pass). The
|
|
264
|
+
* kernel always sets it; it is optional only so a host that builds this
|
|
265
|
+
* context by hand keeps compiling. A matcher on these two events tests it.
|
|
266
|
+
*/
|
|
267
|
+
readonly trigger?: 'manual' | 'auto'
|
|
268
|
+
/**
|
|
269
|
+
* On `pre_compact`: what the person typed with the command, or `null` when
|
|
270
|
+
* they typed nothing or the pass is not one a person asked for.
|
|
271
|
+
*/
|
|
272
|
+
readonly instructions?: string | null
|
|
273
|
+
/**
|
|
274
|
+
* On `post_compact`: the summary the pass wrote, cut to
|
|
275
|
+
* `MAX_COMPACTION_SUMMARY_HOOK_CHARS`. Absent when the pass wrote none (a
|
|
276
|
+
* context reducer shed the history, or the pass shed nothing).
|
|
277
|
+
*/
|
|
278
|
+
readonly summary?: string
|
|
255
279
|
}
|
|
256
280
|
|
|
257
281
|
/** One call in a `post_tool_batch` hook's context. */
|
|
@@ -275,19 +299,22 @@ export interface PluginNotificationInfo {
|
|
|
275
299
|
}
|
|
276
300
|
|
|
277
301
|
/**
|
|
278
|
-
* Why a
|
|
302
|
+
* Why a conversation began, on `session_start`: `startup` for a conversation
|
|
279
303
|
* with no earlier turn, `resume` for one that already had turns (a resumed
|
|
280
|
-
* conversation, a `--continue`, or a session rebuilt under a running
|
|
304
|
+
* conversation, a `--continue`, a fork, or a session rebuilt under a running
|
|
305
|
+
* one), `clear` for the empty conversation a host opens when the person
|
|
306
|
+
* clears the old one (`/clear`, `/new`).
|
|
281
307
|
*/
|
|
282
|
-
export type PluginSessionSource = 'startup' | 'resume'
|
|
308
|
+
export type PluginSessionSource = 'startup' | 'resume' | 'clear'
|
|
283
309
|
|
|
284
310
|
/**
|
|
285
|
-
* Why a
|
|
286
|
-
* with it (the process ends, the person quits), `replaced` when the
|
|
287
|
-
*
|
|
288
|
-
* conversation opened)
|
|
311
|
+
* Why a conversation ended, on `session_end`: `exit` when the host is
|
|
312
|
+
* finishing with it (the process ends, the person quits), `replaced` when the
|
|
313
|
+
* host moves to another session or conversation in its place (a model or
|
|
314
|
+
* account switch, another conversation opened or forked), `clear` when the
|
|
315
|
+
* person cleared it and a new, empty conversation begins (`/clear`, `/new`).
|
|
289
316
|
*/
|
|
290
|
-
export type PluginSessionEndReason = 'exit' | 'replaced'
|
|
317
|
+
export type PluginSessionEndReason = 'exit' | 'replaced' | 'clear'
|
|
291
318
|
|
|
292
319
|
export interface PluginHookContext {
|
|
293
320
|
/** The turn the hook fired in. Absent on `session_start` and `session_end`. */
|
|
@@ -311,6 +338,14 @@ export interface PluginHookContext {
|
|
|
311
338
|
/** The pass, on `pre_compact` and `post_compact`. */
|
|
312
339
|
readonly compaction?: PluginCompactionInfo
|
|
313
340
|
readonly toolName?: string
|
|
341
|
+
/**
|
|
342
|
+
* The id of the call, on `pre_tool_use`, `post_tool_use`,
|
|
343
|
+
* `post_tool_use_failure`, `permission_request` and `permission_denied`: the
|
|
344
|
+
* same id `post_tool_batch` carries as `toolCallId`, so a hook can pair a
|
|
345
|
+
* before with an after. Absent on a call a tool makes through
|
|
346
|
+
* `dispatchTool` (it has no model-issued id).
|
|
347
|
+
*/
|
|
348
|
+
readonly toolCallId?: string
|
|
314
349
|
readonly toolInput?: unknown
|
|
315
350
|
readonly toolResult?: ToolResult
|
|
316
351
|
/** The calls of one model step with their outcomes, on `post_tool_batch`. */
|
|
@@ -328,6 +363,13 @@ export interface PluginHookContext {
|
|
|
328
363
|
* keep the model working until the turn's own limits stop it.
|
|
329
364
|
*/
|
|
330
365
|
readonly stopHookActive?: boolean
|
|
366
|
+
/**
|
|
367
|
+
* On `turn_end` and `subagent_stop`: the text of the model's last message
|
|
368
|
+
* of the turn, cut to `MAX_LAST_MESSAGE_HOOK_CHARS`. Absent when the turn
|
|
369
|
+
* produced no text (it ended on a tool call, was interrupted before the
|
|
370
|
+
* model wrote anything, or failed).
|
|
371
|
+
*/
|
|
372
|
+
readonly lastAssistantMessage?: string
|
|
331
373
|
readonly iteration?: number
|
|
332
374
|
/**
|
|
333
375
|
* Why the turn was stopped, on `turn_interrupt`.
|
|
@@ -501,18 +543,20 @@ export type PluginHookResultFor<E extends PluginHookEvent> = E extends 'user_pro
|
|
|
501
543
|
? Extract<PluginHookResult, { action: 'continue' | 'error' }>
|
|
502
544
|
: E extends 'permission_request'
|
|
503
545
|
? Extract<PluginHookResult, { action: 'continue' | 'skip' }>
|
|
504
|
-
: E extends '
|
|
505
|
-
? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'error' }>
|
|
506
|
-
: E extends '
|
|
507
|
-
? Extract<PluginHookResult, { action: 'continue' | '
|
|
508
|
-
: E extends
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
546
|
+
: E extends 'pre_compact'
|
|
547
|
+
? Extract<PluginHookResult, { action: 'continue' | 'annotate' | 'skip' | 'error' }>
|
|
548
|
+
: E extends 'turn_end' | 'subagent_stop'
|
|
549
|
+
? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'error' }>
|
|
550
|
+
: E extends 'session_start'
|
|
551
|
+
? Extract<PluginHookResult, { action: 'continue' | 'annotate' }>
|
|
552
|
+
: E extends
|
|
553
|
+
| 'turn_interrupt'
|
|
554
|
+
| 'session_end'
|
|
555
|
+
| 'notification'
|
|
556
|
+
| 'turn_failure'
|
|
557
|
+
| 'permission_denied'
|
|
558
|
+
? Extract<PluginHookResult, { action: 'continue' }>
|
|
559
|
+
: Extract<PluginHookResult, { action: 'continue' | 'error' }>
|
|
516
560
|
|
|
517
561
|
/** The event and its handler verdict stay correlated, including in hook arrays. */
|
|
518
562
|
export type PluginHookDefinition<E extends PluginHookEvent = PluginHookEvent> =
|