@namzu/cli 17.0.1 → 18.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/README.md +22 -12
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +1 -0
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/run-stream.d.ts.map +1 -1
- package/dist/commands/run-stream.js +1 -0
- package/dist/commands/run-stream.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +15 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +17 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/doctrine.d.ts +44 -0
- package/dist/context/doctrine.d.ts.map +1 -0
- package/dist/context/doctrine.js +81 -0
- package/dist/context/doctrine.js.map +1 -0
- package/dist/context/turn-snapshot.d.ts +59 -0
- package/dist/context/turn-snapshot.d.ts.map +1 -0
- package/dist/context/turn-snapshot.js +122 -0
- package/dist/context/turn-snapshot.js.map +1 -0
- package/dist/integrations/subagents/activity.d.ts +8 -0
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +38 -2
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/policy.d.ts +6 -0
- package/dist/integrations/subagents/policy.d.ts.map +1 -0
- package/dist/integrations/subagents/policy.js +6 -0
- package/dist/integrations/subagents/policy.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +13 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +77 -13
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/permissions/mode.d.ts +25 -0
- package/dist/permissions/mode.d.ts.map +1 -1
- package/dist/permissions/mode.js +11 -1
- package/dist/permissions/mode.js.map +1 -1
- package/dist/tui/AgentExplorer.d.ts +21 -0
- package/dist/tui/AgentExplorer.d.ts.map +1 -1
- package/dist/tui/AgentExplorer.js +52 -1
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +298 -26
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Composer.d.ts +8 -1
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +23 -3
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +16 -1
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/TaskList.d.ts +30 -0
- package/dist/tui/TaskList.d.ts.map +1 -0
- package/dist/tui/TaskList.js +58 -0
- package/dist/tui/TaskList.js.map +1 -0
- package/dist/tui/agent.d.ts +49 -4
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +171 -20
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +151 -1
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +2 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +6 -3
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/stream-blocks.d.ts +24 -0
- package/dist/tui/stream-blocks.d.ts.map +1 -1
- package/dist/tui/stream-blocks.js +74 -0
- package/dist/tui/stream-blocks.js.map +1 -1
- package/dist/tui/types.d.ts +3 -1
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/tui/agent.js
CHANGED
|
@@ -20,21 +20,25 @@
|
|
|
20
20
|
* `emptySession()` whose `send()` yields a single error event so the UI
|
|
21
21
|
* renders an actionable hint rather than crashing.
|
|
22
22
|
*/
|
|
23
|
-
import { BOOT_EVENT_NAMES, DefaultPathBuilder, DiskMemoryStore, DiskTaskStore, EVENT_NAME_ATTRIBUTE, ProviderRegistry, SESSION_GOAL_TOOL_NAMES, SearchToolsTool, ToolRegistry, asProjectId, asRunId, asSessionId, asTenantId, asTopicId, buildMemoryTools, buildSessionGoalTools, compactNow, createComputerUseTool, createMemoryPromoter, createToolPresenter, generateRunId, getBuiltinTools, isTrustedReadOnly, query, resumeRun, withProviderFallback, } from '@namzu/sdk';
|
|
23
|
+
import { BOOT_EVENT_NAMES, DefaultPathBuilder, DiskMemoryStore, DiskTaskStore, EVENT_NAME_ATTRIBUTE, GuardedFetchProvider, PromptContributionRegistry, ProviderRegistry, SESSION_GOAL_TOOL_NAMES, SearchToolsTool, ToolRegistry, WebFetchTool, asProjectId, asRunId, asSessionId, asTenantId, asTopicId, buildCoordinatorTools, buildMemoryTools, buildSessionGoalTools, compactNow, createComputerUseTool, createMemoryPromoter, createToolPresenter, generateRunId, getBuiltinTools, isTrustedReadOnly, query, resumeRun, webGuidanceContribution, withProviderFallback, } from '@namzu/sdk';
|
|
24
24
|
import { SubprocessComputerUseHost } from '@namzu/computer-use';
|
|
25
25
|
import { realpath, stat } from 'node:fs/promises';
|
|
26
26
|
import { join, parse, resolve } from 'node:path';
|
|
27
27
|
import { probeCapabilities } from '../context/capabilities.js';
|
|
28
|
+
import { NAMZU_DELEGATION_DOCTRINE, NAMZU_PLAN_MODE_DOCTRINE, NAMZU_WORKING_DOCTRINE, } from '../context/doctrine.js';
|
|
28
29
|
import { composeEnvironmentPrompt, readEnvironmentFacts } from '../context/environment.js';
|
|
29
30
|
import { ProjectInstructionTracker } from '../context/project-tracker.js';
|
|
30
31
|
import { resolveSandbox, sandboxResolvedSeverity, } from '../context/sandbox.js';
|
|
32
|
+
import { composeTurnSnapshot, readTurnSnapshot } from '../context/turn-snapshot.js';
|
|
31
33
|
import { connectMcpServers, } from '../integrations/mcp/servers.js';
|
|
32
34
|
import { createCliPluginRuntime } from '../integrations/plugins/runtime.js';
|
|
33
35
|
import { CredentialRefreshRejectedError, CredentialWithdrawnError, PROVIDER_REGISTRY, chainCapabilityDisagreements, chainPositionName, describeAcceptedMismatch, describeCapabilityRefusal, discoverProviders, ensureFreshAnthropicToken, ensureFreshStoredCodexCredential, ensureRegistered, findDetected, isAnthropicOAuthToken, isRegistered, missingCredentialMessage, primaryProvider, readCodexCredentialFile, readPreferences, readSubscriptionCredential, resolveChainCapabilities, sameOAuthCredential, unresolvedMembers, unsupportedProviderMessage, } from '../integrations/providers/index.js';
|
|
34
36
|
import { ensurePrivateStateDirectory } from '../integrations/state/private-directory.js';
|
|
37
|
+
import { CLI_INTERACTIVE_RUN_TIMEOUT_MS } from '../integrations/subagents/policy.js';
|
|
35
38
|
import { createSubagentRuntime } from '../integrations/subagents/runtime.js';
|
|
36
39
|
import { cliLogger } from '../logging.js';
|
|
37
40
|
import { composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
41
|
+
import { ACCEPT_EDITS_TOOLS, PLAN_MODE_REFUSAL } from '../permissions/mode.js';
|
|
38
42
|
import { projectRunConversation } from './conversation-history.js';
|
|
39
43
|
/**
|
|
40
44
|
* Let one caller stop waiting without cutting a shared queue in the middle.
|
|
@@ -788,6 +792,15 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
788
792
|
// This session passes a `taskStore` to query() below, which registers the
|
|
789
793
|
// task tools deferred — so `search_tools` has something to find here.
|
|
790
794
|
registry.register([SearchToolsTool]);
|
|
795
|
+
// Web reach, opted into in the config file and nowhere else. The parent's
|
|
796
|
+
// registry only: a child's config carries no provider, and a tool whose
|
|
797
|
+
// provider is missing is a tool that reports itself unwired — truthful,
|
|
798
|
+
// and noise. The guarded provider refuses private and loopback addresses
|
|
799
|
+
// and bounds redirects and body; every fetch is reviewed like a shell
|
|
800
|
+
// command (see `isPromptExempt`).
|
|
801
|
+
const webCapability = options.web?.fetch ? { fetch: new GuardedFetchProvider() } : undefined;
|
|
802
|
+
if (webCapability)
|
|
803
|
+
registry.register(WebFetchTool);
|
|
791
804
|
// Native sub-agents: register the canonical `Agent` tool so the model can
|
|
792
805
|
// delegate a self-contained task to a fresh sub-agent (own context window).
|
|
793
806
|
// Best-effort — if the runtime can't stand up, the chat still works.
|
|
@@ -863,14 +876,62 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
863
876
|
// and got none had nothing on stderr to say why.
|
|
864
877
|
cliLogger().warn('sub-agent runtime unavailable this session', exceptionAttributes(err));
|
|
865
878
|
}
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
// a
|
|
879
|
+
// `ask_user_question`, where somebody can answer. The SDK tool parks the
|
|
880
|
+
// run through the handler it was BUILT with, so that handler reads the
|
|
881
|
+
// turn's answerer through a holder the prelude fills: the tool is per
|
|
882
|
+
// session, the person answering is per turn. Needs the delegation
|
|
883
|
+
// gateway the tool builder requires; a session without one has no
|
|
884
|
+
// question tool either, and says nothing — it also has no `Agent`.
|
|
885
|
+
let currentOnQuestion;
|
|
886
|
+
if (options.askUser && subagentGateway) {
|
|
887
|
+
const parkQuestion = async (request) => {
|
|
888
|
+
if (request.type !== 'user_question')
|
|
889
|
+
return { action: 'continue' };
|
|
890
|
+
const ask = currentOnQuestion;
|
|
891
|
+
if (!ask)
|
|
892
|
+
return { action: 'continue' };
|
|
893
|
+
const answer = await ask(request.question);
|
|
894
|
+
switch (answer.kind) {
|
|
895
|
+
case 'answer':
|
|
896
|
+
return {
|
|
897
|
+
action: 'answer_question',
|
|
898
|
+
selectedOptionIds: [...answer.selectedOptionIds],
|
|
899
|
+
...(answer.freeText !== undefined ? { freeText: answer.freeText } : {}),
|
|
900
|
+
questionId: request.question.questionId,
|
|
901
|
+
};
|
|
902
|
+
case 'abort':
|
|
903
|
+
return { action: 'abort', reason: 'The user declined to answer.' };
|
|
904
|
+
default:
|
|
905
|
+
return { action: 'continue' };
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
const askTool = buildCoordinatorTools({
|
|
909
|
+
gateway: subagentGateway,
|
|
910
|
+
workingDirectory: cwd,
|
|
911
|
+
allowedAgentIds: [],
|
|
912
|
+
allowDelegation: false,
|
|
913
|
+
resumeHandler: parkQuestion,
|
|
914
|
+
// The builder stamps this on the park request. The handler above
|
|
915
|
+
// routes by the question, not by the run, and no durable park
|
|
916
|
+
// recorder is supplied, so a session-scoped id is what is true: the
|
|
917
|
+
// tool is built once per session and the turn is not known yet.
|
|
918
|
+
runId: asRunId('run_namzu-interactive-question'),
|
|
919
|
+
}).find((tool) => tool.name === 'ask_user_question');
|
|
920
|
+
if (askTool)
|
|
921
|
+
registry.register(askTool);
|
|
922
|
+
}
|
|
923
|
+
// Task store → query registers task_create / task_update / task_list and
|
|
924
|
+
// emits task_created/task_updated, so the agent can track a plan for the
|
|
925
|
+
// current request. Tasks are run-scoped. The kernel's default availability
|
|
926
|
+
// for them is `deferred`; this session overrides that to `active` at the
|
|
927
|
+
// query call, because the doctrine tells the model to plan with them and a
|
|
928
|
+
// tool it must search for first is a tool it skips.
|
|
869
929
|
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
//
|
|
873
|
-
//
|
|
930
|
+
// `search_tools` stays mounted above even so: a tool server or plugin can
|
|
931
|
+
// still register a deferred roster, and that is what the search is for. The
|
|
932
|
+
// task tools are registered inside query(), after this function returns,
|
|
933
|
+
// which is why the connect line reports no count of them — counting here
|
|
934
|
+
// would mean restating query's registration order in the CLI.
|
|
874
935
|
//
|
|
875
936
|
// It is also why `toolNames` below reads the registry rather than a list
|
|
876
937
|
// captured on this line. The count at connect time is unchanged; what
|
|
@@ -1061,8 +1122,44 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1061
1122
|
? await currentPluginSkills(pluginRuntime.skills)
|
|
1062
1123
|
: undefined;
|
|
1063
1124
|
const memoryPrompt = composeMemoryPrompt(readMemory());
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1125
|
+
currentOnQuestion = opts?.onQuestion;
|
|
1126
|
+
const [environmentFacts, turnSnapshot] = await Promise.all([
|
|
1127
|
+
readEnvironmentFacts(cwd),
|
|
1128
|
+
readTurnSnapshot(cwd),
|
|
1129
|
+
]);
|
|
1130
|
+
const environmentPrompt = composeEnvironmentPrompt(environmentFacts);
|
|
1131
|
+
// The repository as it stood when THIS turn began, through the
|
|
1132
|
+
// SDK's `turn` placement — the ephemeral trailing message that is
|
|
1133
|
+
// never cached and never enters history. FIRST iteration only:
|
|
1134
|
+
// later iterations work from state the model itself changed, and
|
|
1135
|
+
// `git status` is the honest source for that. A registry per
|
|
1136
|
+
// turn, closed over this turn's snapshot, rather than one
|
|
1137
|
+
// session-scoped holder every send overwrites: two overlapping
|
|
1138
|
+
// sends would otherwise both render whichever ran second.
|
|
1139
|
+
const turnSnapshotPrompt = turnSnapshot ? composeTurnSnapshot(turnSnapshot) : null;
|
|
1140
|
+
const promptContributions = new PromptContributionRegistry();
|
|
1141
|
+
promptContributions.register({
|
|
1142
|
+
id: 'namzu.turn-snapshot',
|
|
1143
|
+
placement: 'turn',
|
|
1144
|
+
render: ({ iteration }) => (iteration === 1 ? turnSnapshotPrompt : null),
|
|
1145
|
+
});
|
|
1146
|
+
// The citation rules that come with the web tools, only when the
|
|
1147
|
+
// tools are there: guidance about a capability the turn does not
|
|
1148
|
+
// have reads as a capability it should be looking for.
|
|
1149
|
+
if (webCapability)
|
|
1150
|
+
promptContributions.register(webGuidanceContribution);
|
|
1151
|
+
const systemPrompt = [
|
|
1152
|
+
NAMZU_IDENTITY,
|
|
1153
|
+
NAMZU_WORKING_DOCTRINE,
|
|
1154
|
+
NAMZU_DELEGATION_DOCTRINE,
|
|
1155
|
+
// Present only while the turn runs under `plan`. A mode change
|
|
1156
|
+
// is rare, so the cached prefix it re-keys is a price paid once
|
|
1157
|
+
// per switch rather than once per turn.
|
|
1158
|
+
opts?.permissionMode === 'plan' ? NAMZU_PLAN_MODE_DOCTRINE : undefined,
|
|
1159
|
+
environmentPrompt,
|
|
1160
|
+
memoryPrompt,
|
|
1161
|
+
opts?.extraSystem,
|
|
1162
|
+
]
|
|
1066
1163
|
.filter((s) => Boolean(s))
|
|
1067
1164
|
.join('\n\n') || undefined;
|
|
1068
1165
|
let capturedAuthority;
|
|
@@ -1122,6 +1219,16 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1122
1219
|
opts: turnOpts,
|
|
1123
1220
|
resumeHandler,
|
|
1124
1221
|
taskGateway: subagentGateway,
|
|
1222
|
+
promptContributions,
|
|
1223
|
+
...(webCapability ? { web: webCapability } : {}),
|
|
1224
|
+
// Active, not deferred: the doctrine tells the model to open a
|
|
1225
|
+
// task list for multi-step work, and a tool it has to search
|
|
1226
|
+
// for first is a tool it will skip.
|
|
1227
|
+
runtimeToolOverrides: {
|
|
1228
|
+
task_create: 'active',
|
|
1229
|
+
task_update: 'active',
|
|
1230
|
+
task_list: 'active',
|
|
1231
|
+
},
|
|
1125
1232
|
onRunEvent: options.onRunEvent,
|
|
1126
1233
|
...(sandbox.provider ? { sandboxProvider: sandbox.provider } : {}),
|
|
1127
1234
|
...(options.sandbox?.teardownTimeoutMs !== undefined
|
|
@@ -1156,7 +1263,13 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1156
1263
|
: undefined;
|
|
1157
1264
|
const memoryPrompt = composeMemoryPrompt(readMemory());
|
|
1158
1265
|
const environmentPrompt = composeEnvironmentPrompt(await readEnvironmentFacts(cwd));
|
|
1159
|
-
const systemPrompt = [
|
|
1266
|
+
const systemPrompt = [
|
|
1267
|
+
NAMZU_IDENTITY,
|
|
1268
|
+
NAMZU_WORKING_DOCTRINE,
|
|
1269
|
+
NAMZU_DELEGATION_DOCTRINE,
|
|
1270
|
+
environmentPrompt,
|
|
1271
|
+
memoryPrompt,
|
|
1272
|
+
]
|
|
1160
1273
|
.filter((s) => Boolean(s))
|
|
1161
1274
|
.join('\n\n') || undefined;
|
|
1162
1275
|
const resumeHandler = makeResumeHandler(approval, undefined, options.permissionMode, (name, input) => isPromptExempt(registry, name, input));
|
|
@@ -1173,6 +1286,15 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1173
1286
|
skillRegistry: pluginRuntime?.skills,
|
|
1174
1287
|
skills: pluginSkills,
|
|
1175
1288
|
taskStore,
|
|
1289
|
+
// The same availability the original run registered under.
|
|
1290
|
+
// A resumed run re-registers the task tools; leaving them at
|
|
1291
|
+
// the kernel's `deferred` default would hand the model a plan
|
|
1292
|
+
// it started with active tools and can no longer update.
|
|
1293
|
+
runtimeToolOverrides: {
|
|
1294
|
+
task_create: 'active',
|
|
1295
|
+
task_update: 'active',
|
|
1296
|
+
task_list: 'active',
|
|
1297
|
+
},
|
|
1176
1298
|
...(subagentGateway ? { taskGateway: subagentGateway } : {}),
|
|
1177
1299
|
authorizationGate: gateFor(options.rules),
|
|
1178
1300
|
compactionConfig: COMPACTION_CONFIG,
|
|
@@ -1190,7 +1312,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1190
1312
|
runConfig: {
|
|
1191
1313
|
model,
|
|
1192
1314
|
...(sandbox.provider ? { sandbox: { workspace: sandboxWorkspace } } : {}),
|
|
1193
|
-
timeoutMs:
|
|
1315
|
+
timeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS,
|
|
1194
1316
|
tokenBudget: 1_000_000,
|
|
1195
1317
|
maxIterations: 50,
|
|
1196
1318
|
maxResponseTokens: 8192,
|
|
@@ -1587,7 +1709,7 @@ const COMPACTION_CONFIG = {
|
|
|
1587
1709
|
maxCharsPerRequirement: 300,
|
|
1588
1710
|
maxCharsPerTask: 400,
|
|
1589
1711
|
};
|
|
1590
|
-
async function* runTurn({ provider, fallbackProviders, model, tools, pluginManager, skillRegistry, skills, scope, pathBuilder, workingDirectory, sandboxWorkspace, rules, reviewAnswer, maxAnswerReviews, promoteMemory, resumeHandler, taskStore, systemPrompt, messages, projectInstructionContext, opts, taskGateway, sandboxProvider, sandboxTeardownTimeoutMs, onRunEvent, }) {
|
|
1712
|
+
async function* runTurn({ provider, fallbackProviders, model, tools, pluginManager, skillRegistry, skills, scope, pathBuilder, workingDirectory, sandboxWorkspace, rules, reviewAnswer, maxAnswerReviews, promoteMemory, resumeHandler, taskStore, systemPrompt, messages, projectInstructionContext, opts, taskGateway, promptContributions, runtimeToolOverrides, web, sandboxProvider, sandboxTeardownTimeoutMs, onRunEvent, }) {
|
|
1591
1713
|
const signal = opts?.signal;
|
|
1592
1714
|
// One presenter for the whole stream, built from the registry this scope
|
|
1593
1715
|
// already holds. Its absence HERE is what forced presentation to be name
|
|
@@ -1629,7 +1751,7 @@ async function* runTurn({ provider, fallbackProviders, model, tools, pluginManag
|
|
|
1629
1751
|
model,
|
|
1630
1752
|
...(sandboxProvider ? { sandbox: { workspace: sandboxWorkspace } } : {}),
|
|
1631
1753
|
...(opts?.effort !== undefined ? { effort: opts.effort } : {}),
|
|
1632
|
-
timeoutMs:
|
|
1754
|
+
timeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS,
|
|
1633
1755
|
tokenBudget: 1_000_000,
|
|
1634
1756
|
maxIterations: 50,
|
|
1635
1757
|
maxResponseTokens: 8192,
|
|
@@ -1656,6 +1778,9 @@ async function* runTurn({ provider, fallbackProviders, model, tools, pluginManag
|
|
|
1656
1778
|
// tools `query()` registers deferred below and any tool server that
|
|
1657
1779
|
// connected after this session was built.
|
|
1658
1780
|
resumeHandler,
|
|
1781
|
+
...(promptContributions ? { promptContributions } : {}),
|
|
1782
|
+
...(runtimeToolOverrides ? { runtimeToolOverrides } : {}),
|
|
1783
|
+
...(web ? { web } : {}),
|
|
1659
1784
|
signal,
|
|
1660
1785
|
...scope,
|
|
1661
1786
|
});
|
|
@@ -1730,6 +1855,21 @@ exempt = () => false) {
|
|
|
1730
1855
|
if (!batchNeedsPrompt(request.toolCalls, exempt)) {
|
|
1731
1856
|
return { action: 'approve_tools' };
|
|
1732
1857
|
}
|
|
1858
|
+
// A batch of nothing but non-destructive file edits is the case this
|
|
1859
|
+
// mode exists for. One bash call in the same batch and the whole batch
|
|
1860
|
+
// asks — the operator reviews the batch as a unit, and a prompt that
|
|
1861
|
+
// showed only the shell command while the edits went through beside it
|
|
1862
|
+
// would be approving something it did not show.
|
|
1863
|
+
if (mode === 'accept-edits' &&
|
|
1864
|
+
request.toolCalls.every((tc) => !tc.isDestructive && (ACCEPT_EDITS_TOOLS.has(tc.name) || exempt(tc.name, tc.input)))) {
|
|
1865
|
+
return { action: 'approve_tools' };
|
|
1866
|
+
}
|
|
1867
|
+
// Reads were already approved above (they are exempt). Anything that
|
|
1868
|
+
// reached here would change something, and plan mode's answer to that
|
|
1869
|
+
// is the same every time: not now, tell the user what you would do.
|
|
1870
|
+
if (mode === 'plan') {
|
|
1871
|
+
return { action: 'reject_tools', feedback: PLAN_MODE_REFUSAL };
|
|
1872
|
+
}
|
|
1733
1873
|
if (mode === 'strict') {
|
|
1734
1874
|
return {
|
|
1735
1875
|
action: 'reject_tools',
|
|
@@ -1816,6 +1956,11 @@ export function isPromptExempt(registry, name, input) {
|
|
|
1816
1956
|
if (PROMPT_EXEMPT_WRITES.has(name.toLowerCase()))
|
|
1817
1957
|
return true;
|
|
1818
1958
|
const tool = registry.get(name) ?? registry.get(name.toLowerCase());
|
|
1959
|
+
// A fetch changes nothing here and declares itself read-only, and it is
|
|
1960
|
+
// still a request leaving the machine to an address the model chose. The
|
|
1961
|
+
// operator sees the URL before it goes, the way they see a shell command.
|
|
1962
|
+
if (tool?.category === 'network')
|
|
1963
|
+
return false;
|
|
1819
1964
|
// A connected server's own claim about its own tool cannot skip the
|
|
1820
1965
|
// prompt. Same predicate the kernel gate and plan mode use -- three
|
|
1821
1966
|
// doors, one rule, because fixing two would close the issue and leave
|
|
@@ -1854,6 +1999,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
1854
1999
|
case 'tool_executing':
|
|
1855
2000
|
return {
|
|
1856
2001
|
kind: 'tool-start',
|
|
2002
|
+
runId: event.runId,
|
|
1857
2003
|
toolUseId: event.toolUseId,
|
|
1858
2004
|
toolName: event.toolName,
|
|
1859
2005
|
...(() => {
|
|
@@ -1870,6 +2016,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
1870
2016
|
case 'tool_progress':
|
|
1871
2017
|
return {
|
|
1872
2018
|
kind: 'tool-progress',
|
|
2019
|
+
runId: event.runId,
|
|
1873
2020
|
toolUseId: event.toolUseId,
|
|
1874
2021
|
toolName: event.toolName,
|
|
1875
2022
|
message: event.message,
|
|
@@ -1885,6 +2032,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
1885
2032
|
const withoutRepeatedSummary = view.kind === 'terminal' && detail?.[0] === summary ? detail.slice(1) : detail;
|
|
1886
2033
|
return {
|
|
1887
2034
|
kind: 'tool-end',
|
|
2035
|
+
runId: event.runId,
|
|
1888
2036
|
toolUseId: event.toolUseId,
|
|
1889
2037
|
toolName: event.toolName,
|
|
1890
2038
|
isError: event.isError,
|
|
@@ -1959,13 +2107,16 @@ export function toAgentEvent(event, presenter) {
|
|
|
1959
2107
|
};
|
|
1960
2108
|
}
|
|
1961
2109
|
case 'task_created':
|
|
1962
|
-
return { kind: 'task', subject: event.subject, status: event.status };
|
|
1963
2110
|
case 'task_updated':
|
|
1964
|
-
//
|
|
1965
|
-
//
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
:
|
|
2111
|
+
// Every change, not only completions: the live task list needs the
|
|
2112
|
+
// in-progress flips to show which step is current. The transcript
|
|
2113
|
+
// decides for itself which of these it records.
|
|
2114
|
+
return {
|
|
2115
|
+
kind: 'task',
|
|
2116
|
+
taskId: String(event.taskId),
|
|
2117
|
+
subject: event.subject,
|
|
2118
|
+
status: event.status,
|
|
2119
|
+
};
|
|
1969
2120
|
case 'run_paused':
|
|
1970
2121
|
// A pause is not an error and not an invisible end. The checkpoint and
|
|
1971
2122
|
// classification are the recovery surface; dropping this event made a
|