@namzu/sdk 57.1.0 → 58.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 +72 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/file-definitions.d.ts +35 -4
- package/dist/agents/file-definitions.d.ts.map +1 -1
- package/dist/agents/file-definitions.js +98 -8
- package/dist/agents/file-definitions.js.map +1 -1
- package/dist/authorization/protected-paths.d.ts +9 -0
- package/dist/authorization/protected-paths.d.ts.map +1 -1
- package/dist/authorization/protected-paths.js +29 -3
- package/dist/authorization/protected-paths.js.map +1 -1
- package/dist/compaction/context-window.d.ts +1 -1
- package/dist/compaction/context-window.d.ts.map +1 -1
- package/dist/compaction/context-window.js +8 -1
- package/dist/compaction/context-window.js.map +1 -1
- package/dist/compaction/plan.d.ts.map +1 -1
- package/dist/compaction/plan.js +2 -2
- package/dist/compaction/plan.js.map +1 -1
- package/dist/config/runtime.d.ts +10 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +2 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/mcp/client.d.ts +8 -0
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +75 -2
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/constants/mcp/index.d.ts +2 -0
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +2 -0
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/constants/plugin/index.d.ts +2 -0
- package/dist/constants/plugin/index.d.ts.map +1 -1
- package/dist/constants/plugin/index.js +2 -0
- package/dist/constants/plugin/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +25 -4
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +2 -1
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +2 -2
- package/dist/persona/assembler.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/lifecycle.d.ts +8 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +9 -2
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/shell-hook-http.d.ts +39 -0
- package/dist/plugin/shell-hook-http.d.ts.map +1 -0
- package/dist/plugin/shell-hook-http.js +163 -0
- package/dist/plugin/shell-hook-http.js.map +1 -0
- package/dist/plugin/shell-hook.d.ts +93 -11
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +223 -39
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +2 -0
- package/dist/public-tools.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.d.ts +1 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +34 -9
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +2 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +25 -1
- 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 +47 -10
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +18 -8
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +33 -9
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +7 -5
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.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 +70 -3
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts +2 -0
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +7 -4
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +2 -2
- package/dist/runtime/query/plugin-hooks.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts +2 -0
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +25 -0
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +3 -2
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +32 -13
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/session/retention/archive.js +1 -1
- package/dist/session/retention/archive.js.map +1 -1
- package/dist/session/workspace/driver.d.ts +6 -0
- package/dist/session/workspace/driver.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +28 -0
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/loader.d.ts +7 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +53 -0
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/registry.d.ts +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +2 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/builtins/bash-cwd.d.ts +52 -0
- package/dist/tools/builtins/bash-cwd.d.ts.map +1 -0
- package/dist/tools/builtins/bash-cwd.js +127 -0
- package/dist/tools/builtins/bash-cwd.js.map +1 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +41 -6
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/bounded-file-read.d.ts +10 -0
- package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -0
- package/dist/tools/builtins/bounded-file-read.js +67 -0
- package/dist/tools/builtins/bounded-file-read.js.map +1 -0
- package/dist/tools/builtins/grep.d.ts +6 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +187 -24
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts +1 -0
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +113 -28
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/read-media.d.ts +68 -0
- package/dist/tools/builtins/read-media.d.ts.map +1 -0
- package/dist/tools/builtins/read-media.js +397 -0
- package/dist/tools/builtins/read-media.js.map +1 -0
- package/dist/tools/builtins/search-tools.js +1 -1
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/builtins/skill.d.ts +4 -2
- package/dist/tools/builtins/skill.d.ts.map +1 -1
- package/dist/tools/builtins/skill.js +61 -2
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/builtins/sleep-guard.d.ts +15 -0
- package/dist/tools/builtins/sleep-guard.d.ts.map +1 -0
- package/dist/tools/builtins/sleep-guard.js +33 -0
- package/dist/tools/builtins/sleep-guard.js.map +1 -0
- package/dist/tools/builtins/view-image.d.ts +23 -1
- package/dist/tools/builtins/view-image.d.ts.map +1 -1
- package/dist/tools/builtins/view-image.js +66 -58
- package/dist/tools/builtins/view-image.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +31 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/toolsets/manager.js +3 -3
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +6 -2
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/agent/task.js.map +1 -1
- package/dist/types/connector/mcp.d.ts +46 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +1 -1
- package/dist/types/plugin/index.d.ts +72 -5
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +12 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/session/sub-session.d.ts +6 -2
- package/dist/types/session/sub-session.d.ts.map +1 -1
- package/dist/types/skills/index.d.ts +36 -0
- package/dist/types/skills/index.d.ts.map +1 -1
- package/dist/types/skills/index.js +9 -0
- package/dist/types/skills/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +22 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/workspace/ref.d.ts +2 -0
- package/dist/types/workspace/ref.d.ts.map +1 -1
- package/dist/utils/frontmatter.d.ts +12 -0
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +6 -0
- package/dist/utils/frontmatter.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +2 -0
- package/src/agents/file-definitions.ts +126 -6
- package/src/authorization/protected-paths.ts +30 -2
- package/src/compaction/context-window.ts +10 -0
- package/src/compaction/plan.ts +12 -2
- package/src/config/runtime.ts +2 -0
- package/src/connector/mcp/client.ts +87 -1
- package/src/constants/mcp/index.ts +3 -0
- package/src/constants/plugin/index.ts +2 -0
- package/src/manager/agent/lifecycle.ts +24 -5
- package/src/manager/session/turn-recorder.ts +2 -1
- package/src/persona/assembler.ts +2 -2
- package/src/plugin/index.ts +5 -0
- package/src/plugin/lifecycle.ts +16 -2
- package/src/plugin/shell-hook-http.ts +181 -0
- package/src/plugin/shell-hook.ts +386 -89
- package/src/public-runtime.ts +2 -0
- package/src/public-tools.ts +2 -0
- package/src/public-types.ts +5 -0
- package/src/runtime/query/executor.ts +40 -10
- package/src/runtime/query/index.ts +33 -0
- package/src/runtime/query/iteration/index.ts +51 -11
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +38 -9
- package/src/runtime/query/iteration/phases/compaction.ts +27 -5
- package/src/runtime/query/iteration/phases/context.ts +2 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +74 -3
- package/src/runtime/query/path-escalation.ts +11 -5
- package/src/runtime/query/plugin-hooks.ts +2 -2
- package/src/runtime/query/prepare-turn.ts +33 -0
- package/src/runtime/query/review-policy.ts +45 -16
- package/src/session/retention/archive.ts +1 -1
- package/src/session/workspace/driver.ts +6 -0
- package/src/session/workspace/git-worktree.ts +31 -0
- package/src/skills/index.ts +6 -1
- package/src/skills/loader.ts +54 -0
- package/src/skills/registry.ts +7 -6
- package/src/tools/builtins/bash-cwd.ts +156 -0
- package/src/tools/builtins/bash.ts +79 -34
- package/src/tools/builtins/bounded-file-read.ts +80 -0
- package/src/tools/builtins/grep.ts +198 -28
- package/src/tools/builtins/read-file.ts +151 -30
- package/src/tools/builtins/read-media.ts +483 -0
- package/src/tools/builtins/search-tools.ts +1 -1
- package/src/tools/builtins/skill.ts +91 -2
- package/src/tools/builtins/sleep-guard.ts +31 -0
- package/src/tools/builtins/view-image.ts +74 -62
- package/src/tools/file-read-tracker.ts +30 -0
- package/src/toolsets/manager.ts +3 -3
- package/src/types/agent/query.ts +2 -0
- package/src/types/agent/task.ts +6 -2
- package/src/types/connector/mcp.ts +48 -0
- package/src/types/hitl/index.ts +1 -1
- package/src/types/plugin/index.ts +102 -16
- package/src/types/session/sub-session.ts +6 -2
- package/src/types/skills/index.ts +48 -0
- package/src/types/tool/index.ts +22 -0
- package/src/types/workspace/ref.ts +2 -0
- package/src/utils/frontmatter.ts +21 -0
package/src/public-runtime.ts
CHANGED
|
@@ -485,6 +485,7 @@ export {
|
|
|
485
485
|
loadSkill,
|
|
486
486
|
resolveSkillChain,
|
|
487
487
|
SKILL_FRONTMATTER_KEYS,
|
|
488
|
+
SKILL_FRONTMATTER_TOLERATED_KEYS,
|
|
488
489
|
SkillCollisionError,
|
|
489
490
|
SkillRegistry,
|
|
490
491
|
} from './skills/index.js'
|
|
@@ -1521,6 +1522,7 @@ export {
|
|
|
1521
1522
|
SKILL_INVOCATION_DEFAULT,
|
|
1522
1523
|
isInvocableBy,
|
|
1523
1524
|
skillInvocation,
|
|
1525
|
+
skillModelDescription,
|
|
1524
1526
|
} from './types/skills/index.js'
|
|
1525
1527
|
|
|
1526
1528
|
// Reaching the web. Fetching ships guarded because its rules are about the
|
package/src/public-tools.ts
CHANGED
|
@@ -79,6 +79,8 @@ export {
|
|
|
79
79
|
backgroundTasksDisabled,
|
|
80
80
|
execHostShell,
|
|
81
81
|
} from './tools/builtins/bash.js'
|
|
82
|
+
// Authorization and execution must agree after a session carries its shell cwd.
|
|
83
|
+
export { effectiveBashWorkingDirectory } from './tools/builtins/bash-cwd.js'
|
|
82
84
|
export {
|
|
83
85
|
CODE_ANALYSIS_TOOL_NAME,
|
|
84
86
|
CodeAnalysisTool,
|
package/src/public-types.ts
CHANGED
|
@@ -231,8 +231,13 @@ export type {
|
|
|
231
231
|
SkippedAgentFile,
|
|
232
232
|
} from './agents/file-definitions.js'
|
|
233
233
|
export type {
|
|
234
|
+
HttpHookEntry,
|
|
235
|
+
PromptHookEntry,
|
|
234
236
|
ShellHookEntry,
|
|
235
237
|
ShellHookEvent,
|
|
238
|
+
ShellHookHandler,
|
|
239
|
+
ShellHookJudge,
|
|
240
|
+
ShellHookOnFailure,
|
|
236
241
|
ShellHookInput,
|
|
237
242
|
ShellHookOptions,
|
|
238
243
|
ShellHookOutcome,
|
|
@@ -14,6 +14,11 @@ import { ProbeVetoError } from '../../probe/errors.js'
|
|
|
14
14
|
import { probe as defaultProbeRegistry } from '../../probe/registry.js'
|
|
15
15
|
import type { ProbeEnforcement } from '../../probe/registry.js'
|
|
16
16
|
import type { ActivityStore } from '../../store/activity/memory.js'
|
|
17
|
+
import {
|
|
18
|
+
effectiveBashWorkingDirectory,
|
|
19
|
+
pinBashWorkingDirectory,
|
|
20
|
+
} from '../../tools/builtins/bash-cwd.js'
|
|
21
|
+
import { BashTool } from '../../tools/builtins/bash.js'
|
|
17
22
|
import { fingerprintContent } from '../../tools/builtins/content-fingerprint.js'
|
|
18
23
|
import { sandboxShellSpawn, withoutBashStartup } from '../../tools/command-shell.js'
|
|
19
24
|
import { isAlwaysDestructive } from '../../tools/defineTool.js'
|
|
@@ -99,6 +104,9 @@ import {
|
|
|
99
104
|
export type { EmitEvent } from './events.js'
|
|
100
105
|
import type { EmitEvent, SessionEventDraft } from './events.js'
|
|
101
106
|
|
|
107
|
+
/** Input and identity needed by the shared direct/nested safety checks. */
|
|
108
|
+
type PreparedSafetyCall = Pick<PreparedDirectCall, 'kind' | 'toolName' | 'input'>
|
|
109
|
+
|
|
102
110
|
export type PreparedDirectCall =
|
|
103
111
|
| {
|
|
104
112
|
readonly kind: 'ready'
|
|
@@ -708,6 +716,7 @@ export class ToolExecutor {
|
|
|
708
716
|
private parentSpan?: Span
|
|
709
717
|
private readonly toolCallBudget?: ToolCallBudget
|
|
710
718
|
private readonly preparedBatches = new WeakSet<PreparedToolBatch>()
|
|
719
|
+
private readonly preparedBashDirectories = new WeakMap<PreparedSafetyCall, string>()
|
|
711
720
|
/** Ephemeral proof of actual execution; restored messages do not acquire it. */
|
|
712
721
|
private readonly recordedObservations = new Map<
|
|
713
722
|
string,
|
|
@@ -1180,7 +1189,7 @@ export class ToolExecutor {
|
|
|
1180
1189
|
* safety checks did not run is the one a person must look at.
|
|
1181
1190
|
*/
|
|
1182
1191
|
private async escalationOrUnfinished(
|
|
1183
|
-
call:
|
|
1192
|
+
call: PreparedSafetyCall,
|
|
1184
1193
|
): Promise<ToolCallEscalation | undefined> {
|
|
1185
1194
|
try {
|
|
1186
1195
|
return await this.escalationOf(call)
|
|
@@ -1212,12 +1221,19 @@ export class ToolExecutor {
|
|
|
1212
1221
|
* roots is not mounted there to be approved. The escape is looked at only
|
|
1213
1222
|
* WITH one, since without one there is nothing to escape.
|
|
1214
1223
|
*/
|
|
1215
|
-
private async escalationOf(call:
|
|
1224
|
+
private async escalationOf(call: PreparedSafetyCall): Promise<ToolCallEscalation | undefined> {
|
|
1216
1225
|
if (call.kind === 'synthetic') return undefined
|
|
1217
1226
|
const tool = this.config.tools.get(call.toolName)
|
|
1218
1227
|
if (!tool || call.input === null || typeof call.input !== 'object') return undefined
|
|
1219
1228
|
const input = call.input as Record<string, unknown>
|
|
1220
1229
|
const sandboxed = this.sandboxed
|
|
1230
|
+
const shellCwd =
|
|
1231
|
+
tool.execute === BashTool.execute
|
|
1232
|
+
? effectiveBashWorkingDirectory(this.config.sessionId, this.config.workingDirectory, {
|
|
1233
|
+
sandboxed,
|
|
1234
|
+
})
|
|
1235
|
+
: undefined
|
|
1236
|
+
if (shellCwd !== undefined) this.preparedBashDirectories.set(call, shellCwd)
|
|
1221
1237
|
let outsidePaths: string[] | undefined
|
|
1222
1238
|
if (
|
|
1223
1239
|
this.config.outsideRootAccess === 'review' &&
|
|
@@ -1240,7 +1256,7 @@ export class ToolExecutor {
|
|
|
1240
1256
|
tool.sandboxEscapeArgument !== undefined &&
|
|
1241
1257
|
input[tool.sandboxEscapeArgument] === true
|
|
1242
1258
|
const unknownProgram = this.unknownProgramOf(tool, input, sandboxed)
|
|
1243
|
-
const reaches = await this.pathEscalationsOf(tool, input, sandboxed)
|
|
1259
|
+
const reaches = await this.pathEscalationsOf(tool, input, sandboxed, shellCwd)
|
|
1244
1260
|
if (reaches.outsidePaths.length > 0) {
|
|
1245
1261
|
outsidePaths = [...new Set([...(outsidePaths ?? []), ...reaches.outsidePaths])]
|
|
1246
1262
|
}
|
|
@@ -1281,6 +1297,7 @@ export class ToolExecutor {
|
|
|
1281
1297
|
>,
|
|
1282
1298
|
input: Record<string, unknown>,
|
|
1283
1299
|
sandboxed: boolean,
|
|
1300
|
+
shellCwd?: string,
|
|
1284
1301
|
): Promise<{
|
|
1285
1302
|
outsidePaths: readonly string[]
|
|
1286
1303
|
protectedPaths: readonly string[]
|
|
@@ -1303,6 +1320,7 @@ export class ToolExecutor {
|
|
|
1303
1320
|
const dialect = tool.commandDialect?.({ sandboxed }) ?? 'sh'
|
|
1304
1321
|
const found = await shellEscalations(line, dialect, {
|
|
1305
1322
|
roots,
|
|
1323
|
+
...(shellCwd !== undefined ? { cwd: shellCwd } : {}),
|
|
1306
1324
|
reviewOutside: this.config.outsideRootAccess === 'review' && !sandboxed,
|
|
1307
1325
|
})
|
|
1308
1326
|
protectedPaths.push(...found.protectedPaths)
|
|
@@ -1598,6 +1616,10 @@ export class ToolExecutor {
|
|
|
1598
1616
|
...(this.config.toolPause ? { requestPause: this.config.toolPause(toolCall.id) } : {}),
|
|
1599
1617
|
report: (message: string, fraction?: number) => progress.report(message, fraction),
|
|
1600
1618
|
}
|
|
1619
|
+
if (preparedCall) {
|
|
1620
|
+
const cwd = this.preparedBashDirectories.get(preparedCall)
|
|
1621
|
+
if (cwd !== undefined) pinBashWorkingDirectory(ctx, cwd)
|
|
1622
|
+
}
|
|
1601
1623
|
const run = async () => {
|
|
1602
1624
|
try {
|
|
1603
1625
|
results[i] = await this.executeSingle(
|
|
@@ -1849,7 +1871,10 @@ export class ToolExecutor {
|
|
|
1849
1871
|
signal.throwIfAborted()
|
|
1850
1872
|
// Preparation detaches input synchronously before its first await.
|
|
1851
1873
|
// Admission must not add an earlier yield where the caller can mutate it.
|
|
1852
|
-
const preparedCall =
|
|
1874
|
+
const preparedCall = {
|
|
1875
|
+
...(await this.prepareNestedCall(name, input, signal)),
|
|
1876
|
+
toolName: name,
|
|
1877
|
+
}
|
|
1853
1878
|
signal.throwIfAborted()
|
|
1854
1879
|
if (this.toolCallBudget) {
|
|
1855
1880
|
const refusal = await this.toolCallBudget.admit(1, 'nested', signal)
|
|
@@ -1932,12 +1957,15 @@ export class ToolExecutor {
|
|
|
1932
1957
|
} catch {
|
|
1933
1958
|
needsApproval = true
|
|
1934
1959
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
:
|
|
1939
|
-
|
|
1940
|
-
|
|
1960
|
+
const nestedEscalation = await this.escalationOrUnfinished(preparedCall)
|
|
1961
|
+
if (nestedEscalation || needsApproval || (gateResult && gateResult.decision !== 'allow')) {
|
|
1962
|
+
const reason = nestedEscalation
|
|
1963
|
+
? 'Blocked: this nested call reaches a protected path, removal floor, outside-root boundary or another safety escalation. Call it directly so it can be reviewed.'
|
|
1964
|
+
: needsApproval
|
|
1965
|
+
? `Blocked: "${name}" requires the person's approval for every call, and an approval cannot be requested from inside another tool. Call it directly.`
|
|
1966
|
+
: gateResult?.decision === 'deny'
|
|
1967
|
+
? `Blocked by the authorization gate: ${gateResult.reason}`
|
|
1968
|
+
: `Blocked by the authorization gate: this nested call requires an explicit allow rule because an operator review cannot be opened from inside another tool. ${gateResult?.reason}`
|
|
1941
1969
|
const output = deniedToolOutput(name, reason)
|
|
1942
1970
|
// Same fail-closed durability rule as a direct gate denial: if the
|
|
1943
1971
|
// configured session log cannot record the refusal, do not quietly carry
|
|
@@ -2010,6 +2038,8 @@ export class ToolExecutor {
|
|
|
2010
2038
|
report: (message: string, fraction?: number) => progress.report(message, fraction),
|
|
2011
2039
|
}
|
|
2012
2040
|
|
|
2041
|
+
const nestedCwd = this.preparedBashDirectories.get(preparedCall)
|
|
2042
|
+
if (nestedCwd !== undefined) pinBashWorkingDirectory(childContext, nestedCwd)
|
|
2013
2043
|
await this.emitEvent({
|
|
2014
2044
|
type: 'tool_executing',
|
|
2015
2045
|
turnId: this.config.turnId,
|
|
@@ -553,6 +553,8 @@ export interface QueryParams {
|
|
|
553
553
|
allowedTools?: string[]
|
|
554
554
|
agentId: string
|
|
555
555
|
agentName: string
|
|
556
|
+
/** Application-owned agent kind for lifecycle hooks; defaults to agentName. */
|
|
557
|
+
agentType?: string
|
|
556
558
|
workingDirectory?: string
|
|
557
559
|
/**
|
|
558
560
|
* Directories besides the working directory the file tools may reach,
|
|
@@ -1071,6 +1073,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1071
1073
|
approvalPolicy,
|
|
1072
1074
|
eventTranslator,
|
|
1073
1075
|
executeUserInterruptHooks,
|
|
1076
|
+
executeTurnFailureHooks,
|
|
1074
1077
|
pendingHistoryRepairs,
|
|
1075
1078
|
initialMessages,
|
|
1076
1079
|
historyIds,
|
|
@@ -1877,6 +1880,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1877
1880
|
pluginManager: params.pluginManager,
|
|
1878
1881
|
...(params.parentSessionId !== undefined ? { parentSessionId: params.parentSessionId } : {}),
|
|
1879
1882
|
...(params.parentTurnId !== undefined ? { parentTurnId: params.parentTurnId } : {}),
|
|
1883
|
+
agentType: params.agentType ?? params.agentName,
|
|
1880
1884
|
})
|
|
1881
1885
|
|
|
1882
1886
|
const tracer = getTracer()
|
|
@@ -2041,6 +2045,33 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
2041
2045
|
'namzu.runtime.resume_from_checkpoint': params.resumeFromCheckpoint ?? null,
|
|
2042
2046
|
})
|
|
2043
2047
|
|
|
2048
|
+
// A child session is starting: its hooks may add context for the
|
|
2049
|
+
// child's turn, before the child's first prompt is seen.
|
|
2050
|
+
if (params.pluginManager && params.parentSessionId !== undefined) {
|
|
2051
|
+
const startResults = await params.pluginManager.executeHooks(
|
|
2052
|
+
'subagent_start',
|
|
2053
|
+
{
|
|
2054
|
+
turnId: ctx.turnId,
|
|
2055
|
+
sessionId: params.sessionId,
|
|
2056
|
+
parentSessionId: params.parentSessionId,
|
|
2057
|
+
...(params.parentTurnId !== undefined ? { parentTurnId: params.parentTurnId } : {}),
|
|
2058
|
+
agentType: params.agentType ?? params.agentName,
|
|
2059
|
+
signal: ctx.abortController.signal,
|
|
2060
|
+
},
|
|
2061
|
+
eventTranslator.emitEvent,
|
|
2062
|
+
)
|
|
2063
|
+
const startAnnotations = applyLifecycleHookResults('subagent_start', startResults)
|
|
2064
|
+
if (startAnnotations.length > 0) {
|
|
2065
|
+
promptContributions.replace({
|
|
2066
|
+
id: 'hooks:subagent_start',
|
|
2067
|
+
placement: 'context',
|
|
2068
|
+
render: () =>
|
|
2069
|
+
`Context from the operator's hooks:\n\n${startAnnotations.join('\n\n')}`,
|
|
2070
|
+
})
|
|
2071
|
+
}
|
|
2072
|
+
yield* eventTranslator.drainPending()
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2044
2075
|
// The operator's prompt, before the model sees it. A hook may
|
|
2045
2076
|
// refuse it — the turn ends here, before a prompt is built — or add
|
|
2046
2077
|
// to what the model is told; the addition rides the prompt as a
|
|
@@ -2799,6 +2830,8 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
2799
2830
|
ctx.recorder.setSteps(iterationOrchestrator.getSteps())
|
|
2800
2831
|
await executeUserInterruptHooks(err)
|
|
2801
2832
|
yield* eventTranslator.drainPending()
|
|
2833
|
+
await executeTurnFailureHooks(err)
|
|
2834
|
+
yield* eventTranslator.drainPending()
|
|
2802
2835
|
yield* resultAssembler.handleError(err, rootSpan)
|
|
2803
2836
|
} finally {
|
|
2804
2837
|
// A pending acquisition is withdrawn and awaited first, so a handle
|
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
} from './outstanding-work.js'
|
|
80
80
|
import { runAdvisoryPhase } from './phases/advisory.js'
|
|
81
81
|
import { runIterationCheckpoint } from './phases/checkpoint.js'
|
|
82
|
-
import { compactionBreaker } from './phases/compaction-breaker.js'
|
|
82
|
+
import { compactionBreaker, estimateRefillTokens } from './phases/compaction-breaker.js'
|
|
83
83
|
import {
|
|
84
84
|
AUTOMATIC_COMPACTION_FAILED,
|
|
85
85
|
OVERFLOW_RELIEF_LIMIT,
|
|
@@ -319,7 +319,7 @@ export class IterationOrchestrator {
|
|
|
319
319
|
const feedback: string[] = []
|
|
320
320
|
const ask = async (
|
|
321
321
|
event: 'turn_end' | 'subagent_stop',
|
|
322
|
-
extra: { parentSessionId?: SessionId; parentTurnId?: TurnId },
|
|
322
|
+
extra: { parentSessionId?: SessionId; parentTurnId?: TurnId; agentType?: string },
|
|
323
323
|
): Promise<void> => {
|
|
324
324
|
const results = await manager.executeHooks(
|
|
325
325
|
event,
|
|
@@ -346,6 +346,7 @@ export class IterationOrchestrator {
|
|
|
346
346
|
await ask('subagent_stop', {
|
|
347
347
|
parentSessionId: this.ctx.parentSessionId,
|
|
348
348
|
...(this.ctx.parentTurnId !== undefined ? { parentTurnId: this.ctx.parentTurnId } : {}),
|
|
349
|
+
...(this.ctx.agentType !== undefined ? { agentType: this.ctx.agentType } : {}),
|
|
349
350
|
})
|
|
350
351
|
yield* this.ctx.drainPending()
|
|
351
352
|
}
|
|
@@ -1491,10 +1492,10 @@ export class IterationOrchestrator {
|
|
|
1491
1492
|
// Auto-continuation after an output-ceiling cutoff.
|
|
1492
1493
|
//
|
|
1493
1494
|
// Guards:
|
|
1494
|
-
// -
|
|
1495
|
-
//
|
|
1496
|
-
//
|
|
1497
|
-
//
|
|
1495
|
+
// - An empty cutoff with no exposed reasoning only retries
|
|
1496
|
+
// under an explicit cap. Compatible endpoints can spend
|
|
1497
|
+
// the output allowance without exposing any text; without
|
|
1498
|
+
// a cap the legacy empty-completion fallback stays bounded.
|
|
1498
1499
|
// - `!forceFinalize` so the forced-finalize path
|
|
1499
1500
|
// never auto-continues — that path is invoked
|
|
1500
1501
|
// specifically to extract a closing summary.
|
|
@@ -1507,16 +1508,18 @@ export class IterationOrchestrator {
|
|
|
1507
1508
|
!hasContent &&
|
|
1508
1509
|
(response.message.toolCalls?.length ?? 0) === 0 &&
|
|
1509
1510
|
(response.message.reasoning?.length ?? 0) > 0
|
|
1511
|
+
const cap = this.ctx.maxOutputContinuations
|
|
1512
|
+
const boundedEmptyCut =
|
|
1513
|
+
!hasContent && cap !== undefined && Number.isSafeInteger(cap) && cap >= 0
|
|
1510
1514
|
if (
|
|
1511
1515
|
!forceFinalize &&
|
|
1512
1516
|
response.finishReason === 'length' &&
|
|
1513
1517
|
response.finishDetail !== 'context_window' &&
|
|
1514
|
-
(hasContent || cutDuringReasoning)
|
|
1518
|
+
(hasContent || cutDuringReasoning || boundedEmptyCut)
|
|
1515
1519
|
) {
|
|
1516
1520
|
// A host that opted in gets a cap on the nudges: a model that
|
|
1517
1521
|
// overruns its output limit every time would otherwise spend the
|
|
1518
1522
|
// whole turn on it. Past the cap the turn stops, and says why.
|
|
1519
|
-
const cap = this.ctx.maxOutputContinuations
|
|
1520
1523
|
if (cap !== undefined && this.outputContinuations >= cap) {
|
|
1521
1524
|
this.ctx.log.warn('LLM kept hitting max_tokens — stopping the turn', {
|
|
1522
1525
|
[NAMZU.TURN_ID]: recorder.turnId,
|
|
@@ -1538,7 +1541,7 @@ export class IterationOrchestrator {
|
|
|
1538
1541
|
}
|
|
1539
1542
|
this.outputContinuations++
|
|
1540
1543
|
this.ctx.log.info(
|
|
1541
|
-
cutDuringReasoning
|
|
1544
|
+
cutDuringReasoning || boundedEmptyCut
|
|
1542
1545
|
? 'LLM hit max_tokens while reasoning — nudging to answer briefly'
|
|
1543
1546
|
: 'LLM hit max_tokens mid-text — auto-continuing',
|
|
1544
1547
|
{
|
|
@@ -1549,7 +1552,9 @@ export class IterationOrchestrator {
|
|
|
1549
1552
|
)
|
|
1550
1553
|
recorder.pushMessage(
|
|
1551
1554
|
createRuntimeContextMessage(
|
|
1552
|
-
cutDuringReasoning
|
|
1555
|
+
cutDuringReasoning || boundedEmptyCut
|
|
1556
|
+
? REASONING_CUT_USER_MESSAGE
|
|
1557
|
+
: AUTO_CONTINUATION_USER_MESSAGE,
|
|
1553
1558
|
'auto-continuation',
|
|
1554
1559
|
),
|
|
1555
1560
|
)
|
|
@@ -2176,6 +2181,7 @@ export class IterationOrchestrator {
|
|
|
2176
2181
|
}
|
|
2177
2182
|
// A rung that sheds too little to change the verdict is not worth a
|
|
2178
2183
|
// provider call; climb to the next one at once.
|
|
2184
|
+
const tokensBeforeRelief = estimateRefillTokens(recorder.messages)
|
|
2179
2185
|
let shed = false
|
|
2180
2186
|
while (!shed && overflowReliefs < OVERFLOW_RELIEF_LIMIT) {
|
|
2181
2187
|
overflowReliefs++
|
|
@@ -2185,7 +2191,13 @@ export class IterationOrchestrator {
|
|
|
2185
2191
|
// Clearing results is a compaction too: only summaries and reducers
|
|
2186
2192
|
// recorded themselves, so a clear-only relief was invisible to the
|
|
2187
2193
|
// breaker.
|
|
2188
|
-
|
|
2194
|
+
// Record the whole ladder once, including any insufficient first
|
|
2195
|
+
// rung, against the same estimator the refill check uses.
|
|
2196
|
+
compactionBreaker(this.ctx)?.recordCompaction(
|
|
2197
|
+
iterationNum,
|
|
2198
|
+
tokensBeforeRelief,
|
|
2199
|
+
estimateRefillTokens(recorder.messages),
|
|
2200
|
+
)
|
|
2189
2201
|
this.ctx.log.info('Retrying the turn after relieving a context overflow', {
|
|
2190
2202
|
[NAMZU.TURN_ID]: recorder.turnId,
|
|
2191
2203
|
[NAMZU.ITERATION]: iterationNum,
|
|
@@ -2993,6 +3005,34 @@ export class IterationOrchestrator {
|
|
|
2993
3005
|
iteration: this.ctx.recorder.currentIteration,
|
|
2994
3006
|
messageId: finalMessageId,
|
|
2995
3007
|
})
|
|
3008
|
+
// This call was collected, so it produced no live deltas. Publish
|
|
3009
|
+
// its actual text before completion as the normal stream does;
|
|
3010
|
+
// otherwise a streaming host can show the turn ending with no answer.
|
|
3011
|
+
const parts = response.message.textParts
|
|
3012
|
+
if (parts?.length) {
|
|
3013
|
+
for (const part of parts) {
|
|
3014
|
+
if (!part.text) continue
|
|
3015
|
+
await this.ctx.emitEvent({
|
|
3016
|
+
type: 'text_delta',
|
|
3017
|
+
turnId: this.ctx.recorder.turnId,
|
|
3018
|
+
iteration: this.ctx.recorder.currentIteration,
|
|
3019
|
+
messageId: finalMessageId,
|
|
3020
|
+
text: part.text,
|
|
3021
|
+
textPart: {
|
|
3022
|
+
id: part.id,
|
|
3023
|
+
...(part.phase ? { phase: part.phase } : {}),
|
|
3024
|
+
},
|
|
3025
|
+
})
|
|
3026
|
+
}
|
|
3027
|
+
} else if (response.message.content) {
|
|
3028
|
+
await this.ctx.emitEvent({
|
|
3029
|
+
type: 'text_delta',
|
|
3030
|
+
turnId: this.ctx.recorder.turnId,
|
|
3031
|
+
iteration: this.ctx.recorder.currentIteration,
|
|
3032
|
+
messageId: finalMessageId,
|
|
3033
|
+
text: response.message.content,
|
|
3034
|
+
})
|
|
3035
|
+
}
|
|
2996
3036
|
await this.ctx.emitEvent({
|
|
2997
3037
|
type: 'message_completed',
|
|
2998
3038
|
turnId: this.ctx.recorder.turnId,
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* and the turn carries on, silently as far as the person is concerned (a log
|
|
8
8
|
* line and a `breaker_open` event, no sentence); a pass the provider forces
|
|
9
9
|
* by rejecting the prompt is not gated, so the reactive ladder still runs.
|
|
10
|
-
* - Three compactions in a row, each followed by the
|
|
11
|
-
* within three iterations: something in the loop (a huge file
|
|
12
|
-
* that prints without limit) refills the window faster than
|
|
13
|
-
* empty it. The turn stops, because carrying on
|
|
10
|
+
* - Three compactions in a row, each followed by the history regaining all
|
|
11
|
+
* space shed within three iterations: something in the loop (a huge file
|
|
12
|
+
* read, a tool that prints without limit) refills the window faster than a
|
|
13
|
+
* summary can empty it. The turn stops, because carrying on buys the same wall.
|
|
14
14
|
*
|
|
15
15
|
* Off unless `compactionConfig.circuitBreaker` is true: stopping compaction,
|
|
16
16
|
* and above all stopping a turn, is not something a host had before.
|
|
@@ -19,11 +19,31 @@
|
|
|
19
19
|
* share a count.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { estimateMessageTokens } from '../../../../compaction/token-estimate.js'
|
|
23
|
+
import { type Message, isTurnContextEntry } from '../../../../types/message/index.js'
|
|
24
|
+
import { isReminderMessage } from '../../reminders.js'
|
|
22
25
|
import type { IterationContext } from './context.js'
|
|
23
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Conversation growth for the refill guard. Compaction sheds host reminders
|
|
29
|
+
* and recorded context so the host can announce them again; their return is
|
|
30
|
+
* not evidence of a tool refilling the window. Budgeting still includes them.
|
|
31
|
+
* Use provenance, not text that an operator or tool could happen to quote.
|
|
32
|
+
*/
|
|
33
|
+
export function estimateRefillTokens(messages: readonly Message[]): number {
|
|
34
|
+
return messages.reduce(
|
|
35
|
+
(total, message) =>
|
|
36
|
+
total +
|
|
37
|
+
(isReminderMessage(message) || isTurnContextEntry(message)
|
|
38
|
+
? 0
|
|
39
|
+
: estimateMessageTokens(message)),
|
|
40
|
+
0,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
/** Consecutive failed passes after which automatic compaction stops. */
|
|
25
45
|
export const COMPACTION_FAILURE_LIMIT = 3
|
|
26
|
-
/** A
|
|
46
|
+
/** A full refill sooner than this many iterations counts toward the limit. */
|
|
27
47
|
export const COMPACTION_REFILL_ITERATIONS = 3
|
|
28
48
|
/** Consecutive refills that stop the turn. */
|
|
29
49
|
export const COMPACTION_REFILL_LIMIT = 3
|
|
@@ -41,6 +61,8 @@ export class CompactionBreaker {
|
|
|
41
61
|
private refills = 0
|
|
42
62
|
private pendingRefills = 0
|
|
43
63
|
private lastCompactedIteration: number | undefined
|
|
64
|
+
private lastTokensBefore = 0
|
|
65
|
+
private lastTokensAfter = 0
|
|
44
66
|
private failureNoticeSent = false
|
|
45
67
|
private stopReason: string | undefined
|
|
46
68
|
|
|
@@ -69,18 +91,25 @@ export class CompactionBreaker {
|
|
|
69
91
|
* A summary or reducer pass is about to run at `iteration`. True when it
|
|
70
92
|
* would be the third refill in a row, in which case it must not run.
|
|
71
93
|
*/
|
|
72
|
-
wouldTrip(iteration: number): boolean {
|
|
94
|
+
wouldTrip(iteration: number, tokens: number): boolean {
|
|
95
|
+
// Iteration distance alone confuses continued pressure with a refill:
|
|
96
|
+
// a small reduction may leave the prompt above its trigger. Compare the
|
|
97
|
+
// same history estimator on both sides, never provider usage against an
|
|
98
|
+
// estimate. Count only when new context has replaced ALL the space shed.
|
|
99
|
+
const refilled = this.lastTokensAfter < this.lastTokensBefore && tokens >= this.lastTokensBefore
|
|
73
100
|
const quick =
|
|
74
101
|
this.lastCompactedIteration !== undefined &&
|
|
75
102
|
iteration - this.lastCompactedIteration < COMPACTION_REFILL_ITERATIONS
|
|
76
|
-
this.pendingRefills = quick ? this.refills + 1 : 0
|
|
103
|
+
this.pendingRefills = quick && refilled ? this.refills + 1 : 0
|
|
77
104
|
return this.pendingRefills >= COMPACTION_REFILL_LIMIT
|
|
78
105
|
}
|
|
79
106
|
|
|
80
|
-
/** The pass
|
|
81
|
-
recordCompaction(iteration: number): void {
|
|
107
|
+
/** The allowed pass completed; both sizes must use the same history estimator. */
|
|
108
|
+
recordCompaction(iteration: number, tokensBefore: number, tokensAfter: number): void {
|
|
82
109
|
this.refills = this.pendingRefills
|
|
83
110
|
this.lastCompactedIteration = iteration
|
|
111
|
+
this.lastTokensBefore = tokensBefore
|
|
112
|
+
this.lastTokensAfter = tokensAfter
|
|
84
113
|
this.recordRelief()
|
|
85
114
|
}
|
|
86
115
|
|
|
@@ -29,7 +29,11 @@ import { invariants } from '../../../../invariants/index.js'
|
|
|
29
29
|
import { resolveTaskModel } from '../../../../model-router/task-router.js'
|
|
30
30
|
import type { Message } from '../../../../types/message/index.js'
|
|
31
31
|
import { applyLifecycleHookResults } from '../../plugin-hooks.js'
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
COMPACTION_REFILL_MESSAGE,
|
|
34
|
+
compactionBreaker,
|
|
35
|
+
estimateRefillTokens,
|
|
36
|
+
} from './compaction-breaker.js'
|
|
33
37
|
import type { IterationContext } from './context.js'
|
|
34
38
|
import { isWorkingMemoryMessage } from './working-memory.js'
|
|
35
39
|
|
|
@@ -443,7 +447,10 @@ export async function stopOnRefill(ctx: IterationContext): Promise<boolean> {
|
|
|
443
447
|
if (!breaker) return false
|
|
444
448
|
// Already announced: the turn is on its way out; do not say it twice.
|
|
445
449
|
if (breaker.stop !== undefined) return true
|
|
446
|
-
if (
|
|
450
|
+
if (
|
|
451
|
+
!breaker.wouldTrip(ctx.recorder.currentIteration, estimateRefillTokens(ctx.recorder.messages))
|
|
452
|
+
)
|
|
453
|
+
return false
|
|
447
454
|
ctx.log.warn('Compaction keeps refilling — stopping the turn', {
|
|
448
455
|
[NAMZU.TURN_ID]: ctx.recorder.turnId,
|
|
449
456
|
[NAMZU.ITERATION]: ctx.recorder.currentIteration,
|
|
@@ -509,6 +516,7 @@ async function applyReducer(
|
|
|
509
516
|
const messages = ctx.recorder.messages
|
|
510
517
|
const before = messages.length
|
|
511
518
|
const beforeChars = totalChars(messages)
|
|
519
|
+
const beforeTokens = estimateRefillTokens(messages)
|
|
512
520
|
|
|
513
521
|
let next: readonly Message[] | undefined
|
|
514
522
|
try {
|
|
@@ -595,7 +603,11 @@ async function applyReducer(
|
|
|
595
603
|
reduction.contextWindowTokens,
|
|
596
604
|
measurement.windowSource,
|
|
597
605
|
)
|
|
598
|
-
compactionBreaker(ctx)?.recordCompaction(
|
|
606
|
+
compactionBreaker(ctx)?.recordCompaction(
|
|
607
|
+
ctx.recorder.currentIteration,
|
|
608
|
+
beforeTokens,
|
|
609
|
+
estimateRefillTokens(messages),
|
|
610
|
+
)
|
|
599
611
|
}
|
|
600
612
|
|
|
601
613
|
/**
|
|
@@ -716,7 +728,12 @@ async function runCompactionCheckInner(
|
|
|
716
728
|
// well before the summary trigger; every other strategy waits for it.
|
|
717
729
|
const salience = config.strategy === 'salience'
|
|
718
730
|
const maxOutputTokens = ctx.providerCapabilities?.maxOutputTokens
|
|
719
|
-
const trigger = resolveTriggerThreshold(
|
|
731
|
+
const trigger = resolveTriggerThreshold(
|
|
732
|
+
config.triggerThreshold,
|
|
733
|
+
budget,
|
|
734
|
+
maxOutputTokens,
|
|
735
|
+
config.autoCompactWindowTokens,
|
|
736
|
+
)
|
|
720
737
|
const startAt = salience ? Math.min(config.softTarget ?? DEFAULT_SOFT_TARGET, trigger) : trigger
|
|
721
738
|
if (!options?.force && usage < startAt) return
|
|
722
739
|
|
|
@@ -1122,6 +1139,7 @@ async function runCompactionCheckInner(
|
|
|
1122
1139
|
|
|
1123
1140
|
const live = ctx.recorder.messages
|
|
1124
1141
|
const oldCount = live.length
|
|
1142
|
+
const beforeTokens = estimateRefillTokens(live)
|
|
1125
1143
|
// Retained history can survive in full while a summary adds another copy
|
|
1126
1144
|
// of its facts. Even an unretained span may be smaller than the cumulative
|
|
1127
1145
|
// summary. Compare both candidates with the same estimator, rather than
|
|
@@ -1196,5 +1214,9 @@ async function runCompactionCheckInner(
|
|
|
1196
1214
|
reachedResetThreshold: reachedReset,
|
|
1197
1215
|
})
|
|
1198
1216
|
await emitContextUsageSnapshot(ctx, newEstimate, budget, window.source)
|
|
1199
|
-
compactionBreaker(ctx)?.recordCompaction(
|
|
1217
|
+
compactionBreaker(ctx)?.recordCompaction(
|
|
1218
|
+
ctx.recorder.currentIteration,
|
|
1219
|
+
beforeTokens,
|
|
1220
|
+
estimateRefillTokens(live),
|
|
1221
|
+
)
|
|
1200
1222
|
}
|
|
@@ -284,6 +284,8 @@ export interface IterationContext {
|
|
|
284
284
|
/** The delegating session, when this turn is a child's: `subagent_stop` names it. */
|
|
285
285
|
readonly parentSessionId?: import('../../../../types/ids/index.js').SessionId
|
|
286
286
|
readonly parentTurnId?: import('../../../../types/ids/index.js').TurnId
|
|
287
|
+
/** The agent this turn runs, named to `subagent_stop` hooks of a child. */
|
|
288
|
+
readonly agentType?: string
|
|
287
289
|
|
|
288
290
|
/**
|
|
289
291
|
* Override for {@link PARK_RECORD_DELAY_MS}. Internal; tests set `0` to
|
|
@@ -16,7 +16,9 @@ import type {
|
|
|
16
16
|
ReviewedExecution,
|
|
17
17
|
ToolCallDeclines,
|
|
18
18
|
ToolCallDenials,
|
|
19
|
+
ToolCallOutcome,
|
|
19
20
|
} from '../../executor.js'
|
|
21
|
+
import { applyLifecycleHookResults } from '../../plugin-hooks.js'
|
|
20
22
|
import {
|
|
21
23
|
awaitProjectInstructionCallback,
|
|
22
24
|
replaceProjectInstructionSnapshot,
|
|
@@ -212,11 +214,81 @@ export async function* runToolReview(
|
|
|
212
214
|
/** Set only where a person's No is known, so a policy refusal never lands in it. */
|
|
213
215
|
let declines: ToolCallDeclines | undefined
|
|
214
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Tell `permission_denied` hooks about each call refused without a person's
|
|
219
|
+
* No: a rule, the mode's policy, a classifier or a hook. Observational, so
|
|
220
|
+
* a hook's answer changes nothing; one that throws is logged, not allowed to
|
|
221
|
+
* fail the step.
|
|
222
|
+
*/
|
|
223
|
+
const announceRefusals = async (denials?: ToolCallDenials): Promise<void> => {
|
|
224
|
+
if (!ctx.pluginManager || !denials || denials.size === 0) return
|
|
225
|
+
for (const summary of toolCallSummaries) {
|
|
226
|
+
const reason = denials.get(summary.id)
|
|
227
|
+
if (reason === undefined || declines?.has(summary.id)) continue
|
|
228
|
+
if (ctx.abortController.signal.aborted) return
|
|
229
|
+
try {
|
|
230
|
+
await ctx.pluginManager.executeHooks(
|
|
231
|
+
'permission_denied',
|
|
232
|
+
{
|
|
233
|
+
sessionId: ctx.recorder.sessionId,
|
|
234
|
+
turnId: ctx.recorder.turnId,
|
|
235
|
+
toolName: summary.name,
|
|
236
|
+
toolInput: summary.input,
|
|
237
|
+
reason,
|
|
238
|
+
signal: ctx.abortController.signal,
|
|
239
|
+
},
|
|
240
|
+
ctx.emitEvent,
|
|
241
|
+
)
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (ctx.abortController.signal.aborted) return
|
|
244
|
+
ctx.log.warn('A permission_denied hook failed', {
|
|
245
|
+
'exception.message': error instanceof Error ? error.message : String(error),
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Tell `post_tool_batch` hooks that every call of this step has settled.
|
|
253
|
+
* Fires once per step, before the next model request. A hook may ask for the
|
|
254
|
+
* turn to stop once this step is done; an `error` fails the turn like any
|
|
255
|
+
* other lifecycle hook.
|
|
256
|
+
*/
|
|
257
|
+
const announceBatch = async (outcomes: readonly ToolCallOutcome[]): Promise<void> => {
|
|
258
|
+
if (!ctx.pluginManager || ctx.abortController.signal.aborted) return
|
|
259
|
+
const inputs = new Map(toolCallSummaries.map((summary) => [summary.id, summary.input]))
|
|
260
|
+
const toolCalls = outcomes.map((outcome) => ({
|
|
261
|
+
toolCallId: outcome.toolCallId,
|
|
262
|
+
toolName: outcome.toolName,
|
|
263
|
+
toolInput: inputs.get(outcome.toolCallId),
|
|
264
|
+
output: outcome.output,
|
|
265
|
+
isError: outcome.isError === true,
|
|
266
|
+
}))
|
|
267
|
+
if (toolCalls.length === 0) return
|
|
268
|
+
const results = await ctx.pluginManager.executeHooks(
|
|
269
|
+
'post_tool_batch',
|
|
270
|
+
{
|
|
271
|
+
sessionId: ctx.recorder.sessionId,
|
|
272
|
+
turnId: ctx.recorder.turnId,
|
|
273
|
+
toolCalls,
|
|
274
|
+
signal: ctx.abortController.signal,
|
|
275
|
+
},
|
|
276
|
+
ctx.emitEvent,
|
|
277
|
+
)
|
|
278
|
+
applyLifecycleHookResults('post_tool_batch', results)
|
|
279
|
+
for (const result of results) {
|
|
280
|
+
if (result.action === 'continue' && result.halt) {
|
|
281
|
+
ctx.toolExecutor.requestHalt?.(result.halt.reason)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
215
286
|
/** Executes the batch, answering every call, and appends the results. */
|
|
216
287
|
const settleBatch = async (
|
|
217
288
|
denials?: ToolCallDenials,
|
|
218
289
|
review?: ReviewedExecution,
|
|
219
290
|
): Promise<void> => {
|
|
291
|
+
await announceRefusals(denials)
|
|
220
292
|
const startedAt = Date.now()
|
|
221
293
|
const batch = await ctx.toolExecutor.executeBatch(
|
|
222
294
|
response,
|
|
@@ -228,6 +300,7 @@ export async function* runToolReview(
|
|
|
228
300
|
)
|
|
229
301
|
toolMs += Date.now() - startedAt
|
|
230
302
|
executed = batch.results
|
|
303
|
+
await announceBatch(batch.results)
|
|
231
304
|
// Registry receipts are ordered by actual completion, include nested
|
|
232
305
|
// dispatches, and exclude denials/recovered results. A successful possible
|
|
233
306
|
// mutation permits another experiment; unrelated reads cannot do so.
|
|
@@ -533,9 +606,7 @@ export async function* runToolReview(
|
|
|
533
606
|
toolCall.authorization = {
|
|
534
607
|
decision: gateResult.decision,
|
|
535
608
|
...(gateResult.reason ? { reason: gateResult.reason } : {}),
|
|
536
|
-
...(
|
|
537
|
-
? { explicitReview: true as const }
|
|
538
|
-
: {}),
|
|
609
|
+
...(ruleAsked || hookAsked ? { explicitReview: true as const } : {}),
|
|
539
610
|
...(ruleAsked || hookAsked ? { askRule: true as const } : {}),
|
|
540
611
|
...(gateResult.decision === 'allow' && allowRuleScope(gateResult.matchedRule)
|
|
541
612
|
? { allowScope: allowRuleScope(gateResult.matchedRule) }
|