@namzu/cli 19.0.0 → 21.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 +308 -0
- package/README.md +42 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +6 -1
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/run-stream.d.ts.map +1 -1
- package/dist/commands/run-stream.js +9 -1
- package/dist/commands/run-stream.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +14 -11
- package/dist/commands/run.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +21 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +11 -4
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/doctor/checks/chain.d.ts.map +1 -1
- package/dist/doctor/checks/chain.js +11 -5
- package/dist/doctor/checks/chain.js.map +1 -1
- package/dist/doctor/checks/credentials.d.ts.map +1 -1
- package/dist/doctor/checks/credentials.js +5 -1
- package/dist/doctor/checks/credentials.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/providers/access.d.ts +7 -0
- package/dist/integrations/providers/access.d.ts.map +1 -0
- package/dist/integrations/providers/access.js +17 -0
- package/dist/integrations/providers/access.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts +17 -54
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +46 -58
- package/dist/integrations/providers/discover.js.map +1 -1
- package/dist/integrations/providers/harness-credentials.d.ts +14 -0
- package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
- package/dist/integrations/providers/harness-credentials.js +46 -0
- package/dist/integrations/providers/harness-credentials.js.map +1 -1
- package/dist/integrations/providers/model-reasoning.d.ts +4 -0
- package/dist/integrations/providers/model-reasoning.d.ts.map +1 -0
- package/dist/integrations/providers/model-reasoning.js +46 -0
- package/dist/integrations/providers/model-reasoning.js.map +1 -0
- package/dist/integrations/providers/register.d.ts +1 -1
- package/dist/integrations/providers/register.d.ts.map +1 -1
- package/dist/integrations/providers/register.js +11 -1
- package/dist/integrations/providers/register.js.map +1 -1
- package/dist/integrations/providers/registry.d.ts +4 -4
- package/dist/integrations/providers/registry.d.ts.map +1 -1
- package/dist/integrations/providers/registry.js +23 -0
- package/dist/integrations/providers/registry.js.map +1 -1
- package/dist/integrations/sessions/context-inventory.d.ts +4 -0
- package/dist/integrations/sessions/context-inventory.d.ts.map +1 -0
- package/dist/integrations/sessions/context-inventory.js +44 -0
- package/dist/integrations/sessions/context-inventory.js.map +1 -0
- package/dist/integrations/sessions/conversation-search.d.ts +60 -0
- package/dist/integrations/sessions/conversation-search.d.ts.map +1 -0
- package/dist/integrations/sessions/conversation-search.js +461 -0
- package/dist/integrations/sessions/conversation-search.js.map +1 -0
- package/dist/integrations/sessions/task-context.d.ts +4 -0
- package/dist/integrations/sessions/task-context.d.ts.map +1 -0
- package/dist/integrations/sessions/task-context.js +73 -0
- package/dist/integrations/sessions/task-context.js.map +1 -0
- package/dist/integrations/subagents/activity.d.ts +3 -1
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +20 -2
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +15 -6
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +274 -46
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/memory/io.d.ts +12 -0
- package/dist/memory/io.d.ts.map +1 -0
- package/dist/memory/io.js +177 -0
- package/dist/memory/io.js.map +1 -0
- package/dist/memory/presentation.d.ts +12 -0
- package/dist/memory/presentation.d.ts.map +1 -0
- package/dist/memory/presentation.js +51 -0
- package/dist/memory/presentation.js.map +1 -0
- package/dist/memory/store.d.ts +17 -3
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +61 -35
- package/dist/memory/store.js.map +1 -1
- package/dist/output-schema.d.ts +4 -0
- package/dist/output-schema.d.ts.map +1 -0
- package/dist/output-schema.js +18 -0
- package/dist/output-schema.js.map +1 -0
- package/dist/permissions/mode.d.ts +3 -0
- package/dist/permissions/mode.d.ts.map +1 -1
- package/dist/permissions/mode.js +18 -0
- package/dist/permissions/mode.js.map +1 -1
- package/dist/tui/AgentExplorer.d.ts +13 -2
- package/dist/tui/AgentExplorer.d.ts.map +1 -1
- package/dist/tui/AgentExplorer.js +93 -23
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +554 -107
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/BrandHeader.d.ts +2 -5
- package/dist/tui/BrandHeader.d.ts.map +1 -1
- package/dist/tui/BrandHeader.js +3 -6
- package/dist/tui/BrandHeader.js.map +1 -1
- package/dist/tui/Composer.d.ts +3 -2
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +11 -9
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/ComposerFrame.d.ts.map +1 -1
- package/dist/tui/ComposerFrame.js +1 -1
- package/dist/tui/ComposerFrame.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +1 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +12 -4
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/Picker.d.ts.map +1 -1
- package/dist/tui/Picker.js +97 -21
- package/dist/tui/Picker.js.map +1 -1
- package/dist/tui/TextPrompt.d.ts +3 -1
- package/dist/tui/TextPrompt.d.ts.map +1 -1
- package/dist/tui/TextPrompt.js +55 -9
- package/dist/tui/TextPrompt.js.map +1 -1
- package/dist/tui/Transcript.d.ts +1 -9
- package/dist/tui/Transcript.d.ts.map +1 -1
- package/dist/tui/Transcript.js +32 -44
- package/dist/tui/Transcript.js.map +1 -1
- package/dist/tui/agent.d.ts +21 -4
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +207 -78
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/model-choices.d.ts +3 -1
- package/dist/tui/model-choices.d.ts.map +1 -1
- package/dist/tui/model-choices.js +12 -1
- package/dist/tui/model-choices.js.map +1 -1
- package/dist/tui/model-search.d.ts +4 -0
- package/dist/tui/model-search.d.ts.map +1 -0
- package/dist/tui/model-search.js +11 -0
- package/dist/tui/model-search.js.map +1 -0
- package/dist/tui/model-selection-intent.d.ts +8 -0
- package/dist/tui/model-selection-intent.d.ts.map +1 -0
- package/dist/tui/model-selection-intent.js +96 -0
- package/dist/tui/model-selection-intent.js.map +1 -0
- package/dist/tui/model-switch-tool.d.ts +7 -0
- package/dist/tui/model-switch-tool.d.ts.map +1 -0
- package/dist/tui/model-switch-tool.js +74 -0
- package/dist/tui/model-switch-tool.js.map +1 -0
- package/dist/tui/model-switch.d.ts +35 -0
- package/dist/tui/model-switch.d.ts.map +1 -0
- package/dist/tui/model-switch.js +106 -0
- package/dist/tui/model-switch.js.map +1 -0
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +25 -12
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/run-interruption.d.ts +7 -0
- package/dist/tui/run-interruption.d.ts.map +1 -1
- package/dist/tui/run-interruption.js +41 -0
- package/dist/tui/run-interruption.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +5 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +188 -15
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/types.d.ts +6 -13
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +8 -7
package/dist/tui/agent.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
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, BackgroundJobRegistry, DefaultPathBuilder, DiskCheckpointStore, DiskMemoryStore, DiskTaskStore, EVENT_NAME_ATTRIBUTE, GuardedFetchProvider, PromptContributionRegistry, ProviderRegistry, SESSION_GOAL_TOOL_NAMES,
|
|
23
|
+
import { BOOT_EVENT_NAMES, BackgroundJobRegistry, DefaultPathBuilder, DiskCheckpointStore, DiskMemoryStore, DiskTaskStore, EVENT_NAME_ATTRIBUTE, GuardedFetchProvider, PromptContributionRegistry, ProviderRegistry, SESSION_GOAL_TOOL_NAMES, ToolRegistry, WebFetchTool, batchNeedsReview, buildAskUserQuestionTool, buildMemoryTools, buildSessionGoalTools, compactNow, createComputerUseTool, createMemoryPromoter, createMemoryRecallStep, createReviewHandler, createToolPresenter, generateProjectId, generateRunId, generateSessionId, generateTenantId, generateTopicId, getBuiltinTools, isReviewExempt, 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';
|
|
@@ -34,15 +34,22 @@ import { resolveSandbox, sandboxResolvedSeverity, } from '../context/sandbox.js'
|
|
|
34
34
|
import { composeTurnSnapshot, readTurnSnapshot } from '../context/turn-snapshot.js';
|
|
35
35
|
import { connectMcpServers, } from '../integrations/mcp/servers.js';
|
|
36
36
|
import { createCliPluginRuntime } from '../integrations/plugins/runtime.js';
|
|
37
|
+
import { hasApiCredential, requiresCredentialForModel } from '../integrations/providers/access.js';
|
|
37
38
|
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';
|
|
39
|
+
import { modelReasoningView } from '../integrations/providers/model-reasoning.js';
|
|
40
|
+
import { createContextInventoryStep } from '../integrations/sessions/context-inventory.js';
|
|
41
|
+
import { buildConversationReadTool, buildConversationSearchTool, } from '../integrations/sessions/conversation-search.js';
|
|
42
|
+
import { createTaskContextStep } from '../integrations/sessions/task-context.js';
|
|
38
43
|
import { ensurePrivateStateDirectory } from '../integrations/state/private-directory.js';
|
|
39
44
|
import { discoverAgentDefinitions } from '../integrations/subagents/definitions.js';
|
|
40
45
|
import { SubagentPathBuilder, resolveSubagentParent } from '../integrations/subagents/parent.js';
|
|
41
46
|
import { CLI_INTERACTIVE_RUN_TIMEOUT_MS } from '../integrations/subagents/policy.js';
|
|
42
47
|
import { createSubagentRuntime } from '../integrations/subagents/runtime.js';
|
|
43
48
|
import { cliLogger } from '../logging.js';
|
|
49
|
+
import { formatMemoryDiagnostics } from '../memory/presentation.js';
|
|
44
50
|
import { composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
45
51
|
import { projectRunConversation } from './conversation-history.js';
|
|
52
|
+
import { buildSwitchModelTool } from './model-switch-tool.js';
|
|
46
53
|
/**
|
|
47
54
|
* Let one caller stop waiting without cutting a shared queue in the middle.
|
|
48
55
|
*
|
|
@@ -303,10 +310,12 @@ function credentialGap(prefs, detected) {
|
|
|
303
310
|
// one ever did, it is not a credential problem and must not be reported as
|
|
304
311
|
// one — a wrong diagnosis sends the operator to paste a key that would not
|
|
305
312
|
// have helped.
|
|
306
|
-
if (!entry ||
|
|
313
|
+
if (!entry ||
|
|
314
|
+
!entry.constructible ||
|
|
315
|
+
!requiresCredentialForModel(entry, primary.model ?? entry.defaultModel))
|
|
307
316
|
return null;
|
|
308
317
|
const det = findDetected(detected, primary.id);
|
|
309
|
-
if (det?.apiKey)
|
|
318
|
+
if (hasApiCredential(entry, det?.apiKey))
|
|
310
319
|
return null;
|
|
311
320
|
return { providerId: primary.id, reason: missingCredentialMessage(entry) };
|
|
312
321
|
}
|
|
@@ -363,9 +372,12 @@ function foregroundOnlyBash(tool) {
|
|
|
363
372
|
};
|
|
364
373
|
}
|
|
365
374
|
function builtinTools(backgroundJobs) {
|
|
366
|
-
return getBuiltinTools().flatMap((
|
|
367
|
-
if (EXCLUDED_BUILTINS.has(
|
|
375
|
+
return getBuiltinTools().flatMap((source) => {
|
|
376
|
+
if (EXCLUDED_BUILTINS.has(source.name))
|
|
368
377
|
return [];
|
|
378
|
+
const tool = ['bash', 'write', 'edit'].includes(source.name)
|
|
379
|
+
? { ...source, executionBarrier: true }
|
|
380
|
+
: source;
|
|
369
381
|
if (backgroundJobs)
|
|
370
382
|
return [tool];
|
|
371
383
|
if (tool.name === 'job')
|
|
@@ -399,14 +411,8 @@ function buildToolRegistry(cwd, projectStateRoot = join(cwd, '.namzu'), backgrou
|
|
|
399
411
|
// handing `getIndex()` to the synchronous overload before the first store
|
|
400
412
|
// read makes a new process report every persisted memory as absent.
|
|
401
413
|
registry.register(buildMemoryTools(memoryStore));
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// callers: the session below passes a `taskStore`, so query() registers the
|
|
405
|
-
// task tools deferred and the search has a roster; a sub-agent is built with
|
|
406
|
-
// no task store, so its registry has nothing deferred and the tool could
|
|
407
|
-
// only ever answer "no deferred tools matching X" — a capability advertised
|
|
408
|
-
// every turn that costs a turn to discover is unusable. Mounting it is the
|
|
409
|
-
// caller's decision, made where the roster is known.
|
|
414
|
+
// query() mounts search_tools only if a deferred roster actually exists,
|
|
415
|
+
// after runtime tools are registered. Ordinary CLI task tools are active.
|
|
410
416
|
return { registry, memoryStore };
|
|
411
417
|
}
|
|
412
418
|
/** Refresh plugin skill metadata before each provider operation. */
|
|
@@ -464,7 +470,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
464
470
|
return emptySession(`Unknown provider "${primary.id}" — pick another.`, 'invocation');
|
|
465
471
|
}
|
|
466
472
|
const det = findDetected(detected, primary.id);
|
|
467
|
-
if (entry.
|
|
473
|
+
if (requiresCredentialForModel(entry, primary.model ?? entry.defaultModel) &&
|
|
474
|
+
!hasApiCredential(entry, det?.apiKey)) {
|
|
468
475
|
// The BACKSTOP, not the operator-facing answer. The TUI never reaches this
|
|
469
476
|
// line any more: `probeAgentSession` reports the same gap as a
|
|
470
477
|
// `credentialGap` and the App routes into the picker, where a credential
|
|
@@ -473,7 +480,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
473
480
|
// pieces of advice below are real: an environment variable, or
|
|
474
481
|
// `--provider`. Keeping the refusal is what makes those runs fail rather
|
|
475
482
|
// than quietly start on something else.
|
|
476
|
-
return emptySession(`No credential found for ${entry.label}. Set one of: ${entry.envVars.join(', ')} — or pass --provider with one that is configured.`);
|
|
483
|
+
return emptySession(`No credential found for ${entry.label}${entry.id === 'zen' ? ' with the selected model. Choose muse-spark-1.3-contributor-free for public access' : ''}. Set one of: ${entry.envVars.join(', ')} — or pass --provider with one that is configured.`);
|
|
477
484
|
}
|
|
478
485
|
try {
|
|
479
486
|
await ensureRegistered(primary.id);
|
|
@@ -523,7 +530,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
523
530
|
}
|
|
524
531
|
let provider;
|
|
525
532
|
try {
|
|
526
|
-
provider = constructProvider(primary.id, det, model
|
|
533
|
+
provider = constructProvider(primary.id, det, model, {
|
|
534
|
+
sessionId: scope.sessionId,
|
|
535
|
+
});
|
|
527
536
|
}
|
|
528
537
|
catch (err) {
|
|
529
538
|
return emptySession(`Failed to construct ${entry.label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -597,7 +606,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
597
606
|
if (fresh === currentToken)
|
|
598
607
|
return;
|
|
599
608
|
try {
|
|
600
|
-
const refreshedProvider = constructProvider('anthropic', { ...det, apiKey: fresh }, model);
|
|
609
|
+
const refreshedProvider = constructProvider('anthropic', { ...det, apiKey: fresh }, model, { sessionId: scope.sessionId });
|
|
601
610
|
// Publish the pair together. If construction fails, both old values
|
|
602
611
|
// remain live and the next operation can retry against the stored token.
|
|
603
612
|
provider = refreshedProvider;
|
|
@@ -634,7 +643,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
634
643
|
expiresAt: credential.expiresAt,
|
|
635
644
|
origin: 'codex-file',
|
|
636
645
|
},
|
|
637
|
-
}, model);
|
|
646
|
+
}, model, { sessionId: scope.sessionId });
|
|
638
647
|
provider = refreshedProvider;
|
|
639
648
|
currentToken = credential.accessToken;
|
|
640
649
|
currentCodexAccount = credential.accountId;
|
|
@@ -652,7 +661,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
652
661
|
}
|
|
653
662
|
if (credential.accessToken === currentToken)
|
|
654
663
|
return;
|
|
655
|
-
provider = constructProvider('anthropic', { ...det, apiKey: credential.accessToken }, model);
|
|
664
|
+
provider = constructProvider('anthropic', { ...det, apiKey: credential.accessToken }, model, { sessionId: scope.sessionId });
|
|
656
665
|
currentToken = credential.accessToken;
|
|
657
666
|
};
|
|
658
667
|
const refreshStoredCodex = async (signal) => {
|
|
@@ -668,7 +677,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
668
677
|
expiresAt: credential.expiresAt,
|
|
669
678
|
origin: 'stored',
|
|
670
679
|
},
|
|
671
|
-
}, model);
|
|
680
|
+
}, model, { sessionId: scope.sessionId });
|
|
672
681
|
provider = refreshedProvider;
|
|
673
682
|
currentToken = credential.accessToken;
|
|
674
683
|
currentCodexAccount = credential.accountId;
|
|
@@ -695,6 +704,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
695
704
|
credentialTail = queued.catch(() => { });
|
|
696
705
|
return signal ? observeWithSignal(queued, signal) : queued;
|
|
697
706
|
};
|
|
707
|
+
// The TUI can replace its conversation without replacing this session object.
|
|
708
|
+
// Bind each admitted run to its captured conversation, including durable resumes;
|
|
709
|
+
// a Zen client must never generate a fresh Go session for each model call.
|
|
710
|
+
const providerForSession = (sessionId) => primary.id === 'zen' || primary.id === 'zen-go'
|
|
711
|
+
? constructProvider(primary.id, det, model, { sessionId })
|
|
712
|
+
: provider;
|
|
698
713
|
// Session-owned discovery with one drain cursor per run. A child shares the
|
|
699
714
|
// discovered scopes without being able to consume the parent's update, and
|
|
700
715
|
// an edit takes effect in this session rather than only after reconnecting.
|
|
@@ -874,9 +889,6 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
874
889
|
computerUseReady: computerUseHost !== undefined,
|
|
875
890
|
...(computerUseError ? { computerUseError } : {}),
|
|
876
891
|
});
|
|
877
|
-
// This session passes a `taskStore` to query() below, which registers the
|
|
878
|
-
// task tools deferred — so `search_tools` has something to find here.
|
|
879
|
-
registry.register([SearchToolsTool]);
|
|
880
892
|
// Web reach, opted into in the config file and nowhere else. The parent's
|
|
881
893
|
// registry only: a child's config carries no provider, and a tool whose
|
|
882
894
|
// provider is missing is a tool that reports itself unwired — truthful,
|
|
@@ -890,6 +902,19 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
890
902
|
// delegate a self-contained task to a fresh sub-agent (own context window).
|
|
891
903
|
// Best-effort — if the runtime can't stand up, the chat still works.
|
|
892
904
|
const delegationScopes = new Map();
|
|
905
|
+
const delegatedInputWaiters = new Map();
|
|
906
|
+
if (options.conversationSessions) {
|
|
907
|
+
const sessions = options.conversationSessions;
|
|
908
|
+
for (const build of [buildConversationSearchTool, buildConversationReadTool])
|
|
909
|
+
registry.register(build((context) => {
|
|
910
|
+
const owner = delegationScopes.get(context.runId);
|
|
911
|
+
if (!owner ||
|
|
912
|
+
owner.projectId !== sessions.projectId ||
|
|
913
|
+
owner.tenantId !== sessions.tenantId)
|
|
914
|
+
throw new Error('The requesting run does not own this conversation.');
|
|
915
|
+
return { sessions, sessionId: owner.sessionId };
|
|
916
|
+
}));
|
|
917
|
+
}
|
|
893
918
|
let subagentRuntime;
|
|
894
919
|
// Stays empty when the runtime below throws, which is the honest answer: the
|
|
895
920
|
// catch is non-fatal and the session then genuinely has no delegate to
|
|
@@ -921,6 +946,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
921
946
|
},
|
|
922
947
|
sandboxWorkspace,
|
|
923
948
|
resolveResumeHandler: (runId) => delegatedResumeHandlers.get(runId),
|
|
949
|
+
resolveWaitForInbound: (runId) => delegatedInputWaiters.get(runId),
|
|
924
950
|
...(sandbox.provider ? { sandboxProvider: sandbox.provider } : {}),
|
|
925
951
|
...(options.sandbox?.teardownTimeoutMs !== undefined
|
|
926
952
|
? { sandboxTeardownTimeoutMs: options.sandbox.teardownTimeoutMs }
|
|
@@ -948,7 +974,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
948
974
|
// child announcing a swap the parent never made, inside a tool result,
|
|
949
975
|
// is a worse surface than the child simply failing and the parent
|
|
950
976
|
// reporting it.
|
|
951
|
-
buildProvider: () =>
|
|
977
|
+
buildProvider: (sessionId) => {
|
|
978
|
+
if (!sessionId && (primary.id === 'zen' || primary.id === 'zen-go')) {
|
|
979
|
+
throw new Error('A delegated provider requires its invoking conversation.');
|
|
980
|
+
}
|
|
981
|
+
return constructProvider(primary.id, det ? { ...det, apiKey: currentToken ?? det.apiKey } : det, model, { sessionId });
|
|
982
|
+
},
|
|
952
983
|
buildTools: () => {
|
|
953
984
|
// Sub-agents get the parent's working set minus `search_tools`:
|
|
954
985
|
// they run without a task store, so nothing in their registry is
|
|
@@ -964,7 +995,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
964
995
|
authorizationGate: gateFor(options.rules),
|
|
965
996
|
});
|
|
966
997
|
subagentRuntime = sub;
|
|
967
|
-
registry.register([sub.agentTool]);
|
|
998
|
+
registry.register([sub.agentTool, sub.waitForTaskTool]);
|
|
999
|
+
if (sub.sendMessageTool)
|
|
1000
|
+
registry.register(sub.sendMessageTool);
|
|
968
1001
|
allowedAgentIds = sub.allowedAgentIds;
|
|
969
1002
|
}
|
|
970
1003
|
catch (err) {
|
|
@@ -978,6 +1011,20 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
978
1011
|
// and got none had nothing on stderr to say why.
|
|
979
1012
|
cliLogger().warn('sub-agent runtime unavailable this session', exceptionAttributes(err));
|
|
980
1013
|
}
|
|
1014
|
+
// This capability belongs to the active main turn, never the child roster.
|
|
1015
|
+
const modelSwitchHandlers = new Map();
|
|
1016
|
+
if (options.allowModelSwitch) {
|
|
1017
|
+
registry.register(buildSwitchModelTool(async (request, context) => {
|
|
1018
|
+
const handler = modelSwitchHandlers.get(context.runId);
|
|
1019
|
+
if (!handler || context.abortSignal?.aborted) {
|
|
1020
|
+
return {
|
|
1021
|
+
kind: 'rejected',
|
|
1022
|
+
reason: 'This turn no longer owns model selection.',
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return handler(request, context.abortSignal);
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
981
1028
|
// `ask_user_question`, where somebody can answer. The SDK tool parks the
|
|
982
1029
|
// run through the handler it was BUILT with, so that handler reads the
|
|
983
1030
|
// turn's answerer through a holder the prelude fills: the tool is per
|
|
@@ -1019,8 +1066,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1019
1066
|
// query call, because the doctrine tells the model to plan with them and a
|
|
1020
1067
|
// tool it must search for first is a tool it skips.
|
|
1021
1068
|
//
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1069
|
+
// A tool server or plugin can still register a deferred roster; query()
|
|
1070
|
+
// mounts search_tools when one exists. Do not advertise an empty search. The
|
|
1024
1071
|
// task tools are registered inside query(), after this function returns,
|
|
1025
1072
|
// which is why the connect line reports no count of them — counting here
|
|
1026
1073
|
// would mean restating query's registration order in the CLI.
|
|
@@ -1065,16 +1112,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1065
1112
|
// Persists across turns: once the user picks "approve all", later tool
|
|
1066
1113
|
// batches in this session run without prompting.
|
|
1067
1114
|
const approval = { all: false };
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
// index for every message.
|
|
1072
|
-
//
|
|
1073
|
-
// Unconditional, unlike the answer gate. A gate changes what a run may do
|
|
1074
|
-
// and so must be asked for; promotion changes only what survives it, and
|
|
1075
|
-
// the alternative — the run's own extracted knowledge being discarded at
|
|
1076
|
-
// settle — is what this repository has been doing all along by accident.
|
|
1077
|
-
// A run that learned nothing still writes nothing.
|
|
1115
|
+
// Share the project store with tools and recall. Each run selects either
|
|
1116
|
+
// this extracted-claim promoter or explicit consolidation, never both.
|
|
1117
|
+
// Candidates without useful claims write nothing.
|
|
1078
1118
|
const promoteMemory = createMemoryPromoter({ store: memoryStore });
|
|
1079
1119
|
// Plugins are the last fallible startup resource. The ordering is ownership:
|
|
1080
1120
|
// a malformed MCP entry cannot strand imported plugin hooks, and a plugin
|
|
@@ -1153,10 +1193,25 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1153
1193
|
let reasoningEffortDefault;
|
|
1154
1194
|
let effortNotice;
|
|
1155
1195
|
try {
|
|
1156
|
-
const
|
|
1196
|
+
const capabilityMembers = [
|
|
1157
1197
|
{ provider, model },
|
|
1158
|
-
...fallbackPlan.build(currentToken),
|
|
1159
|
-
]
|
|
1198
|
+
...fallbackPlan.build(currentToken, scope.sessionId),
|
|
1199
|
+
];
|
|
1200
|
+
const capabilityView = withProviderFallback(await Promise.all(capabilityMembers.map(async (member) => {
|
|
1201
|
+
const memberModel = member.model ?? model;
|
|
1202
|
+
const known = member.provider.reasoningEffortLevelsFor
|
|
1203
|
+
? member.provider.reasoningEffortLevelsFor(memberModel)
|
|
1204
|
+
: member.provider.effortLevelsFor?.(memberModel);
|
|
1205
|
+
// A model-owned answer (including []) needs no extra catalogue
|
|
1206
|
+
// request. Discover only missing capability information.
|
|
1207
|
+
const catalogue = known === undefined && member.provider.listModels
|
|
1208
|
+
? await runPickerProviderOperation(undefined, (signal) => member.provider.listModels?.(signal) ?? Promise.resolve([])).catch(() => [])
|
|
1209
|
+
: [];
|
|
1210
|
+
return {
|
|
1211
|
+
...member,
|
|
1212
|
+
provider: modelReasoningView(member.provider, memberModel, catalogue),
|
|
1213
|
+
};
|
|
1214
|
+
})));
|
|
1160
1215
|
const offered = capabilityView.reasoningEffortLevelsFor
|
|
1161
1216
|
? capabilityView.reasoningEffortLevelsFor(model)
|
|
1162
1217
|
: capabilityView.effortLevelsFor?.(model);
|
|
@@ -1197,7 +1252,10 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1197
1252
|
const pluginSkills = pluginRuntime
|
|
1198
1253
|
? await currentPluginSkills(pluginRuntime.skills)
|
|
1199
1254
|
: undefined;
|
|
1200
|
-
const
|
|
1255
|
+
const curatedMemory = readMemory(undefined, cwd);
|
|
1256
|
+
for (const notice of formatMemoryDiagnostics(curatedMemory))
|
|
1257
|
+
cliLogger().warn(notice);
|
|
1258
|
+
const memoryPrompt = composeMemoryPrompt(curatedMemory);
|
|
1201
1259
|
const environmentPrompt = composeEnvironmentPrompt({
|
|
1202
1260
|
...(await readEnvironmentFacts(cwd)),
|
|
1203
1261
|
additionalDirectories: [...directories],
|
|
@@ -1217,15 +1275,16 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1217
1275
|
}
|
|
1218
1276
|
delegatedResumeHandlers.set(entry.runId, resumeHandler);
|
|
1219
1277
|
delegationScopes.set(entry.runId, { ...entry, topicId: scope.topicId });
|
|
1278
|
+
const runTaskStore = selectTaskStore(entry.runId, { ...entry, topicId: scope.topicId });
|
|
1220
1279
|
try {
|
|
1221
1280
|
return await resumeRun({
|
|
1222
|
-
provider,
|
|
1223
|
-
fallbackProviders: fallbackPlan.build(currentToken),
|
|
1281
|
+
provider: providerForSession(entry.sessionId),
|
|
1282
|
+
fallbackProviders: fallbackPlan.build(currentToken, entry.sessionId),
|
|
1224
1283
|
tools: registry,
|
|
1225
1284
|
pluginManager: pluginRuntime?.manager,
|
|
1226
1285
|
skillRegistry: pluginRuntime?.skills,
|
|
1227
1286
|
skills: pluginSkills,
|
|
1228
|
-
taskStore:
|
|
1287
|
+
taskStore: runTaskStore,
|
|
1229
1288
|
// The same availability the original run registered under.
|
|
1230
1289
|
// A resumed run re-registers the task tools; leaving them at
|
|
1231
1290
|
// the kernel's `deferred` default would hand the model a plan
|
|
@@ -1242,6 +1301,21 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1242
1301
|
: {}),
|
|
1243
1302
|
authorizationGate: gateFor(options.rules),
|
|
1244
1303
|
compactionConfig: compactionConfigFor(options.compaction),
|
|
1304
|
+
prepareStep: [
|
|
1305
|
+
createTaskContextStep(runTaskStore, entry.tenantId),
|
|
1306
|
+
...(options.memory?.recall === false
|
|
1307
|
+
? []
|
|
1308
|
+
: [
|
|
1309
|
+
createMemoryRecallStep({
|
|
1310
|
+
store: memoryStore,
|
|
1311
|
+
identifierGrounding: options.memory?.identifierGrounding,
|
|
1312
|
+
}),
|
|
1313
|
+
]),
|
|
1314
|
+
...(options.conversationSessions ? [createContextInventoryStep()] : []),
|
|
1315
|
+
],
|
|
1316
|
+
...(options.compaction?.consolidate
|
|
1317
|
+
? { consolidateInto: memoryStore }
|
|
1318
|
+
: { promoteMemory }),
|
|
1245
1319
|
projectInstructionContext: projectInstructions.createRunContext(),
|
|
1246
1320
|
pathBuilder,
|
|
1247
1321
|
...(sandbox.provider ? { sandboxProvider: sandbox.provider } : {}),
|
|
@@ -1389,11 +1463,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1389
1463
|
reasoningEffortLevels,
|
|
1390
1464
|
reasoningEffortDefault,
|
|
1391
1465
|
compact: (messages) => operations.promise(undefined, async (signal) => {
|
|
1466
|
+
const sessionId = scope.sessionId;
|
|
1392
1467
|
await prepareProviderCredential(signal);
|
|
1393
1468
|
return compactNow({
|
|
1394
1469
|
messages,
|
|
1395
1470
|
config: compactionConfigFor(options.compaction),
|
|
1396
|
-
provider,
|
|
1471
|
+
provider: providerForSession(sessionId),
|
|
1397
1472
|
model,
|
|
1398
1473
|
signal,
|
|
1399
1474
|
});
|
|
@@ -1457,6 +1532,10 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1457
1532
|
throw new Error(`Run ${runId} already owns a delegated review channel.`);
|
|
1458
1533
|
}
|
|
1459
1534
|
delegatedResumeHandlers.set(runId, resumeHandler);
|
|
1535
|
+
if (opts?.onModelSwitch)
|
|
1536
|
+
modelSwitchHandlers.set(runId, opts.onModelSwitch);
|
|
1537
|
+
if (opts?.waitForInbound)
|
|
1538
|
+
delegatedInputWaiters.set(runId, opts.waitForInbound);
|
|
1460
1539
|
const turnScope = { ...scope };
|
|
1461
1540
|
delegationScopes.set(runId, turnScope);
|
|
1462
1541
|
try {
|
|
@@ -1477,7 +1556,11 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1477
1556
|
const pluginSkills = pluginRuntime
|
|
1478
1557
|
? await currentPluginSkills(pluginRuntime.skills)
|
|
1479
1558
|
: undefined;
|
|
1480
|
-
const
|
|
1559
|
+
const curatedMemory = readMemory(undefined, cwd);
|
|
1560
|
+
for (const notice of formatMemoryDiagnostics(curatedMemory)) {
|
|
1561
|
+
yield { kind: 'context', text: notice, shed: false };
|
|
1562
|
+
}
|
|
1563
|
+
const memoryPrompt = composeMemoryPrompt(curatedMemory);
|
|
1481
1564
|
currentOnQuestion = opts?.onQuestion;
|
|
1482
1565
|
const [environmentFacts, turnSnapshot] = await Promise.all([
|
|
1483
1566
|
readEnvironmentFacts(cwd),
|
|
@@ -1549,9 +1632,10 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1549
1632
|
capturedAuthority = Object.freeze({ ...opts.goalRound });
|
|
1550
1633
|
goalAuthorities.set(opts.runId, capturedAuthority);
|
|
1551
1634
|
}
|
|
1635
|
+
const runTaskStore = selectTaskStore(runId, turnScope);
|
|
1552
1636
|
try {
|
|
1553
1637
|
yield* runTurn({
|
|
1554
|
-
provider,
|
|
1638
|
+
provider: providerForSession(turnScope.sessionId),
|
|
1555
1639
|
compactionConfig: compactionConfigFor(options.compaction),
|
|
1556
1640
|
...(options.compaction?.consolidate ? { consolidateInto: memoryStore } : {}),
|
|
1557
1641
|
...(jobRegistry
|
|
@@ -1567,7 +1651,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1567
1651
|
// that expired hours ago — and a chain whose own members are stale
|
|
1568
1652
|
// is a fallback that fails for the reason the fallback exists to
|
|
1569
1653
|
// survive. Building a driver is a client object, not a request.
|
|
1570
|
-
fallbackProviders: fallbackPlan.build(currentToken),
|
|
1654
|
+
fallbackProviders: fallbackPlan.build(currentToken, turnScope.sessionId),
|
|
1571
1655
|
model,
|
|
1572
1656
|
tools: registry,
|
|
1573
1657
|
pluginManager: pluginRuntime?.manager,
|
|
@@ -1580,16 +1664,31 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1580
1664
|
...(options.limits ? { limits: options.limits } : {}),
|
|
1581
1665
|
sandboxWorkspace,
|
|
1582
1666
|
rules: options.rules,
|
|
1667
|
+
structuredOutput: options.structuredOutput,
|
|
1583
1668
|
reviewAnswer: options.reviewAnswer,
|
|
1584
1669
|
maxAnswerReviews: options.maxAnswerReviews,
|
|
1585
|
-
promoteMemory,
|
|
1586
|
-
|
|
1670
|
+
promoteMemory: options.compaction?.consolidate ? undefined : promoteMemory,
|
|
1671
|
+
prepareStep: [
|
|
1672
|
+
createTaskContextStep(runTaskStore, turnScope.tenantId),
|
|
1673
|
+
...(options.memory?.recall === false
|
|
1674
|
+
? []
|
|
1675
|
+
: [
|
|
1676
|
+
createMemoryRecallStep({
|
|
1677
|
+
store: memoryStore,
|
|
1678
|
+
query: lastUserText(messages),
|
|
1679
|
+
identifierGrounding: options.memory?.identifierGrounding,
|
|
1680
|
+
}),
|
|
1681
|
+
]),
|
|
1682
|
+
...(options.conversationSessions ? [createContextInventoryStep()] : []),
|
|
1683
|
+
],
|
|
1684
|
+
taskStore: runTaskStore,
|
|
1587
1685
|
systemPrompt,
|
|
1588
1686
|
messages,
|
|
1589
1687
|
projectInstructionContext: projectInstructions.createRunContext(),
|
|
1590
1688
|
opts: turnOpts,
|
|
1591
1689
|
resumeHandler,
|
|
1592
1690
|
taskGateway: await subagentRuntime?.gatewayForRun(runId),
|
|
1691
|
+
completionInbox: await subagentRuntime?.completionInboxForRun(runId),
|
|
1593
1692
|
promptContributions,
|
|
1594
1693
|
...(webCapability ? { web: webCapability } : {}),
|
|
1595
1694
|
// Active, not deferred: the doctrine tells the model to open a
|
|
@@ -1620,6 +1719,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1620
1719
|
finally {
|
|
1621
1720
|
if (delegatedResumeHandlers.get(runId) === resumeHandler) {
|
|
1622
1721
|
delegatedResumeHandlers.delete(runId);
|
|
1722
|
+
modelSwitchHandlers.delete(runId);
|
|
1723
|
+
delegatedInputWaiters.delete(runId);
|
|
1623
1724
|
delegationScopes.delete(runId);
|
|
1624
1725
|
await subagentRuntime?.releaseRun(runId);
|
|
1625
1726
|
}
|
|
@@ -1650,7 +1751,8 @@ function planFallbacks(members, detected) {
|
|
|
1650
1751
|
continue;
|
|
1651
1752
|
}
|
|
1652
1753
|
const det = findDetected(detected, member.id);
|
|
1653
|
-
if (entry.
|
|
1754
|
+
if (requiresCredentialForModel(entry, member.model ?? entry.defaultModel) &&
|
|
1755
|
+
!hasApiCredential(entry, det?.apiKey)) {
|
|
1654
1756
|
notices.push(`Provider chain: ${position} (${entry.label}) has no credential, so nothing will fall over to it. ` +
|
|
1655
1757
|
`Set one of: ${entry.envVars.join(', ')}.`);
|
|
1656
1758
|
continue;
|
|
@@ -1665,7 +1767,7 @@ function planFallbacks(members, detected) {
|
|
|
1665
1767
|
}
|
|
1666
1768
|
return {
|
|
1667
1769
|
notices,
|
|
1668
|
-
build(headToken) {
|
|
1770
|
+
build(headToken, sessionId) {
|
|
1669
1771
|
const out = [];
|
|
1670
1772
|
for (const { choice, det } of usable) {
|
|
1671
1773
|
const entry = PROVIDER_REGISTRY[choice.id];
|
|
@@ -1675,7 +1777,9 @@ function planFallbacks(members, detected) {
|
|
|
1675
1777
|
try {
|
|
1676
1778
|
const credential = headToken !== undefined && det?.oauth ? { ...det, apiKey: headToken } : det;
|
|
1677
1779
|
out.push({
|
|
1678
|
-
provider: constructProvider(choice.id, credential, memberModel
|
|
1780
|
+
provider: constructProvider(choice.id, credential, memberModel, {
|
|
1781
|
+
sessionId,
|
|
1782
|
+
}),
|
|
1679
1783
|
model: memberModel,
|
|
1680
1784
|
});
|
|
1681
1785
|
}
|
|
@@ -1691,7 +1795,7 @@ function planFallbacks(members, detected) {
|
|
|
1691
1795
|
},
|
|
1692
1796
|
};
|
|
1693
1797
|
}
|
|
1694
|
-
export function constructProvider(id, det, model) {
|
|
1798
|
+
export function constructProvider(id, det, model, context = {}) {
|
|
1695
1799
|
switch (id) {
|
|
1696
1800
|
case 'anthropic': {
|
|
1697
1801
|
const token = det?.apiKey ?? '';
|
|
@@ -1743,6 +1847,24 @@ export function constructProvider(id, det, model) {
|
|
|
1743
1847
|
});
|
|
1744
1848
|
return provider;
|
|
1745
1849
|
}
|
|
1850
|
+
case 'zen':
|
|
1851
|
+
case 'zen-go': {
|
|
1852
|
+
const apiKey = hasApiCredential(PROVIDER_REGISTRY[id], det?.apiKey) ? det?.apiKey : undefined;
|
|
1853
|
+
if (id === 'zen-go' && apiKey === undefined)
|
|
1854
|
+
throw new Error('Zen Go requires an API key.');
|
|
1855
|
+
const { provider } = ProviderRegistry.create({
|
|
1856
|
+
...(id === 'zen-go'
|
|
1857
|
+
? { type: 'zen-go', apiKey: apiKey }
|
|
1858
|
+
: {
|
|
1859
|
+
type: 'zen',
|
|
1860
|
+
...(apiKey === undefined ? {} : { apiKey }),
|
|
1861
|
+
}),
|
|
1862
|
+
baseURL: det?.baseUrl,
|
|
1863
|
+
model,
|
|
1864
|
+
...(context.sessionId ? { sessionId: context.sessionId } : {}),
|
|
1865
|
+
});
|
|
1866
|
+
return provider;
|
|
1867
|
+
}
|
|
1746
1868
|
case 'ollama': {
|
|
1747
1869
|
const { provider } = ProviderRegistry.create({
|
|
1748
1870
|
type: 'ollama',
|
|
@@ -2007,7 +2129,7 @@ function compactionConfigFor(compaction) {
|
|
|
2007
2129
|
: {}),
|
|
2008
2130
|
};
|
|
2009
2131
|
}
|
|
2010
|
-
async function* runTurn({ provider, compactionConfig, consolidateInto, backgroundJobs, backgroundJobOwner, fallbackProviders, model, tools, pluginManager, skillRegistry, skills, scope, pathBuilder, workingDirectory, limits, additionalDirectories, sandboxWorkspace, rules, reviewAnswer, maxAnswerReviews, promoteMemory, resumeHandler, taskStore, systemPrompt, messages, projectInstructionContext, opts, taskGateway, promptContributions, runtimeToolOverrides, web, sandboxProvider, sandboxTeardownTimeoutMs, onRunEvent, }) {
|
|
2132
|
+
async function* runTurn({ provider, compactionConfig, consolidateInto, backgroundJobs, backgroundJobOwner, fallbackProviders, model, tools, pluginManager, skillRegistry, skills, scope, pathBuilder, workingDirectory, limits, additionalDirectories, sandboxWorkspace, rules, structuredOutput, reviewAnswer, maxAnswerReviews, promoteMemory, prepareStep, resumeHandler, taskStore, systemPrompt, messages, projectInstructionContext, opts, taskGateway, completionInbox, promptContributions, runtimeToolOverrides, web, sandboxProvider, sandboxTeardownTimeoutMs, onRunEvent, }) {
|
|
2011
2133
|
const signal = opts?.signal;
|
|
2012
2134
|
// One presenter for the whole stream, built from the registry this scope
|
|
2013
2135
|
// already holds. Its absence HERE is what forced presentation to be name
|
|
@@ -2016,6 +2138,7 @@ async function* runTurn({ provider, compactionConfig, consolidateInto, backgroun
|
|
|
2016
2138
|
const presenter = createToolPresenter(tools);
|
|
2017
2139
|
try {
|
|
2018
2140
|
const events = query({
|
|
2141
|
+
...(structuredOutput ? { structuredOutput } : {}),
|
|
2019
2142
|
provider,
|
|
2020
2143
|
pathBuilder,
|
|
2021
2144
|
...(opts?.runId ? { runId: opts.runId } : {}),
|
|
@@ -2062,17 +2185,17 @@ async function* runTurn({ provider, compactionConfig, consolidateInto, backgroun
|
|
|
2062
2185
|
// that shipped before gates existed.
|
|
2063
2186
|
...(reviewAnswer ? { reviewAnswer } : {}),
|
|
2064
2187
|
...(maxAnswerReviews !== undefined ? { maxAnswerReviews } : {}),
|
|
2065
|
-
//
|
|
2066
|
-
// do and so must be asked for; promotion changes only what
|
|
2067
|
-
// survives it, and a run that learned nothing still writes
|
|
2068
|
-
// nothing.
|
|
2188
|
+
// Undefined when the host selected consolidation as its writer.
|
|
2069
2189
|
promoteMemory,
|
|
2190
|
+
...(prepareStep ? { prepareStep } : {}),
|
|
2070
2191
|
agentId: 'namzu',
|
|
2071
2192
|
agentName: 'namzu',
|
|
2072
2193
|
...(systemPrompt ? { systemPrompt } : {}),
|
|
2073
2194
|
projectInstructionContext,
|
|
2074
2195
|
messages: [...messages],
|
|
2075
2196
|
...(opts?.inboundMessages ? { inboundMessages: opts.inboundMessages } : {}),
|
|
2197
|
+
...(opts?.waitForInbound ? { waitForInbound: opts.waitForInbound } : {}),
|
|
2198
|
+
...(completionInbox ? { completionInbox } : {}),
|
|
2076
2199
|
workingDirectory,
|
|
2077
2200
|
...(additionalDirectories?.length ? { additionalDirectories } : {}),
|
|
2078
2201
|
// The exemption reads `tools` at decision time, so it sees the task
|
|
@@ -2086,6 +2209,7 @@ async function* runTurn({ provider, compactionConfig, consolidateInto, backgroun
|
|
|
2086
2209
|
...scope,
|
|
2087
2210
|
});
|
|
2088
2211
|
let settled = false;
|
|
2212
|
+
let abortReported = false;
|
|
2089
2213
|
try {
|
|
2090
2214
|
while (true) {
|
|
2091
2215
|
const next = await events.next();
|
|
@@ -2106,8 +2230,14 @@ async function* runTurn({ provider, compactionConfig, consolidateInto, backgroun
|
|
|
2106
2230
|
// be a recording of the interface rather than of the session.
|
|
2107
2231
|
onRunEvent?.(event);
|
|
2108
2232
|
if (signal?.aborted) {
|
|
2109
|
-
|
|
2110
|
-
|
|
2233
|
+
if (!abortReported) {
|
|
2234
|
+
abortReported = true;
|
|
2235
|
+
yield { kind: 'error', message: 'aborted' };
|
|
2236
|
+
}
|
|
2237
|
+
// Let cancellation settle in the kernel. Calling return() here
|
|
2238
|
+
// discarded its Run and forced App to save only visible prose,
|
|
2239
|
+
// losing tool receipts and reasoning before the next user turn.
|
|
2240
|
+
continue;
|
|
2111
2241
|
}
|
|
2112
2242
|
const mapped = toAgentEvent(event, presenter);
|
|
2113
2243
|
if (!mapped)
|
|
@@ -2215,14 +2345,12 @@ export function toAgentEvent(event, presenter) {
|
|
|
2215
2345
|
output: event.result,
|
|
2216
2346
|
});
|
|
2217
2347
|
const detail = viewToLines(view);
|
|
2218
|
-
//
|
|
2219
|
-
//
|
|
2220
|
-
// whitespaced copy of the same text — a `read` used to show its
|
|
2221
|
-
// first line twice, once collapsed and once numbered.
|
|
2348
|
+
// Drop only an exact duplicate. A shortened summary cannot replace
|
|
2349
|
+
// the first line's evidence in expanded or raw output.
|
|
2222
2350
|
const summary = view.kind === 'terminal' && detail && detail.length > 0
|
|
2223
2351
|
? truncate(detail[0], 120)
|
|
2224
2352
|
: firstLine(event.result);
|
|
2225
|
-
const withoutRepeatedSummary = view.kind === 'terminal' && detail
|
|
2353
|
+
const withoutRepeatedSummary = view.kind === 'terminal' && detail?.[0] === summary ? detail.slice(1) : detail;
|
|
2226
2354
|
return {
|
|
2227
2355
|
kind: 'tool-end',
|
|
2228
2356
|
runId: event.runId,
|
|
@@ -2334,6 +2462,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
2334
2462
|
// answer was refused.
|
|
2335
2463
|
return {
|
|
2336
2464
|
kind: 'done',
|
|
2465
|
+
text: event.result,
|
|
2337
2466
|
...(event.budget ? { budget: event.budget } : {}),
|
|
2338
2467
|
...(event.stopReason ? { stopReason: event.stopReason } : {}),
|
|
2339
2468
|
};
|
|
@@ -2483,7 +2612,7 @@ function describeFallback(event) {
|
|
|
2483
2612
|
* server's, a plugin's — could not get a diff no matter what it did.
|
|
2484
2613
|
*
|
|
2485
2614
|
* The tool now says which admitted shape it wants, and this decides what
|
|
2486
|
-
* that looks like in a terminal.
|
|
2615
|
+
* that looks like in a terminal. Collapsing and the `STDOUT:`/`STDERR:`
|
|
2487
2616
|
* cleanup stay here on purpose: how many rows fit and how a shell labels
|
|
2488
2617
|
* its streams are properties of this surface, not of the tool.
|
|
2489
2618
|
*/
|
|
@@ -2499,13 +2628,13 @@ export function viewToLines(view) {
|
|
|
2499
2628
|
// never produces this — it returns no view at all for an insert,
|
|
2500
2629
|
// rather than claim the file was empty.
|
|
2501
2630
|
if (view.before === '') {
|
|
2502
|
-
const lines =
|
|
2631
|
+
const lines = outputLines(view.after);
|
|
2503
2632
|
return lines.length > 0 ? lines : undefined;
|
|
2504
2633
|
}
|
|
2505
2634
|
const lines = [];
|
|
2506
|
-
for (const line of
|
|
2635
|
+
for (const line of outputLines(view.before))
|
|
2507
2636
|
lines.push(`- ${line}`);
|
|
2508
|
-
for (const line of
|
|
2637
|
+
for (const line of outputLines(view.after))
|
|
2509
2638
|
lines.push(`+ ${line}`);
|
|
2510
2639
|
return lines.length > 0 ? lines : undefined;
|
|
2511
2640
|
}
|
|
@@ -2514,7 +2643,7 @@ export function viewToLines(view) {
|
|
|
2514
2643
|
return undefined;
|
|
2515
2644
|
const lines = resultToLines(view.output);
|
|
2516
2645
|
// A single short line is already the summary — no need to repeat it.
|
|
2517
|
-
return lines.length
|
|
2646
|
+
return lines.length === 1 && lines[0] === truncate(lines[0] ?? '', 120) ? undefined : lines;
|
|
2518
2647
|
}
|
|
2519
2648
|
}
|
|
2520
2649
|
}
|
|
@@ -2533,10 +2662,10 @@ function truncate(value, max) {
|
|
|
2533
2662
|
const oneLine = value.replace(/\s+/g, ' ');
|
|
2534
2663
|
return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine;
|
|
2535
2664
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
return
|
|
2665
|
+
function outputLines(value) {
|
|
2666
|
+
// The renderer bounds the preview. Retain admitted output so expanding
|
|
2667
|
+
// or selecting raw text never loses a diagnostic after an arbitrary line.
|
|
2668
|
+
return value.replace(/\s+$/, '').split('\n');
|
|
2540
2669
|
}
|
|
2541
2670
|
/** Parse a string as a JSON object, or null. Connector tools return JSON. */
|
|
2542
2671
|
function parseJsonObject(s) {
|
|
@@ -2578,11 +2707,11 @@ function payloadString(result) {
|
|
|
2578
2707
|
function resultToLines(result) {
|
|
2579
2708
|
const payload = payloadString(result);
|
|
2580
2709
|
if (payload !== null)
|
|
2581
|
-
return
|
|
2710
|
+
return outputLines(payload);
|
|
2582
2711
|
const obj = parseJsonObject(result);
|
|
2583
2712
|
if (obj)
|
|
2584
|
-
return
|
|
2585
|
-
return
|
|
2713
|
+
return outputLines(JSON.stringify(obj, null, 2));
|
|
2714
|
+
return outputLines(cleanToolText(result.trim()));
|
|
2586
2715
|
}
|
|
2587
2716
|
/** Concise one-line summary of a tool result for the `⎿` line. */
|
|
2588
2717
|
function firstLine(result) {
|