@gajae-code/coding-agent 0.12.0 → 0.12.2
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 +111 -0
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +56 -2
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +23 -3
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +37 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +40 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +30 -4
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +7 -1
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +9 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +290 -0
- package/src/async/job-manager.ts +99 -39
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +53 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +25 -20
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -26
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +491 -27
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +635 -612
- package/src/gjc-runtime/workflow-manifest.generated.json +37 -99
- package/src/gjc-runtime/workflow-manifest.ts +21 -70
- package/src/internal-urls/docs-index.generated.ts +16 -16
- package/src/modes/DESIGN.md +104 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -15
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +42 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +193 -34
- package/src/modes/controllers/selector-controller.ts +160 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/runtime-init.ts +3 -0
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +7 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/session.ts +89 -9
- package/src/session/agent-session.ts +844 -173
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +200 -111
- package/src/session/internal/managed-session-storage.ts +492 -62
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +38 -45
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +280 -45
- package/src/task/output-manager.ts +24 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +34 -8
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +6 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
package/src/task/index.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - Progress tracking via JSON events
|
|
13
13
|
* - Session artifacts for debugging
|
|
14
14
|
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
15
16
|
import * as fs from "node:fs/promises";
|
|
16
17
|
import * as os from "node:os";
|
|
17
18
|
import path from "node:path";
|
|
@@ -19,7 +20,7 @@ import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@gajae
|
|
|
19
20
|
import type { Model, Usage } from "@gajae-code/ai";
|
|
20
21
|
import { $pickenv, prompt, Snowflake } from "@gajae-code/utils";
|
|
21
22
|
import type { ToolSession } from "..";
|
|
22
|
-
import { AsyncJobManager, OwnerSubagentShutdownError, type ResumeRunner } from "../async";
|
|
23
|
+
import { AsyncJobManager, OwnerSubagentShutdownError, type ResumeRunner, type SubagentRunOutcome } from "../async";
|
|
23
24
|
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
|
24
25
|
import type { Theme } from "../modes/theme/theme";
|
|
25
26
|
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
resolveTaskRepositoryBinding,
|
|
54
55
|
} from "../gjc-runtime/repository-binding";
|
|
55
56
|
import { initializeLocalRoot, type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
57
|
+
import { ArtifactManager } from "../session/artifacts";
|
|
56
58
|
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
57
59
|
import * as git from "../utils/git";
|
|
58
60
|
import { discoverAgents, filterVisibleAgents, getAgent } from "./discovery";
|
|
@@ -88,6 +90,7 @@ interface TaskResumeDescriptor {
|
|
|
88
90
|
params: TaskParams;
|
|
89
91
|
task: TaskItem & { id: string };
|
|
90
92
|
sessionFile: string | null;
|
|
93
|
+
durableOutputAllowed?: boolean;
|
|
91
94
|
forkContextSeed?: ForkContextSeed;
|
|
92
95
|
agentSource: AgentDefinition["source"];
|
|
93
96
|
}
|
|
@@ -98,6 +101,23 @@ function isTaskResumeDescriptor(value: unknown): value is TaskResumeDescriptor {
|
|
|
98
101
|
function renderTaskAssignment(assignment: string, simpleMode: TaskSimpleMode): string {
|
|
99
102
|
return renderSubagentUserPrompt(assignment, simpleMode === "independent");
|
|
100
103
|
}
|
|
104
|
+
|
|
105
|
+
export function subagentRunOutcomeFromSingleResult(
|
|
106
|
+
finalText: string,
|
|
107
|
+
singleResult: Pick<SingleResult, "aborted" | "exitCode" | "paused" | "setupFailure"> | undefined,
|
|
108
|
+
): string | SubagentRunOutcome {
|
|
109
|
+
if (singleResult?.paused) return { kind: "paused" };
|
|
110
|
+
if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
|
|
111
|
+
return {
|
|
112
|
+
kind: "failed",
|
|
113
|
+
text: finalText,
|
|
114
|
+
...(singleResult.setupFailure && !singleResult.aborted
|
|
115
|
+
? { setupFailureSummary: singleResult.setupFailure.summary }
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return finalText;
|
|
120
|
+
}
|
|
101
121
|
function createUsageTotals(): Usage {
|
|
102
122
|
return {
|
|
103
123
|
input: 0,
|
|
@@ -403,6 +423,37 @@ export function resolveForkContextMaxTokens(configured: number, model: Model | u
|
|
|
403
423
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
404
424
|
// Tool Class
|
|
405
425
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
426
|
+
interface SessionLifetimeArtifactsState {
|
|
427
|
+
dir?: string;
|
|
428
|
+
manager?: ArtifactManager;
|
|
429
|
+
ensurePromise?: Promise<string | null>;
|
|
430
|
+
outputPrefix: string;
|
|
431
|
+
authorized: boolean;
|
|
432
|
+
cleanupRegistered: boolean;
|
|
433
|
+
originalGetArtifactsDir?: ToolSession["getArtifactsDir"];
|
|
434
|
+
originalGetAuthorizedArtifactsDirs?: ToolSession["getAuthorizedArtifactsDirs"];
|
|
435
|
+
originalGetArtifactManager?: ToolSession["getArtifactManager"];
|
|
436
|
+
originalAgentOutputManager?: AgentOutputManager;
|
|
437
|
+
installedGetArtifactsDir?: ToolSession["getArtifactsDir"];
|
|
438
|
+
installedGetAuthorizedArtifactsDirs?: ToolSession["getAuthorizedArtifactsDirs"];
|
|
439
|
+
installedGetArtifactManager?: ToolSession["getArtifactManager"];
|
|
440
|
+
installedAgentOutputManager?: AgentOutputManager;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const sessionLifetimeArtifacts = new WeakMap<ToolSession, SessionLifetimeArtifactsState>();
|
|
444
|
+
|
|
445
|
+
function sessionLifetimeArtifactsState(session: ToolSession): SessionLifetimeArtifactsState {
|
|
446
|
+
let state = sessionLifetimeArtifacts.get(session);
|
|
447
|
+
if (!state) {
|
|
448
|
+
state = {
|
|
449
|
+
authorized: false,
|
|
450
|
+
cleanupRegistered: false,
|
|
451
|
+
outputPrefix: `0-Session${randomUUID().replaceAll("-", "")}`,
|
|
452
|
+
};
|
|
453
|
+
sessionLifetimeArtifacts.set(session, state);
|
|
454
|
+
}
|
|
455
|
+
return state;
|
|
456
|
+
}
|
|
406
457
|
|
|
407
458
|
/**
|
|
408
459
|
* Task tool - Delegate tasks to specialized agents.
|
|
@@ -419,6 +470,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
419
470
|
readonly renderResult = renderResult;
|
|
420
471
|
readonly #discoveredAgents: AgentDefinition[];
|
|
421
472
|
readonly #blockedAgent: string | undefined;
|
|
473
|
+
/**
|
|
474
|
+
* Session-lifetime durable artifact state is shared atomically by every
|
|
475
|
+
* TaskTool created for the same parent ToolSession.
|
|
476
|
+
*/
|
|
422
477
|
|
|
423
478
|
get parameters(): TaskToolSchemaInstance {
|
|
424
479
|
const isolationEnabled = this.session.settings.get("task.isolation.mode") !== "none";
|
|
@@ -461,6 +516,172 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
461
516
|
return this.session.settings.get("task.simple");
|
|
462
517
|
}
|
|
463
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Ensure a session-lifetime artifact directory when the parent has no session file.
|
|
521
|
+
* Returns null only when durable allocation fails (mkdir error) — callers must not
|
|
522
|
+
* advertise agent:// URIs without a successful allocation.
|
|
523
|
+
*/
|
|
524
|
+
async #ensureSessionLifetimeArtifacts(): Promise<{ dir: string; manager: ArtifactManager } | null> {
|
|
525
|
+
const state = sessionLifetimeArtifactsState(this.session);
|
|
526
|
+
if (state.authorized && state.dir && state.manager) return { dir: state.dir, manager: state.manager };
|
|
527
|
+
|
|
528
|
+
const sessionArtifactsDir = this.session.getArtifactsDir?.() ?? null;
|
|
529
|
+
if (sessionArtifactsDir) {
|
|
530
|
+
const existingManager = this.session.getArtifactManager?.() ?? null;
|
|
531
|
+
if (existingManager && path.resolve(existingManager.dir) !== path.resolve(sessionArtifactsDir)) return null;
|
|
532
|
+
const manager = existingManager ?? new ArtifactManager(sessionArtifactsDir);
|
|
533
|
+
state.dir = sessionArtifactsDir;
|
|
534
|
+
state.manager = manager;
|
|
535
|
+
if (!(await this.#authorizeSessionLifetimeArtifacts(state, sessionArtifactsDir, manager, false))) return null;
|
|
536
|
+
return { dir: sessionArtifactsDir, manager };
|
|
537
|
+
}
|
|
538
|
+
if (!this.session.registerSessionCleanup) return null;
|
|
539
|
+
|
|
540
|
+
state.ensurePromise ??= this.#allocateSessionLifetimeArtifacts(state);
|
|
541
|
+
const dir = await state.ensurePromise;
|
|
542
|
+
if (!dir || !state.manager) {
|
|
543
|
+
state.ensurePromise = undefined;
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
return { dir, manager: state.manager };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async #allocateSessionLifetimeArtifacts(state: SessionLifetimeArtifactsState): Promise<string | null> {
|
|
550
|
+
let dir: string;
|
|
551
|
+
try {
|
|
552
|
+
dir = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-task-session-"));
|
|
553
|
+
} catch {
|
|
554
|
+
state.dir = undefined;
|
|
555
|
+
state.manager = undefined;
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
state.dir = dir;
|
|
559
|
+
state.manager = new ArtifactManager(dir);
|
|
560
|
+
if (!(await this.#authorizeSessionLifetimeArtifacts(state, dir, state.manager, true))) return null;
|
|
561
|
+
return dir;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Expose the durable root on the parent ToolSession so subsequent parent-side
|
|
566
|
+
* tools and same-session descendants can resolve agent:// without registry-wide
|
|
567
|
+
* enumeration (#3302 scoped authorization).
|
|
568
|
+
*/
|
|
569
|
+
async #authorizeSessionLifetimeArtifacts(
|
|
570
|
+
state: SessionLifetimeArtifactsState,
|
|
571
|
+
dir: string,
|
|
572
|
+
manager: ArtifactManager,
|
|
573
|
+
owned: boolean,
|
|
574
|
+
): Promise<boolean> {
|
|
575
|
+
if (state.authorized) return true;
|
|
576
|
+
state.originalGetArtifactsDir = this.session.getArtifactsDir;
|
|
577
|
+
state.originalGetAuthorizedArtifactsDirs = this.session.getAuthorizedArtifactsDirs;
|
|
578
|
+
state.originalGetArtifactManager = this.session.getArtifactManager;
|
|
579
|
+
state.originalAgentOutputManager = this.session.agentOutputManager;
|
|
580
|
+
|
|
581
|
+
state.installedGetArtifactsDir = () => state.originalGetArtifactsDir?.() ?? dir;
|
|
582
|
+
state.installedGetAuthorizedArtifactsDirs = () => {
|
|
583
|
+
const dirs: string[] = [];
|
|
584
|
+
const add = (candidate: string | null | undefined) => {
|
|
585
|
+
if (!candidate) return;
|
|
586
|
+
const normalized = path.resolve(candidate);
|
|
587
|
+
if (!dirs.includes(normalized)) dirs.push(normalized);
|
|
588
|
+
};
|
|
589
|
+
for (const authorized of state.originalGetAuthorizedArtifactsDirs?.() ?? []) add(authorized);
|
|
590
|
+
add(dir);
|
|
591
|
+
return dirs;
|
|
592
|
+
};
|
|
593
|
+
state.installedGetArtifactManager = () => manager;
|
|
594
|
+
if (owned || !this.session.agentOutputManager) {
|
|
595
|
+
state.installedAgentOutputManager = new AgentOutputManager(() => this.session.getArtifactsDir?.() ?? null, {
|
|
596
|
+
parentPrefix: owned ? state.outputPrefix : undefined,
|
|
597
|
+
getAuthorizedArtifactsDirs: () => this.session.getAuthorizedArtifactsDirs?.() ?? [],
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
let cleanupRan = false;
|
|
602
|
+
const cleanup = async () => {
|
|
603
|
+
cleanupRan = true;
|
|
604
|
+
try {
|
|
605
|
+
if (owned) await fs.rm(dir, { recursive: true, force: true });
|
|
606
|
+
} finally {
|
|
607
|
+
if (this.session.getArtifactsDir === state.installedGetArtifactsDir)
|
|
608
|
+
this.session.getArtifactsDir = state.originalGetArtifactsDir;
|
|
609
|
+
if (this.session.getAuthorizedArtifactsDirs === state.installedGetAuthorizedArtifactsDirs)
|
|
610
|
+
this.session.getAuthorizedArtifactsDirs = state.originalGetAuthorizedArtifactsDirs;
|
|
611
|
+
if (this.session.getArtifactManager === state.installedGetArtifactManager)
|
|
612
|
+
this.session.getArtifactManager = state.originalGetArtifactManager;
|
|
613
|
+
if (this.session.agentOutputManager === state.installedAgentOutputManager)
|
|
614
|
+
this.session.agentOutputManager = state.originalAgentOutputManager;
|
|
615
|
+
sessionLifetimeArtifacts.delete(this.session);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
if (owned) {
|
|
619
|
+
const registerCleanup = this.session.registerSessionCleanup;
|
|
620
|
+
if (!registerCleanup) {
|
|
621
|
+
state.dir = undefined;
|
|
622
|
+
state.manager = undefined;
|
|
623
|
+
state.ensurePromise = undefined;
|
|
624
|
+
sessionLifetimeArtifacts.delete(this.session);
|
|
625
|
+
await fs.rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
registerCleanup(cleanup);
|
|
630
|
+
} catch {
|
|
631
|
+
state.dir = undefined;
|
|
632
|
+
state.manager = undefined;
|
|
633
|
+
state.ensurePromise = undefined;
|
|
634
|
+
sessionLifetimeArtifacts.delete(this.session);
|
|
635
|
+
await fs.rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
if (cleanupRan) return false;
|
|
639
|
+
state.cleanupRegistered = true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
state.authorized = true;
|
|
643
|
+
this.session.getArtifactsDir = state.installedGetArtifactsDir;
|
|
644
|
+
this.session.getAuthorizedArtifactsDirs = state.installedGetAuthorizedArtifactsDirs;
|
|
645
|
+
this.session.getArtifactManager = state.installedGetArtifactManager;
|
|
646
|
+
if (state.installedAgentOutputManager) this.session.agentOutputManager = state.installedAgentOutputManager;
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Resolve the effective artifacts directory for this task batch.
|
|
652
|
+
* File-backed sessions use the session artifacts path; in-memory parents use
|
|
653
|
+
* the session-lifetime durable temp root when allocation succeeds.
|
|
654
|
+
*/
|
|
655
|
+
async #resolveEffectiveArtifactsDir(): Promise<{
|
|
656
|
+
sessionArtifactsDir: string | null;
|
|
657
|
+
durableArtifactsDir: string | null;
|
|
658
|
+
effectiveArtifactsDir: string | undefined;
|
|
659
|
+
parentArtifactManager: ArtifactManager | undefined;
|
|
660
|
+
}> {
|
|
661
|
+
const sessionFile = this.session.getSessionFile();
|
|
662
|
+
const sessionArtifactsDir = sessionFile ? sessionFile.slice(0, -6) : null;
|
|
663
|
+
if (sessionArtifactsDir) {
|
|
664
|
+
const candidateManager = this.session.getArtifactManager?.() ?? undefined;
|
|
665
|
+
const parentArtifactManager =
|
|
666
|
+
candidateManager && path.resolve(candidateManager.dir) === path.resolve(sessionArtifactsDir)
|
|
667
|
+
? candidateManager
|
|
668
|
+
: undefined;
|
|
669
|
+
return {
|
|
670
|
+
sessionArtifactsDir,
|
|
671
|
+
durableArtifactsDir: null,
|
|
672
|
+
effectiveArtifactsDir: sessionArtifactsDir,
|
|
673
|
+
parentArtifactManager,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const durable = await this.#ensureSessionLifetimeArtifacts();
|
|
677
|
+
return {
|
|
678
|
+
sessionArtifactsDir: null,
|
|
679
|
+
durableArtifactsDir: durable?.dir ?? null,
|
|
680
|
+
effectiveArtifactsDir: durable?.dir,
|
|
681
|
+
parentArtifactManager: durable?.manager,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
464
685
|
/**
|
|
465
686
|
* Create a TaskTool instance.
|
|
466
687
|
* Repository authority is captured from session cwd *before* agent discovery so
|
|
@@ -572,6 +793,25 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
572
793
|
};
|
|
573
794
|
}
|
|
574
795
|
|
|
796
|
+
// Prefer file-backed session artifacts; otherwise allocate a session-lifetime
|
|
797
|
+
// durable root so detached outputs remain readable for the parent session.
|
|
798
|
+
// Resolve before ID allocation so agentOutputManager can scan the durable root.
|
|
799
|
+
const { effectiveArtifactsDir: batchArtifactsDir, parentArtifactManager: asyncParentArtifactManager } =
|
|
800
|
+
await this.#resolveEffectiveArtifactsDir();
|
|
801
|
+
let externalTaskSessionsDir: string | undefined;
|
|
802
|
+
if (!batchArtifactsDir) {
|
|
803
|
+
// Durable allocation failed: keep child session jsonl under local:// only.
|
|
804
|
+
// Do not advertise agent:// URIs without durable output backing.
|
|
805
|
+
const asyncLocalOptions: LocalProtocolOptions = {
|
|
806
|
+
getArtifactsDir: this.session.getArtifactsDir ?? (() => null),
|
|
807
|
+
isManagedDestination: this.session.isManagedSessionDestination,
|
|
808
|
+
getSessionId: this.session.getSessionId ?? (() => null),
|
|
809
|
+
};
|
|
810
|
+
await initializeLocalRoot(asyncLocalOptions);
|
|
811
|
+
externalTaskSessionsDir = resolveLocalUrlToPath("local://subagents/sessions", asyncLocalOptions);
|
|
812
|
+
await fs.mkdir(externalTaskSessionsDir, { recursive: true, mode: 0o700 });
|
|
813
|
+
}
|
|
814
|
+
|
|
575
815
|
const outputManager =
|
|
576
816
|
this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null));
|
|
577
817
|
const uniqueIds = await outputManager.allocateBatch(taskItems.map(t => t.id));
|
|
@@ -651,19 +891,19 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
651
891
|
resumeMessage: message,
|
|
652
892
|
sessionFiles: new Map([[descriptor.task.id, descriptor.sessionFile]]),
|
|
653
893
|
suppressRoiReconciliation: true,
|
|
894
|
+
...(descriptor.durableOutputAllowed === true
|
|
895
|
+
? {}
|
|
896
|
+
: {
|
|
897
|
+
persistence: {
|
|
898
|
+
effectiveArtifactsDir: undefined,
|
|
899
|
+
parentArtifactManager: undefined,
|
|
900
|
+
},
|
|
901
|
+
}),
|
|
654
902
|
},
|
|
655
903
|
);
|
|
656
904
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
657
905
|
const singleResult = result.details?.results[0];
|
|
658
|
-
|
|
659
|
-
// A resumed subprocess that aborted or exited non-zero is a failed
|
|
660
|
-
// resume, not a completed one. Throw the rendered failure summary
|
|
661
|
-
// (finalText) so the leg is reported failed instead of being
|
|
662
|
-
// returned as successful continuation.
|
|
663
|
-
if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
|
|
664
|
-
throw new Error(finalText);
|
|
665
|
-
}
|
|
666
|
-
return finalText;
|
|
906
|
+
return subagentRunOutcomeFromSingleResult(finalText, singleResult);
|
|
667
907
|
},
|
|
668
908
|
{
|
|
669
909
|
id: `${descriptor.task.id}-resume-${Snowflake.next()}`,
|
|
@@ -705,20 +945,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
705
945
|
});
|
|
706
946
|
};
|
|
707
947
|
const frozenForkSeeds = new Map<string, ForkContextSeed>();
|
|
708
|
-
const asyncParentArtifactManager = this.session.getArtifactManager?.() ?? undefined;
|
|
709
|
-
const parentSessionFileForBatch = this.session.getSessionFile();
|
|
710
|
-
const batchArtifactsDir = parentSessionFileForBatch ? parentSessionFileForBatch.slice(0, -6) : null;
|
|
711
|
-
let externalTaskSessionsDir: string | undefined;
|
|
712
|
-
if (!batchArtifactsDir) {
|
|
713
|
-
const asyncLocalOptions: LocalProtocolOptions = {
|
|
714
|
-
getArtifactsDir: this.session.getArtifactsDir ?? (() => null),
|
|
715
|
-
isManagedDestination: this.session.isManagedSessionDestination,
|
|
716
|
-
getSessionId: this.session.getSessionId ?? (() => null),
|
|
717
|
-
};
|
|
718
|
-
await initializeLocalRoot(asyncLocalOptions);
|
|
719
|
-
externalTaskSessionsDir = resolveLocalUrlToPath("local://subagents/sessions", asyncLocalOptions);
|
|
720
|
-
await fs.mkdir(externalTaskSessionsDir, { recursive: true, mode: 0o700 });
|
|
721
|
-
}
|
|
722
948
|
|
|
723
949
|
for (let i = 0; i < taskItems.length; i++) {
|
|
724
950
|
const taskItem = taskItems[i];
|
|
@@ -784,6 +1010,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
784
1010
|
{
|
|
785
1011
|
sessionFiles: new Map([[uniqueId, subtaskSessionFile]]),
|
|
786
1012
|
suppressRoiReconciliation: true,
|
|
1013
|
+
persistence: {
|
|
1014
|
+
effectiveArtifactsDir: batchArtifactsDir,
|
|
1015
|
+
parentArtifactManager: asyncParentArtifactManager,
|
|
1016
|
+
},
|
|
787
1017
|
},
|
|
788
1018
|
);
|
|
789
1019
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
@@ -809,6 +1039,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
809
1039
|
}
|
|
810
1040
|
: undefined;
|
|
811
1041
|
progress.retryState = undefined;
|
|
1042
|
+
progress.setupFailure = singleResult?.setupFailure;
|
|
812
1043
|
}
|
|
813
1044
|
completedJobs += 1;
|
|
814
1045
|
if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
|
|
@@ -831,10 +1062,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
831
1062
|
`Background task batch complete: ${completedJobs}/${taskItems.length} finished.`,
|
|
832
1063
|
);
|
|
833
1064
|
}
|
|
834
|
-
|
|
835
|
-
return { kind: "paused" };
|
|
836
|
-
}
|
|
837
|
-
return finalText;
|
|
1065
|
+
return subagentRunOutcomeFromSingleResult(finalText, singleResult);
|
|
838
1066
|
} catch (error) {
|
|
839
1067
|
if (progress) {
|
|
840
1068
|
progress.status = "failed";
|
|
@@ -895,6 +1123,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
895
1123
|
params,
|
|
896
1124
|
task: { ...taskItem, id: uniqueId },
|
|
897
1125
|
sessionFile: subtaskSessionFile,
|
|
1126
|
+
durableOutputAllowed: Boolean(batchArtifactsDir),
|
|
898
1127
|
forkContextSeed: frozenForkSeed,
|
|
899
1128
|
agentSource: fallbackAgentSource,
|
|
900
1129
|
} satisfies TaskResumeDescriptor,
|
|
@@ -991,6 +1220,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
991
1220
|
runMode?: "initial" | "resume" | "message";
|
|
992
1221
|
resumeMessage?: string;
|
|
993
1222
|
sessionFiles?: ReadonlyMap<string, string | null>;
|
|
1223
|
+
persistence?: {
|
|
1224
|
+
effectiveArtifactsDir: string | undefined;
|
|
1225
|
+
parentArtifactManager: ArtifactManager | undefined;
|
|
1226
|
+
};
|
|
994
1227
|
/**
|
|
995
1228
|
* Set for per-child async runs: the spawnPlan is carried for gate
|
|
996
1229
|
* consistency, but batch-level ROI reconciliation must not be computed
|
|
@@ -1240,11 +1473,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1240
1473
|
|
|
1241
1474
|
const preferredIsolationBackend = parseIsolationMode(isolationMode);
|
|
1242
1475
|
|
|
1243
|
-
//
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1476
|
+
// File-backed session artifacts, or a session-lifetime durable temp root for
|
|
1477
|
+
// in-memory parents. Never use a per-task temp dir that is deleted on return —
|
|
1478
|
+
// advertised agent:// URIs must remain readable for the parent session lifetime.
|
|
1479
|
+
const { effectiveArtifactsDir, parentArtifactManager } =
|
|
1480
|
+
executionOverrides?.persistence ?? (await this.#resolveEffectiveArtifactsDir());
|
|
1481
|
+
const hasDurableOutputBacking = Boolean(effectiveArtifactsDir);
|
|
1248
1482
|
|
|
1249
1483
|
// Share the parent session's local:// root with subagents so they read/write the same scratch space
|
|
1250
1484
|
const localProtocolOptions: LocalProtocolOptions = {
|
|
@@ -1253,9 +1487,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1253
1487
|
getSessionId: this.session.getSessionId ?? (() => null),
|
|
1254
1488
|
};
|
|
1255
1489
|
|
|
1256
|
-
// Subagents adopt the parent's ArtifactManager
|
|
1257
|
-
//
|
|
1258
|
-
|
|
1490
|
+
// Subagents adopt the parent's ArtifactManager (including session-lifetime
|
|
1491
|
+
// durable managers) so artifact IDs are unique and agent:// resolves under
|
|
1492
|
+
// scoped authorization for the whole tree.
|
|
1259
1493
|
|
|
1260
1494
|
// Initialize progress tracking
|
|
1261
1495
|
const progressMap = new Map<number, AgentProgress>();
|
|
@@ -1461,7 +1695,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1461
1695
|
thinkingLevel: thinkingLevelOverride,
|
|
1462
1696
|
outputSchema: effectiveOutputSchema,
|
|
1463
1697
|
sessionFile: taskSessionFile,
|
|
1464
|
-
persistArtifacts:
|
|
1698
|
+
persistArtifacts: hasDurableOutputBacking,
|
|
1465
1699
|
artifactsDir: effectiveArtifactsDir,
|
|
1466
1700
|
managedPersistence,
|
|
1467
1701
|
contextFile: contextFilePath,
|
|
@@ -1481,6 +1715,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1481
1715
|
agentRegistry: this.session.agentRegistry,
|
|
1482
1716
|
settings: this.session.settings,
|
|
1483
1717
|
inheritedServiceTier: this.session.serviceTier,
|
|
1718
|
+
isFastForSubagentProvider: provider => this.session.isFastForSubagentProvider?.(provider) ?? false,
|
|
1484
1719
|
contextFiles,
|
|
1485
1720
|
skills: availableSkills,
|
|
1486
1721
|
autoloadSkills: resolvedAutoloadSkills,
|
|
@@ -1534,7 +1769,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1534
1769
|
thinkingLevel: thinkingLevelOverride,
|
|
1535
1770
|
outputSchema: effectiveOutputSchema,
|
|
1536
1771
|
sessionFile: taskSessionFile,
|
|
1537
|
-
persistArtifacts:
|
|
1772
|
+
persistArtifacts: hasDurableOutputBacking,
|
|
1538
1773
|
artifactsDir: effectiveArtifactsDir,
|
|
1539
1774
|
managedPersistence,
|
|
1540
1775
|
contextFile: contextFilePath,
|
|
@@ -1554,6 +1789,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1554
1789
|
agentRegistry: this.session.agentRegistry,
|
|
1555
1790
|
settings: this.session.settings,
|
|
1556
1791
|
inheritedServiceTier: this.session.serviceTier,
|
|
1792
|
+
isFastForSubagentProvider: provider => this.session.isFastForSubagentProvider?.(provider) ?? false,
|
|
1557
1793
|
contextFiles,
|
|
1558
1794
|
skills: availableSkills,
|
|
1559
1795
|
autoloadSkills: resolvedAutoloadSkills,
|
|
@@ -1694,7 +1930,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1694
1930
|
abortReason: "Cancelled before start",
|
|
1695
1931
|
};
|
|
1696
1932
|
});
|
|
1697
|
-
|
|
1933
|
+
// Only advertise agent:// when outputs land on durable backing (file-backed
|
|
1934
|
+
// session artifacts or the session-lifetime durable temp root). Ephemeral
|
|
1935
|
+
// or failed allocation must not emit dead URIs (#3471 / #295).
|
|
1936
|
+
if (!hasDurableOutputBacking) {
|
|
1698
1937
|
for (const result of results) {
|
|
1699
1938
|
delete result.outputMeta;
|
|
1700
1939
|
delete result.outputPath;
|
|
@@ -1904,12 +2143,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1904
2143
|
mergeSummary,
|
|
1905
2144
|
});
|
|
1906
2145
|
|
|
1907
|
-
//
|
|
1908
|
-
|
|
1909
|
-
tempArtifactsDir && (!isIsolated || changesApplied === true || changesApplied === null);
|
|
1910
|
-
if (shouldCleanupTempArtifacts) {
|
|
1911
|
-
await fs.rm(tempArtifactsDir, { recursive: true, force: true });
|
|
1912
|
-
}
|
|
2146
|
+
// Session-lifetime durable dirs must outlive this return so parent +
|
|
2147
|
+
// same-session descendants can resolve agent://. Do not rm them here.
|
|
1913
2148
|
|
|
1914
2149
|
const details: TaskToolDetails = {
|
|
1915
2150
|
projectAgentsDir,
|
|
@@ -29,10 +29,15 @@ export class AgentOutputManager {
|
|
|
29
29
|
#initPromise: Promise<void> | undefined;
|
|
30
30
|
readonly #getArtifactsDir: () => string | null;
|
|
31
31
|
readonly #parentPrefix: string | undefined;
|
|
32
|
+
readonly #getAuthorizedArtifactsDirs: (() => readonly string[]) | undefined;
|
|
32
33
|
|
|
33
|
-
constructor(
|
|
34
|
+
constructor(
|
|
35
|
+
getArtifactsDir: () => string | null,
|
|
36
|
+
options?: { parentPrefix?: string; getAuthorizedArtifactsDirs?: () => readonly string[] },
|
|
37
|
+
) {
|
|
34
38
|
this.#getArtifactsDir = getArtifactsDir;
|
|
35
39
|
this.#parentPrefix = options?.parentPrefix;
|
|
40
|
+
this.#getAuthorizedArtifactsDirs = options?.getAuthorizedArtifactsDirs;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/**
|
|
@@ -53,24 +58,29 @@ export class AgentOutputManager {
|
|
|
53
58
|
* This ensures we don't overwrite outputs when resuming a session.
|
|
54
59
|
*/
|
|
55
60
|
async #scanExistingOutputs(): Promise<void> {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
61
|
+
const dirs: string[] = [];
|
|
62
|
+
const add = (dir: string | null | undefined) => {
|
|
63
|
+
if (!dir || dirs.includes(dir)) return;
|
|
64
|
+
dirs.push(dir);
|
|
65
|
+
};
|
|
66
|
+
add(this.#getArtifactsDir());
|
|
67
|
+
for (const dir of this.#getAuthorizedArtifactsDirs?.() ?? []) add(dir);
|
|
68
|
+
if (dirs.length === 0) return;
|
|
65
69
|
|
|
66
70
|
const pattern = this.#parentPrefix
|
|
67
71
|
? new RegExp(`^${escapeRegExp(this.#parentPrefix)}\\.(\\d+)-.*\\.${RESERVED_OUTPUT_EXTENSIONS_PATTERN}$`)
|
|
68
72
|
: new RegExp(`^(\\d+)-.*\\.${RESERVED_OUTPUT_EXTENSIONS_PATTERN}$`);
|
|
69
|
-
|
|
70
73
|
let maxId = -1;
|
|
71
|
-
for (const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
for (const dir of dirs) {
|
|
75
|
+
let files: string[];
|
|
76
|
+
try {
|
|
77
|
+
files = await fs.readdir(dir);
|
|
78
|
+
} catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
const match = file.match(pattern);
|
|
83
|
+
if (!match) continue;
|
|
74
84
|
const id = Number.parseInt(match[1], 10);
|
|
75
85
|
if (id > maxId) maxId = id;
|
|
76
86
|
}
|
|
@@ -4,8 +4,24 @@ export type ProviderRetryKind = NonNullable<AgentProgress["retryState"]>["kind"]
|
|
|
4
4
|
|
|
5
5
|
const FIRST_EVENT_TIMEOUT_PATTERN = /stream timed out while waiting for the first event/i;
|
|
6
6
|
const IDLE_STREAM_STALL_PATTERN = /stream stalled while waiting for the next event/i;
|
|
7
|
+
const STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE = "stream_first_event_timeout";
|
|
7
8
|
|
|
8
9
|
export function classifyProviderRetry(errorMessage: string): ProviderRetryKind {
|
|
10
|
+
return classifyProviderRetryFromTransport({ errorMessage });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Prefer typed transport facts when present; fall back to message regex for
|
|
15
|
+
* message-only callers that never received `transportFailure.providerCode`.
|
|
16
|
+
*/
|
|
17
|
+
export function classifyProviderRetryFromTransport(facts: {
|
|
18
|
+
providerCode?: string;
|
|
19
|
+
errorMessage?: string;
|
|
20
|
+
}): ProviderRetryKind {
|
|
21
|
+
if (facts.providerCode?.toLowerCase() === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE) {
|
|
22
|
+
return "first_event_timeout";
|
|
23
|
+
}
|
|
24
|
+
const errorMessage = facts.errorMessage ?? "";
|
|
9
25
|
if (FIRST_EVENT_TIMEOUT_PATTERN.test(errorMessage)) return "first_event_timeout";
|
|
10
26
|
if (IDLE_STREAM_STALL_PATTERN.test(errorMessage)) return "idle_stream_stall";
|
|
11
27
|
return "provider_error";
|