@gajae-code/coding-agent 0.16.3 → 0.16.6
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 +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +26 -1
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +12 -12
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +11 -8
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
package/src/system-prompt.ts
CHANGED
|
@@ -235,6 +235,10 @@ export async function resolvePromptInput(input: string | undefined, description:
|
|
|
235
235
|
export interface LoadContextFilesOptions {
|
|
236
236
|
/** Working directory to start walking up from. Default: getProjectDir() */
|
|
237
237
|
cwd?: string;
|
|
238
|
+
/** Agent directory backing native user-scope context and system prompt files. */
|
|
239
|
+
agentDir?: string;
|
|
240
|
+
/** Resolver-owned classification for `agentDir`; preserve it across HOME/config refreshes. */
|
|
241
|
+
profileAuthority?: "default" | "custom";
|
|
238
242
|
}
|
|
239
243
|
|
|
240
244
|
function dedupeExactContextFiles(
|
|
@@ -265,7 +269,11 @@ export async function loadProjectContextFilesResult(
|
|
|
265
269
|
options: LoadContextFilesOptions = {},
|
|
266
270
|
): Promise<ProjectContextFilesResult> {
|
|
267
271
|
const resolvedCwd = options.cwd ?? getProjectDir();
|
|
268
|
-
const result = await loadCapability(contextFileCapability.id, {
|
|
272
|
+
const result = await loadCapability(contextFileCapability.id, {
|
|
273
|
+
cwd: resolvedCwd,
|
|
274
|
+
agentDir: options.agentDir,
|
|
275
|
+
profileAuthority: options.profileAuthority,
|
|
276
|
+
});
|
|
269
277
|
const items = result.items as ContextFile[];
|
|
270
278
|
|
|
271
279
|
// Native user-global context applies everywhere and is least specific, so it
|
|
@@ -311,7 +319,11 @@ export async function loadProjectContextFiles(
|
|
|
311
319
|
export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {}): Promise<string | null> {
|
|
312
320
|
const resolvedCwd = options.cwd ?? getProjectDir();
|
|
313
321
|
|
|
314
|
-
const result = await loadCapability<SystemPromptFile>(systemPromptCapability.id, {
|
|
322
|
+
const result = await loadCapability<SystemPromptFile>(systemPromptCapability.id, {
|
|
323
|
+
cwd: resolvedCwd,
|
|
324
|
+
agentDir: options.agentDir,
|
|
325
|
+
profileAuthority: options.profileAuthority,
|
|
326
|
+
});
|
|
315
327
|
|
|
316
328
|
if (result.items.length === 0) return null;
|
|
317
329
|
|
|
@@ -368,10 +380,16 @@ export interface BuildSystemPromptOptions {
|
|
|
368
380
|
pluginAppendices?: string;
|
|
369
381
|
/** Repeat full tool descriptions in system prompt. Default: false */
|
|
370
382
|
repeatToolDescriptions?: boolean;
|
|
383
|
+
/** Language guidance for internal technical reasoning. Default: off. */
|
|
384
|
+
reasoningLanguage?: "off" | "english";
|
|
371
385
|
/** Skills settings for discovery. */
|
|
372
386
|
skillsSettings?: SkillsSettings;
|
|
373
387
|
/** Working directory. Default: getProjectDir() */
|
|
374
388
|
cwd?: string;
|
|
389
|
+
/** Agent directory backing native user-scope prompt discovery. */
|
|
390
|
+
agentDir?: string;
|
|
391
|
+
/** Resolver-owned classification for `agentDir`; preserve it across HOME/config refreshes. */
|
|
392
|
+
profileAuthority?: "default" | "custom";
|
|
375
393
|
/** Pre-loaded context files (skips discovery if provided). */
|
|
376
394
|
contextFiles?: Array<{ path: string; content: string; depth?: number }>;
|
|
377
395
|
/** Skills provided directly to system prompt construction. */
|
|
@@ -542,6 +560,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
542
560
|
appendSystemPrompt,
|
|
543
561
|
pluginAppendices,
|
|
544
562
|
repeatToolDescriptions = false,
|
|
563
|
+
reasoningLanguage = "off",
|
|
545
564
|
toolNames: providedToolNames,
|
|
546
565
|
cwd,
|
|
547
566
|
contextFiles: providedContextFiles,
|
|
@@ -600,10 +619,16 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
600
619
|
|
|
601
620
|
const systemPromptCustomizationPromise = logger.time("loadSystemPromptFiles", loadSystemPromptFiles, {
|
|
602
621
|
cwd: resolvedCwd,
|
|
622
|
+
agentDir: options.agentDir,
|
|
623
|
+
profileAuthority: options.profileAuthority,
|
|
603
624
|
});
|
|
604
625
|
const contextFilesPromise = providedContextFiles
|
|
605
626
|
? Promise.resolve({ contextFiles: providedContextFiles, warnings: [] })
|
|
606
|
-
: logger.time("loadProjectContextFiles", loadProjectContextFilesResult, {
|
|
627
|
+
: logger.time("loadProjectContextFiles", loadProjectContextFilesResult, {
|
|
628
|
+
cwd: resolvedCwd,
|
|
629
|
+
agentDir: options.agentDir,
|
|
630
|
+
profileAuthority: options.profileAuthority,
|
|
631
|
+
});
|
|
607
632
|
const workspaceTreePromise =
|
|
608
633
|
providedWorkspaceTree !== undefined
|
|
609
634
|
? Promise.resolve(providedWorkspaceTree)
|
|
@@ -710,6 +735,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
710
735
|
tools: toolNames,
|
|
711
736
|
toolInfo,
|
|
712
737
|
repeatToolDescriptions,
|
|
738
|
+
reasoningLanguageEnglish: reasoningLanguage === "english",
|
|
713
739
|
toolRefs,
|
|
714
740
|
environment,
|
|
715
741
|
contextFiles: sanitizedContextFiles,
|
package/src/task/executor.ts
CHANGED
|
@@ -281,6 +281,9 @@ export interface ExecutorOptions {
|
|
|
281
281
|
authStorage?: AuthStorage;
|
|
282
282
|
modelRegistry?: ModelRegistry;
|
|
283
283
|
settings?: Settings;
|
|
284
|
+
/** Parent session's selected agent directory and immutable resolver-owned authority. */
|
|
285
|
+
agentDir?: string;
|
|
286
|
+
profileAuthority?: "default" | "custom";
|
|
284
287
|
/** Parent session's registry; shared by child sessions for IRC routing and roster visibility. */
|
|
285
288
|
agentRegistry?: AgentRegistry;
|
|
286
289
|
/**
|
|
@@ -300,7 +303,8 @@ export interface ExecutorOptions {
|
|
|
300
303
|
managedPersistence?: ManagedTaskPersistence;
|
|
301
304
|
/**
|
|
302
305
|
* The parent session's ENDPOINT-owned AsyncJobManager (resolved by the
|
|
303
|
-
* TaskTool via
|
|
306
|
+
* TaskTool via its opaque async endpoint accessor, then the process-global
|
|
307
|
+
* fallback). Model metadata and
|
|
304
308
|
* live-handle state for THIS subagent are recorded in the SAME manager the
|
|
305
309
|
* task job runs in — with concurrent top-level sessions the process-global
|
|
306
310
|
* instance belongs to a different session and would surface this subagent
|
|
@@ -1951,6 +1955,8 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1951
1955
|
const { session } = await awaitAbortable(
|
|
1952
1956
|
canonicalSdk.createAgentSession({
|
|
1953
1957
|
cwd: worktree ?? cwd,
|
|
1958
|
+
agentDir: options.agentDir,
|
|
1959
|
+
profileAuthority: options.profileAuthority,
|
|
1954
1960
|
authStorage,
|
|
1955
1961
|
modelRegistry,
|
|
1956
1962
|
settings: subagentSettings,
|
package/src/task/index.ts
CHANGED
|
@@ -375,6 +375,14 @@ function createTaskModeError(text: string): AgentToolResult<TaskToolDetails> {
|
|
|
375
375
|
};
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
/** Remove only strict-provider blank placeholders for the optional caller-authored schema. */
|
|
379
|
+
function normalizeTaskAdmissionParams(params: TaskParams): TaskParams {
|
|
380
|
+
if (typeof params.schema !== "string" || params.schema.trim() !== "") return params;
|
|
381
|
+
const normalized = { ...params };
|
|
382
|
+
delete normalized.schema;
|
|
383
|
+
return normalized;
|
|
384
|
+
}
|
|
385
|
+
|
|
378
386
|
function validateTaskModeParams(simpleMode: TaskSimpleMode, params: TaskParams): string | undefined {
|
|
379
387
|
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
|
|
380
388
|
const disallowedFields: string[] = [];
|
|
@@ -888,7 +896,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
888
896
|
* thread P1).
|
|
889
897
|
*/
|
|
890
898
|
#resolveOwnedJobManager(): AsyncJobManager | undefined {
|
|
891
|
-
const endpointId = this.session.getSessionId?.() ?? undefined;
|
|
899
|
+
const endpointId = this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined;
|
|
892
900
|
return (
|
|
893
901
|
this.session.getAsyncJobManager?.() ?? AsyncJobManager.forEndpoint(endpointId) ?? AsyncJobManager.instance()
|
|
894
902
|
);
|
|
@@ -900,7 +908,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
900
908
|
signal?: AbortSignal,
|
|
901
909
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
902
910
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
903
|
-
const params = rawParams as TaskParams;
|
|
911
|
+
const params = normalizeTaskAdmissionParams(rawParams as TaskParams);
|
|
904
912
|
const simpleMode = this.#getTaskSimpleMode();
|
|
905
913
|
const validationError = validateTaskModeParams(simpleMode, params);
|
|
906
914
|
if (validationError) {
|
|
@@ -1245,7 +1253,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1245
1253
|
manager,
|
|
1246
1254
|
resumeToolCallId ?? descriptor.toolCallId,
|
|
1247
1255
|
resumeJobId,
|
|
1248
|
-
this.session.getSessionId?.() ?? undefined,
|
|
1256
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
1249
1257
|
);
|
|
1250
1258
|
return resumeJobId;
|
|
1251
1259
|
};
|
|
@@ -1477,7 +1485,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1477
1485
|
},
|
|
1478
1486
|
},
|
|
1479
1487
|
);
|
|
1480
|
-
registerOwnedIfLineaged(
|
|
1488
|
+
registerOwnedIfLineaged(
|
|
1489
|
+
manager,
|
|
1490
|
+
_toolCallId,
|
|
1491
|
+
jobId,
|
|
1492
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
1493
|
+
);
|
|
1481
1494
|
startedJobs.push({ jobId, taskId: taskItem.id });
|
|
1482
1495
|
if (typeof manager.registerResumeDescriptor === "function") {
|
|
1483
1496
|
manager.registerResumeDescriptor(
|
|
@@ -2324,6 +2337,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
2324
2337
|
modelRegistry: this.session.modelRegistry,
|
|
2325
2338
|
agentRegistry: this.session.agentRegistry,
|
|
2326
2339
|
settings: this.session.settings,
|
|
2340
|
+
agentDir: this.session.getSessionAgentDir?.() ?? this.session.settings.getAgentDir(),
|
|
2341
|
+
profileAuthority: this.session.profileAuthority,
|
|
2327
2342
|
inheritedServiceTier: this.session.serviceTier,
|
|
2328
2343
|
contextFiles,
|
|
2329
2344
|
skills: availableSkills,
|
|
@@ -2408,6 +2423,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
2408
2423
|
modelRegistry: this.session.modelRegistry,
|
|
2409
2424
|
agentRegistry: this.session.agentRegistry,
|
|
2410
2425
|
settings: this.session.settings,
|
|
2426
|
+
agentDir: this.session.getSessionAgentDir?.() ?? this.session.settings.getAgentDir(),
|
|
2427
|
+
profileAuthority: this.session.profileAuthority,
|
|
2411
2428
|
inheritedServiceTier: this.session.serviceTier,
|
|
2412
2429
|
contextFiles,
|
|
2413
2430
|
skills: availableSkills,
|
package/src/tools/bash.ts
CHANGED
|
@@ -103,6 +103,7 @@ const BASH_ERROR_MAX_BYTES = 4096;
|
|
|
103
103
|
const ARTIFACT_SAVE_DIAGNOSTIC_MAX_BYTES = 256;
|
|
104
104
|
const BASH_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
105
105
|
const MASTER_CAPABILITY_ENV = "GJC_MASTER_CAPABILITY";
|
|
106
|
+
const MASTER_OWNER_SESSION_ENV = "GJC_MASTER_OWNER_SESSION_ID";
|
|
106
107
|
const DEFAULT_AUTO_BACKGROUND_THRESHOLD_MS = 60_000;
|
|
107
108
|
const ACP_RELEASE_TIMEOUT_MS = 1_000;
|
|
108
109
|
const READ_ONLY_BASH_ENV: Record<string, string> = {
|
|
@@ -186,8 +187,9 @@ function artifactReferenceIsReachable(text: string, result: BashResult | BashInt
|
|
|
186
187
|
);
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
function suffixPrefixOverlap(source: string, target: string): number {
|
|
190
|
+
export function suffixPrefixOverlap(source: string, target: string): number {
|
|
190
191
|
if (source.length === 0 || target.length === 0) return 0;
|
|
192
|
+
if (target.startsWith(source)) return source.length;
|
|
191
193
|
const prefix = new Uint32Array(target.length);
|
|
192
194
|
for (let index = 1; index < target.length; index += 1) {
|
|
193
195
|
let length = prefix[index - 1] ?? 0;
|
|
@@ -244,10 +246,17 @@ function failureStatusCause(
|
|
|
244
246
|
if (result.cancelled) return leadingStatus ?? lastStatus ?? "Command cancelled";
|
|
245
247
|
if (isInteractiveResult(result) && result.timedOut) return lastStatus ?? "Command timed out";
|
|
246
248
|
if (result.exitCode === undefined) return "Command failed: missing exit status";
|
|
247
|
-
if (result.exitCode !== 0) return
|
|
249
|
+
if (result.exitCode !== 0) return nonZeroExitCause(result);
|
|
248
250
|
return undefined;
|
|
249
251
|
}
|
|
250
252
|
|
|
253
|
+
function nonZeroExitCause(result: BashResult | BashInteractiveResult): string {
|
|
254
|
+
if ("signal" in result && result.signal) {
|
|
255
|
+
return `Command terminated by ${result.signal} (exit code ${result.exitCode})`;
|
|
256
|
+
}
|
|
257
|
+
return `Command exited with code ${result.exitCode}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
function removeTrailingFailureCause(text: string, cause: string | undefined): string {
|
|
252
261
|
if (!cause) return text;
|
|
253
262
|
for (const suffix of [`\n\n${cause}`, `\n${cause}`, cause]) {
|
|
@@ -717,7 +726,12 @@ export function masterCommandEnvOverrides(
|
|
|
717
726
|
directMasterSpawn: boolean,
|
|
718
727
|
): Record<string, string> {
|
|
719
728
|
if (directMasterSpawn) return {};
|
|
720
|
-
|
|
729
|
+
// The master capability is single-use spawn authority and the owner id is
|
|
730
|
+
// spawn-lineage identity: neither is ambient state for ordinary Bash or a
|
|
731
|
+
// chained/pipelined descendant (issue #5374).
|
|
732
|
+
return Object.fromEntries(
|
|
733
|
+
Object.entries(env ?? {}).filter(([key]) => key !== MASTER_CAPABILITY_ENV && key !== MASTER_OWNER_SESSION_ENV),
|
|
734
|
+
);
|
|
721
735
|
}
|
|
722
736
|
|
|
723
737
|
function escapeBashEnvValueForDisplay(value: string): string {
|
|
@@ -896,9 +910,8 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
896
910
|
throw new ToolError(formatBashFailureMessage(result, `${outputText}\n\nCommand failed: missing exit status`));
|
|
897
911
|
}
|
|
898
912
|
if (result.exitCode !== 0) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
);
|
|
913
|
+
const cause = nonZeroExitCause(result);
|
|
914
|
+
throw new ToolError(formatBashFailureMessage(result, `${outputText}\n\n${cause}`));
|
|
902
915
|
}
|
|
903
916
|
return outputText;
|
|
904
917
|
}
|
|
@@ -1107,7 +1120,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1107
1120
|
manager.failNow(jobId, jobGeneration, "Bash job owner was torn down.");
|
|
1108
1121
|
});
|
|
1109
1122
|
void completion.promise.finally(unregisterOwnerCleanup);
|
|
1110
|
-
registerOwnedIfLineaged(
|
|
1123
|
+
registerOwnedIfLineaged(
|
|
1124
|
+
manager,
|
|
1125
|
+
options.toolCallId,
|
|
1126
|
+
jobId,
|
|
1127
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
1128
|
+
);
|
|
1111
1129
|
|
|
1112
1130
|
return {
|
|
1113
1131
|
jobId,
|
|
@@ -1132,7 +1150,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1132
1150
|
* running (review thread P1).
|
|
1133
1151
|
*/
|
|
1134
1152
|
#resolveOwnedJobManager(): AsyncJobManager | undefined {
|
|
1135
|
-
const endpointId = this.session.getSessionId?.() ?? undefined;
|
|
1153
|
+
const endpointId = this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined;
|
|
1136
1154
|
if (this.session.getAsyncJobManager) return this.session.getAsyncJobManager();
|
|
1137
1155
|
return AsyncJobManager.forEndpoint(endpointId) ?? AsyncJobManager.instance();
|
|
1138
1156
|
}
|
|
@@ -1433,7 +1451,10 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1433
1451
|
// command that contained shell syntax into a privileged one.
|
|
1434
1452
|
const directMasterSpawn = isStrictDirectSdkSpawnCommand(rawCommand) && isStrictDirectSdkSpawnCommand(command);
|
|
1435
1453
|
const masterCapability = directMasterSpawn ? this.session.getMasterBashCapability?.() : undefined;
|
|
1436
|
-
|
|
1454
|
+
// Master ownership is lineage identity, not session identity: it travels
|
|
1455
|
+
// under its own variable on every bash env so a master-owned child can
|
|
1456
|
+
// still read its OWN id from GJC_SESSION_ID (issue #5374).
|
|
1457
|
+
const masterOwnerSessionId = this.session.getMasterOwnerSessionId?.();
|
|
1437
1458
|
const commandEnvOverrides = masterCommandEnvOverrides(expandedEnv, directMasterSpawn);
|
|
1438
1459
|
const resolvedEnv = {
|
|
1439
1460
|
...buildGjcRuntimeSessionEnv({
|
|
@@ -1446,7 +1467,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1446
1467
|
: {}),
|
|
1447
1468
|
...commandEnvOverrides,
|
|
1448
1469
|
...(masterCapability ? { [MASTER_CAPABILITY_ENV]: masterCapability } : {}),
|
|
1449
|
-
...(masterOwnerSessionId ? {
|
|
1470
|
+
...(masterOwnerSessionId ? { [MASTER_OWNER_SESSION_ENV]: masterOwnerSessionId } : {}),
|
|
1450
1471
|
...(this.session.bashRestrictionProfile === "read-only" ? READ_ONLY_BASH_ENV : {}),
|
|
1451
1472
|
...(allowedPrefixes && allowedPrefixes.length > 0 ? { [GJC_RESTRICTED_ROLE_AGENT_BASH_ENV]: "1" } : {}),
|
|
1452
1473
|
};
|
|
@@ -1498,8 +1519,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1498
1519
|
resolvedEnv: Record<string, string> | undefined,
|
|
1499
1520
|
): Promise<BashResult> {
|
|
1500
1521
|
const masterCapability = resolvedEnv?.[MASTER_CAPABILITY_ENV];
|
|
1522
|
+
const ownerSessionId = resolvedEnv?.[MASTER_OWNER_SESSION_ENV];
|
|
1501
1523
|
const sessionAgentDir = resolvedEnv?.GJC_CODING_AGENT_DIR;
|
|
1502
|
-
if (masterCapability === undefined || sessionAgentDir === undefined)
|
|
1524
|
+
if (masterCapability === undefined || ownerSessionId === undefined || sessionAgentDir === undefined)
|
|
1503
1525
|
throw new ToolError("Master spawn authority context is unavailable.");
|
|
1504
1526
|
const args = parseDirectSdkSpawnArgs(command);
|
|
1505
1527
|
args.agentDir = sessionAgentDir;
|
|
@@ -1507,8 +1529,8 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1507
1529
|
const request = runSdkSpawn(args, {
|
|
1508
1530
|
env: {
|
|
1509
1531
|
...process.env,
|
|
1510
|
-
|
|
1511
|
-
|
|
1532
|
+
[MASTER_CAPABILITY_ENV]: masterCapability,
|
|
1533
|
+
[MASTER_OWNER_SESSION_ENV]: ownerSessionId,
|
|
1512
1534
|
GJC_CODING_AGENT_DIR: sessionAgentDir,
|
|
1513
1535
|
},
|
|
1514
1536
|
});
|
|
@@ -1681,7 +1703,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1681
1703
|
// Monitor jobs are exact owned background work of the turn that started
|
|
1682
1704
|
// them: register the five-tuple so scope:"owned" terminal abort stops the
|
|
1683
1705
|
// monitor too (review thread P2).
|
|
1684
|
-
registerOwnedIfLineaged(
|
|
1706
|
+
registerOwnedIfLineaged(
|
|
1707
|
+
manager,
|
|
1708
|
+
opts.toolCallId,
|
|
1709
|
+
jobId,
|
|
1710
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
1711
|
+
);
|
|
1685
1712
|
currentJobId = jobId;
|
|
1686
1713
|
return { jobId, label, commandCwd: prepared.commandCwd };
|
|
1687
1714
|
}
|
|
@@ -1883,19 +1910,31 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1883
1910
|
}
|
|
1884
1911
|
if (waitResult.kind === "completed") {
|
|
1885
1912
|
ownedManager.acknowledgeDeliveries([job.jobId]);
|
|
1886
|
-
unregisterForegroundOwnedBash(
|
|
1913
|
+
unregisterForegroundOwnedBash(
|
|
1914
|
+
ownedManager,
|
|
1915
|
+
job.jobId,
|
|
1916
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? "local",
|
|
1917
|
+
);
|
|
1887
1918
|
return waitResult.result;
|
|
1888
1919
|
}
|
|
1889
1920
|
if (waitResult.kind === "failed") {
|
|
1890
1921
|
ownedManager.acknowledgeDeliveries([job.jobId]);
|
|
1891
|
-
unregisterForegroundOwnedBash(
|
|
1922
|
+
unregisterForegroundOwnedBash(
|
|
1923
|
+
ownedManager,
|
|
1924
|
+
job.jobId,
|
|
1925
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? "local",
|
|
1926
|
+
);
|
|
1892
1927
|
throw waitResult.error;
|
|
1893
1928
|
}
|
|
1894
1929
|
if (waitResult.kind === "aborted") {
|
|
1895
1930
|
ownedManager.cancel(job.jobId);
|
|
1896
1931
|
const terminal = await job.completion;
|
|
1897
1932
|
ownedManager.acknowledgeDeliveries([job.jobId]);
|
|
1898
|
-
unregisterForegroundOwnedBash(
|
|
1933
|
+
unregisterForegroundOwnedBash(
|
|
1934
|
+
ownedManager,
|
|
1935
|
+
job.jobId,
|
|
1936
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? "local",
|
|
1937
|
+
);
|
|
1899
1938
|
if (terminal.kind === "failed") {
|
|
1900
1939
|
throw new ToolAbortError(
|
|
1901
1940
|
formatManagedAbortFailure(terminal.error, terminal.result, job.getLatestText()),
|
|
@@ -2357,7 +2396,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2357
2396
|
// already ran above, so exactly ONE remote handle exists and is retained
|
|
2358
2397
|
// across the fold.
|
|
2359
2398
|
const bridgeManager = ownedManager;
|
|
2360
|
-
const bridgeEndpointId = this.session.getSessionId?.() ?? "local";
|
|
2399
|
+
const bridgeEndpointId = this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? "local";
|
|
2361
2400
|
const bridgeLabel = command.length > 120 ? `${command.slice(0, 117)}...` : command;
|
|
2362
2401
|
const bridgeCompletion = Promise.withResolvers<ManagedBashJobCompletion>();
|
|
2363
2402
|
let bridgeForegroundSettled = false;
|
|
@@ -2621,7 +2660,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2621
2660
|
throw error;
|
|
2622
2661
|
}
|
|
2623
2662
|
const ptyGeneration = ptyManager.getJob(ptyJobId)?.generation ?? ptyJobId;
|
|
2624
|
-
registerOwnedIfLineaged(
|
|
2663
|
+
registerOwnedIfLineaged(
|
|
2664
|
+
ptyManager,
|
|
2665
|
+
toolCallId,
|
|
2666
|
+
ptyJobId,
|
|
2667
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
2668
|
+
);
|
|
2625
2669
|
const unregisterPtyOwnerCleanup = ptyManager.registerOwnerCleanup(ptyOwnerId, () => {
|
|
2626
2670
|
killPtyOnce();
|
|
2627
2671
|
ptyManager.failNow(ptyJobId, ptyGeneration, ownerTeardownText());
|
package/src/tools/cron.ts
CHANGED
|
@@ -592,7 +592,7 @@ export class CronTool implements AgentTool<typeof cronSchema, CronToolDetails> {
|
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
#resolveOwnedJobManager(): AsyncJobManager | undefined {
|
|
595
|
-
const endpointId = this.session.getSessionId?.() ?? undefined;
|
|
595
|
+
const endpointId = this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined;
|
|
596
596
|
return (
|
|
597
597
|
this.session.getAsyncJobManager?.() ?? AsyncJobManager.forEndpoint(endpointId) ?? AsyncJobManager.instance()
|
|
598
598
|
);
|
package/src/tools/fetch.ts
CHANGED
|
@@ -1167,13 +1167,36 @@ interface ReadUrlCacheEntry {
|
|
|
1167
1167
|
wrappedPreambleChars?: number;
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
+
// Entries hold whole-page outputs and image payloads, so this map must stay
|
|
1171
|
+
// bounded in long-lived hosts (TUI, SDK broker). Each fetch caches one entry
|
|
1172
|
+
// under up to two keys (requested + final URL). FIFO eviction is sufficient:
|
|
1173
|
+
// a miss simply refetches, so correctness never depends on a hit.
|
|
1174
|
+
export const READ_URL_CACHE_MAX_KEYS = 64;
|
|
1175
|
+
|
|
1170
1176
|
const readUrlCache = new Map<string, ReadUrlCacheEntry>();
|
|
1171
1177
|
|
|
1172
1178
|
function getReadUrlCacheKey(session: ToolSession, requestedUrl: string, raw: boolean): string {
|
|
1173
|
-
const
|
|
1179
|
+
const sessionFile = session.getSessionFile();
|
|
1180
|
+
const sessionId = session.getSessionId?.();
|
|
1181
|
+
const scope = sessionFile ? `file:${sessionFile}` : sessionId ? `session:${sessionId}` : `cwd:${session.cwd}`;
|
|
1174
1182
|
return `${scope}::${raw ? "raw" : "rendered"}::${normalizeUrl(requestedUrl)}`;
|
|
1175
1183
|
}
|
|
1176
1184
|
|
|
1185
|
+
/** Test-only seam for deterministic module-global cache assertions. */
|
|
1186
|
+
export const readUrlCacheTestHooks = {
|
|
1187
|
+
get size(): number {
|
|
1188
|
+
return readUrlCache.size;
|
|
1189
|
+
},
|
|
1190
|
+
get retainedOutputChars(): number {
|
|
1191
|
+
let total = 0;
|
|
1192
|
+
for (const entry of new Set(readUrlCache.values())) total += entry.output.length;
|
|
1193
|
+
return total;
|
|
1194
|
+
},
|
|
1195
|
+
reset(): void {
|
|
1196
|
+
readUrlCache.clear();
|
|
1197
|
+
},
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1177
1200
|
async function readArtifactOutput(session: ToolSession, artifactId: string): Promise<string | null> {
|
|
1178
1201
|
const artifactsDir = session.getArtifactsDir?.();
|
|
1179
1202
|
if (!artifactsDir) return null;
|
|
@@ -1224,6 +1247,11 @@ async function ensureReadUrlCacheArtifact(session: ToolSession, entry: ReadUrlCa
|
|
|
1224
1247
|
function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: boolean, entry: ReadUrlCacheEntry): void {
|
|
1225
1248
|
readUrlCache.set(getReadUrlCacheKey(session, requestedUrl, raw), entry);
|
|
1226
1249
|
readUrlCache.set(getReadUrlCacheKey(session, entry.details.finalUrl, raw), entry);
|
|
1250
|
+
while (readUrlCache.size > READ_URL_CACHE_MAX_KEYS) {
|
|
1251
|
+
const oldest = readUrlCache.keys().next().value;
|
|
1252
|
+
if (oldest === undefined) break;
|
|
1253
|
+
readUrlCache.delete(oldest);
|
|
1254
|
+
}
|
|
1227
1255
|
}
|
|
1228
1256
|
|
|
1229
1257
|
async function buildReadUrlCacheEntry(
|
package/src/tools/gh.ts
CHANGED
|
@@ -2738,7 +2738,14 @@ export async function getOrFetchIssue(options: IssueViewLookupOptions): Promise<
|
|
|
2738
2738
|
}
|
|
2739
2739
|
}
|
|
2740
2740
|
|
|
2741
|
-
const doFetch = () =>
|
|
2741
|
+
const doFetch = (signal?: AbortSignal) =>
|
|
2742
|
+
fetchIssueViewFresh(
|
|
2743
|
+
options.cwd,
|
|
2744
|
+
repo,
|
|
2745
|
+
identifier,
|
|
2746
|
+
includeComments,
|
|
2747
|
+
signal && options.signal ? AbortSignal.any([signal, options.signal]) : (signal ?? options.signal),
|
|
2748
|
+
);
|
|
2742
2749
|
|
|
2743
2750
|
if (!repo || cacheNumber === undefined) {
|
|
2744
2751
|
const fresh = await doFetch();
|
|
@@ -2752,6 +2759,7 @@ export async function getOrFetchIssue(options: IssueViewLookupOptions): Promise<
|
|
|
2752
2759
|
includeComments,
|
|
2753
2760
|
settings: options.settings,
|
|
2754
2761
|
authKey,
|
|
2762
|
+
signal: options.signal,
|
|
2755
2763
|
fetchFresh: doFetch,
|
|
2756
2764
|
});
|
|
2757
2765
|
return {
|
|
@@ -2771,7 +2779,14 @@ export async function getOrFetchIssue(options: IssueViewLookupOptions): Promise<
|
|
|
2771
2779
|
export async function getOrFetchPr(options: PrViewLookupOptions): Promise<ViewLookupResult<GhPrViewData>> {
|
|
2772
2780
|
const includeComments = options.includeComments ?? true;
|
|
2773
2781
|
const authKey = options.cacheAuthKey === undefined ? (resolveGithubCacheAuthKey() ?? null) : options.cacheAuthKey;
|
|
2774
|
-
const doFetch = () =>
|
|
2782
|
+
const doFetch = (signal?: AbortSignal) =>
|
|
2783
|
+
fetchPrViewFresh(
|
|
2784
|
+
options.cwd,
|
|
2785
|
+
options.repo,
|
|
2786
|
+
options.number,
|
|
2787
|
+
includeComments,
|
|
2788
|
+
signal && options.signal ? AbortSignal.any([signal, options.signal]) : (signal ?? options.signal),
|
|
2789
|
+
);
|
|
2775
2790
|
const lookup = await getOrFetchView<GhPrViewData>({
|
|
2776
2791
|
repo: options.repo,
|
|
2777
2792
|
kind: "pr",
|
|
@@ -2779,6 +2794,7 @@ export async function getOrFetchPr(options: PrViewLookupOptions): Promise<ViewLo
|
|
|
2779
2794
|
includeComments,
|
|
2780
2795
|
settings: options.settings,
|
|
2781
2796
|
authKey,
|
|
2797
|
+
signal: options.signal,
|
|
2782
2798
|
fetchFresh: doFetch,
|
|
2783
2799
|
});
|
|
2784
2800
|
return {
|
|
@@ -3121,7 +3137,13 @@ async function fetchPrDiffFresh(
|
|
|
3121
3137
|
*/
|
|
3122
3138
|
export async function getOrFetchPrDiff(options: PrDiffLookupOptions): Promise<ViewLookupResult<PrDiffPayload>> {
|
|
3123
3139
|
const authKey = options.cacheAuthKey === undefined ? (resolveGithubCacheAuthKey() ?? null) : options.cacheAuthKey;
|
|
3124
|
-
const doFetch = () =>
|
|
3140
|
+
const doFetch = (signal?: AbortSignal) =>
|
|
3141
|
+
fetchPrDiffFresh(
|
|
3142
|
+
options.cwd,
|
|
3143
|
+
options.repo,
|
|
3144
|
+
options.number,
|
|
3145
|
+
signal && options.signal ? AbortSignal.any([signal, options.signal]) : (signal ?? options.signal),
|
|
3146
|
+
);
|
|
3125
3147
|
const lookup = await getOrFetchView<PrDiffPayload>({
|
|
3126
3148
|
repo: options.repo,
|
|
3127
3149
|
kind: "pr-diff",
|
|
@@ -3129,6 +3151,7 @@ export async function getOrFetchPrDiff(options: PrDiffLookupOptions): Promise<Vi
|
|
|
3129
3151
|
includeComments: false,
|
|
3130
3152
|
settings: options.settings,
|
|
3131
3153
|
authKey,
|
|
3154
|
+
signal: options.signal,
|
|
3132
3155
|
fetchFresh: doFetch,
|
|
3133
3156
|
});
|
|
3134
3157
|
return {
|
|
@@ -152,6 +152,26 @@ function evictExpired(db: Database, hardTtlMs: number): void {
|
|
|
152
152
|
*/
|
|
153
153
|
const SWEEP_INTERVAL_MS = 60_000;
|
|
154
154
|
let lastSweepAt = 0;
|
|
155
|
+
const backgroundRefreshes = new Map<string, { generation: number; controller: AbortController }>();
|
|
156
|
+
let backgroundRefreshGeneration = 0;
|
|
157
|
+
const BACKGROUND_REFRESH_TIMEOUT_MS = 30_000;
|
|
158
|
+
|
|
159
|
+
function cacheIdentityKey(
|
|
160
|
+
authKey: string,
|
|
161
|
+
repo: string,
|
|
162
|
+
kind: CacheKind,
|
|
163
|
+
number: number,
|
|
164
|
+
includeComments: boolean,
|
|
165
|
+
): string {
|
|
166
|
+
return JSON.stringify([authKey, normalizeRepo(repo), kind, number, includeComments]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function retireBackgroundRefresh(key: string): void {
|
|
170
|
+
const entry = backgroundRefreshes.get(key);
|
|
171
|
+
if (!entry) return;
|
|
172
|
+
entry.controller.abort();
|
|
173
|
+
backgroundRefreshes.delete(key);
|
|
174
|
+
}
|
|
155
175
|
|
|
156
176
|
function sweepIfDue(hardTtlMs: number): void {
|
|
157
177
|
const now = Date.now();
|
|
@@ -296,6 +316,12 @@ export function invalidate(
|
|
|
296
316
|
includeComments?: boolean,
|
|
297
317
|
authKey: string = DEFAULT_CACHE_AUTH_KEY,
|
|
298
318
|
): void {
|
|
319
|
+
if (includeComments === undefined) {
|
|
320
|
+
retireBackgroundRefresh(cacheIdentityKey(authKey, repo, kind, number, false));
|
|
321
|
+
retireBackgroundRefresh(cacheIdentityKey(authKey, repo, kind, number, true));
|
|
322
|
+
} else {
|
|
323
|
+
retireBackgroundRefresh(cacheIdentityKey(authKey, repo, kind, number, includeComments));
|
|
324
|
+
}
|
|
299
325
|
const db = openDb();
|
|
300
326
|
if (!db) return;
|
|
301
327
|
try {
|
|
@@ -342,6 +368,9 @@ export function resetForTests(): void {
|
|
|
342
368
|
cachedDb = null;
|
|
343
369
|
openAttempted = false;
|
|
344
370
|
lastSweepAt = 0;
|
|
371
|
+
for (const { controller } of backgroundRefreshes.values()) controller.abort();
|
|
372
|
+
backgroundRefreshes.clear();
|
|
373
|
+
backgroundRefreshGeneration += 1;
|
|
345
374
|
}
|
|
346
375
|
|
|
347
376
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -365,9 +394,10 @@ export interface CacheLookupOptions<T> {
|
|
|
365
394
|
* must bypass persistent cache reads/writes.
|
|
366
395
|
*/
|
|
367
396
|
authKey?: string | null;
|
|
368
|
-
fetchFresh: () => Promise<FreshResult<T>>;
|
|
397
|
+
fetchFresh: (signal?: AbortSignal) => Promise<FreshResult<T>>;
|
|
369
398
|
settings?: Settings | undefined;
|
|
370
399
|
now?: number;
|
|
400
|
+
signal?: AbortSignal;
|
|
371
401
|
}
|
|
372
402
|
|
|
373
403
|
export type CacheStatus = "miss" | "fresh" | "stale" | "disabled";
|
|
@@ -448,22 +478,41 @@ function scheduleBackgroundRefresh<T>(
|
|
|
448
478
|
kind: CacheKind,
|
|
449
479
|
number: number,
|
|
450
480
|
includeComments: boolean,
|
|
451
|
-
fetchFresh: () => Promise<FreshResult<T>>,
|
|
481
|
+
fetchFresh: (signal?: AbortSignal) => Promise<FreshResult<T>>,
|
|
452
482
|
): void {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
483
|
+
const key = cacheIdentityKey(authKey, repo, kind, number, includeComments);
|
|
484
|
+
if (backgroundRefreshes.has(key)) return;
|
|
485
|
+
const generation = backgroundRefreshGeneration;
|
|
486
|
+
const controller = new AbortController();
|
|
487
|
+
const entry = { generation, controller };
|
|
488
|
+
backgroundRefreshes.set(key, entry);
|
|
489
|
+
queueMicrotask(async () => {
|
|
490
|
+
const timeoutHandle = setTimeout(() => {
|
|
491
|
+
controller.abort(new Error("GitHub background refresh timed out"));
|
|
492
|
+
if (backgroundRefreshes.get(key) === entry) backgroundRefreshes.delete(key);
|
|
493
|
+
logger.debug("github cache: background refresh timed out", { repo, kind, number });
|
|
494
|
+
}, BACKGROUND_REFRESH_TIMEOUT_MS);
|
|
495
|
+
try {
|
|
496
|
+
const fresh = await fetchFresh(controller.signal);
|
|
497
|
+
if (
|
|
498
|
+
!controller.signal.aborted &&
|
|
499
|
+
backgroundRefreshes.get(key) === entry &&
|
|
500
|
+
backgroundRefreshGeneration === generation
|
|
501
|
+
) {
|
|
457
502
|
storeResult(authKey, repo, kind, number, includeComments, fresh, Date.now());
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
503
|
+
}
|
|
504
|
+
} catch (err) {
|
|
505
|
+
controller.abort();
|
|
506
|
+
logger.debug("github cache: background refresh failed", {
|
|
507
|
+
err: String(err),
|
|
508
|
+
repo,
|
|
509
|
+
kind,
|
|
510
|
+
number,
|
|
466
511
|
});
|
|
512
|
+
} finally {
|
|
513
|
+
clearTimeout(timeoutHandle);
|
|
514
|
+
if (backgroundRefreshes.get(key) === entry) backgroundRefreshes.delete(key);
|
|
515
|
+
}
|
|
467
516
|
});
|
|
468
517
|
}
|
|
469
518
|
|
|
@@ -473,9 +522,10 @@ export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise
|
|
|
473
522
|
const authKey = options.authKey === undefined ? DEFAULT_CACHE_AUTH_KEY : options.authKey;
|
|
474
523
|
|
|
475
524
|
if (!ttl.enabled || authKey === null) {
|
|
476
|
-
const fresh = await options.fetchFresh();
|
|
525
|
+
const fresh = await options.fetchFresh(options.signal);
|
|
477
526
|
return { ...fresh, status: "disabled", fetchedAt: now };
|
|
478
527
|
}
|
|
528
|
+
const cacheKey = cacheIdentityKey(authKey, options.repo, options.kind, options.number, options.includeComments);
|
|
479
529
|
|
|
480
530
|
// Enforce the *configured* hard TTL against on-disk rows. This is what
|
|
481
531
|
// makes `github.cache.hardTtlSec` a real retention cap rather than a soft
|
|
@@ -496,6 +546,7 @@ export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise
|
|
|
496
546
|
// Past hard TTL: drop the row eagerly so the on-disk exposure window
|
|
497
547
|
// is bounded even if `fetchFresh()` then fails (network down, gh
|
|
498
548
|
// auth lapse, etc.) and we never get to overwrite it.
|
|
549
|
+
retireBackgroundRefresh(cacheKey);
|
|
499
550
|
invalidate(options.repo, options.kind, options.number, options.includeComments, authKey);
|
|
500
551
|
} else if (age <= ttl.softMs) {
|
|
501
552
|
return {
|
|
@@ -524,8 +575,9 @@ export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise
|
|
|
524
575
|
}
|
|
525
576
|
}
|
|
526
577
|
|
|
527
|
-
const fresh = await options.fetchFresh();
|
|
578
|
+
const fresh = await options.fetchFresh(options.signal);
|
|
528
579
|
const fetchedAt = Date.now();
|
|
580
|
+
retireBackgroundRefresh(cacheKey);
|
|
529
581
|
storeResult(authKey, options.repo, options.kind, options.number, options.includeComments, fresh, fetchedAt);
|
|
530
582
|
return { ...fresh, status: "miss", fetchedAt };
|
|
531
583
|
}
|