@gajae-code/coding-agent 0.17.4 → 0.17.5
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 +73 -0
- package/dist/types/config/autorouting-tier-map.d.ts +1031 -3
- package/dist/types/config/file-lock.d.ts +4 -2
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
- package/dist/types/harness-control-plane/finalize.d.ts +14 -3
- package/dist/types/main.d.ts +1 -1
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +13 -6
- package/dist/types/sdk/broker/broker.d.ts +4 -2
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/identity.d.ts +2 -0
- package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
- package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +8 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +5 -1
- package/dist/types/sdk/prompt-deadline-flush.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +3 -3
- package/dist/types/session/agent-session.d.ts +7 -2
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tools/bash-interactive.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +10 -1
- package/dist/types/tools/subagent.d.ts +8 -1
- package/package.json +7 -7
- package/src/cli/public-command-registry.ts +8 -2
- package/src/cli/setup-cli.ts +1 -0
- package/src/cli/stats-cli.ts +11 -1
- package/src/config/autorouting-tier-map.ts +529 -3
- package/src/config/file-lock.ts +59 -14
- package/src/config/model-discovery-manager.ts +4 -4
- package/src/config/model-profile-activation.ts +83 -23
- package/src/config/model-registry.ts +72 -12
- package/src/config/model-resolver.ts +6 -1
- package/src/coordinator-mcp/question-state.ts +4 -1
- package/src/coordinator-mcp/server.ts +14 -6
- package/src/exec/bash-executor.ts +3 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
- package/src/gjc-runtime/state-writer.ts +317 -6
- package/src/harness-control-plane/control-endpoint.ts +24 -6
- package/src/harness-control-plane/finalize.ts +312 -25
- package/src/harness-control-plane/owner.ts +323 -89
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/main.ts +69 -19
- package/src/modes/acp/acp-agent.ts +581 -37
- package/src/modes/components/gjc-bundle-settings.ts +21 -1
- package/src/modes/components/tool-status-header.ts +72 -6
- package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
- package/src/runtime-mcp/loader.ts +6 -3
- package/src/runtime-mcp/manager.ts +177 -75
- package/src/sdk/broker/broker.ts +1057 -150
- package/src/sdk/broker/ensure.ts +21 -3
- package/src/sdk/broker/identity.ts +13 -0
- package/src/sdk/broker/managed-task-dag.ts +1927 -0
- package/src/sdk/broker/managed-task-verification.ts +642 -0
- package/src/sdk/broker/spawn-authority.ts +140 -7
- package/src/sdk/broker/startup-failure.ts +80 -1
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +22 -6
- package/src/sdk/bus/telegram-daemon-contract.ts +5 -1
- package/src/sdk/cli/session-cli.ts +18 -0
- package/src/sdk/host/session-runtime.test.ts +46 -0
- package/src/sdk/host/session-runtime.ts +13 -6
- package/src/sdk/lifecycle/service.ts +2 -0
- package/src/sdk/prompt-deadline-flush.ts +26 -4
- package/src/sdk/session.ts +587 -115
- package/src/session/agent-session.ts +40 -12
- package/src/session/internal/managed-session-scope.ts +10 -0
- package/src/session/internal/managed-session-storage.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/task/executor.ts +46 -17
- package/src/task/types.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -0
- package/src/tools/bash.ts +20 -0
- package/src/tools/fetch.ts +21 -5
- package/src/tools/subagent-render.ts +24 -1
- package/src/tools/subagent.ts +27 -1
- package/src/utils/mupdf.ts +11 -4
|
@@ -5941,8 +5941,13 @@ export class AgentSession {
|
|
|
5941
5941
|
if (next && MCPManager.instance() === undefined) MCPManager.setInstance(next);
|
|
5942
5942
|
}
|
|
5943
5943
|
|
|
5944
|
-
/** Swap named custom
|
|
5945
|
-
async replaceNamedCustomTools(
|
|
5944
|
+
/** Swap named custom tools and their optional mandatory MCP selection. */
|
|
5945
|
+
async replaceNamedCustomTools(
|
|
5946
|
+
previousNames: readonly string[],
|
|
5947
|
+
nextTools: readonly CustomTool[],
|
|
5948
|
+
options?: { mandatoryMCPToolNames?: readonly string[]; activateNewTools?: boolean },
|
|
5949
|
+
): Promise<void> {
|
|
5950
|
+
const previousSelectedMCPToolNames = this.getSelectedMCPToolNames();
|
|
5946
5951
|
const previous = new Set(previousNames);
|
|
5947
5952
|
const previousActive = this.getActiveToolNames();
|
|
5948
5953
|
for (const name of previous) this.#toolRegistry.delete(name);
|
|
@@ -5956,11 +5961,21 @@ export class AgentSession {
|
|
|
5956
5961
|
this.#toolRegistry.set(finalTool.name, finalTool);
|
|
5957
5962
|
added.push(finalTool.name);
|
|
5958
5963
|
}
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
+
if (options?.mandatoryMCPToolNames) {
|
|
5965
|
+
this.#mandatoryMCPToolNames = new Set(
|
|
5966
|
+
options.mandatoryMCPToolNames.map(name => name.toLowerCase()).filter(name => this.#toolRegistry.has(name)),
|
|
5967
|
+
);
|
|
5968
|
+
}
|
|
5969
|
+
this.#setDiscoverableMCPTools(this.#collectDiscoverableMCPToolsFromRegistry());
|
|
5970
|
+
await this.#applyActiveToolsByName(
|
|
5971
|
+
[
|
|
5972
|
+
...previousActive.filter(name => !previous.has(name)),
|
|
5973
|
+
...added.filter(
|
|
5974
|
+
name => previousActive.includes(name) || (options?.activateNewTools !== false && !previous.has(name)),
|
|
5975
|
+
),
|
|
5976
|
+
],
|
|
5977
|
+
{ previousSelectedMCPToolNames },
|
|
5978
|
+
);
|
|
5964
5979
|
}
|
|
5965
5980
|
|
|
5966
5981
|
/** Best-effort accessor for the active skill's `current_phase` field from
|
|
@@ -11884,6 +11899,7 @@ export class AgentSession {
|
|
|
11884
11899
|
options: {
|
|
11885
11900
|
selectedMCPToolNames?: string[];
|
|
11886
11901
|
activeMCPToolNames?: string[];
|
|
11902
|
+
mandatoryMCPToolNames?: string[];
|
|
11887
11903
|
persistMCPSelection?: boolean;
|
|
11888
11904
|
} = {},
|
|
11889
11905
|
): Promise<void> {
|
|
@@ -11918,6 +11934,11 @@ export class AgentSession {
|
|
|
11918
11934
|
}
|
|
11919
11935
|
|
|
11920
11936
|
this.#setDiscoverableMCPTools(this.#collectDiscoverableMCPToolsFromRegistry());
|
|
11937
|
+
if (options.mandatoryMCPToolNames !== undefined) {
|
|
11938
|
+
this.#mandatoryMCPToolNames = new Set(
|
|
11939
|
+
options.mandatoryMCPToolNames.map(name => name.toLowerCase()).filter(name => this.#toolRegistry.has(name)),
|
|
11940
|
+
);
|
|
11941
|
+
}
|
|
11921
11942
|
this.#pruneSelectedMCPToolNames();
|
|
11922
11943
|
const hasPersistedMCPToolSelection = this.buildDisplaySessionContext().hasPersistedMCPToolSelection;
|
|
11923
11944
|
if (options.selectedMCPToolNames) {
|
|
@@ -18209,6 +18230,7 @@ export class AgentSession {
|
|
|
18209
18230
|
reason?: TemporaryModelReason;
|
|
18210
18231
|
providerSessionScope?: TemporaryProviderSessionScope;
|
|
18211
18232
|
signal?: AbortSignal;
|
|
18233
|
+
onMutationStarted?: () => void;
|
|
18212
18234
|
},
|
|
18213
18235
|
// biome-ignore lint/suspicious/noConfusingVoidType: Existing session adapters return Promise<void>; a scope is optional.
|
|
18214
18236
|
): Promise<TemporaryProviderSessionScope | void> {
|
|
@@ -18226,6 +18248,7 @@ export class AgentSession {
|
|
|
18226
18248
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
18227
18249
|
}
|
|
18228
18250
|
if (suppliedScope && this.#temporaryProviderSessionScopes.at(-1)?.token !== suppliedScope) return;
|
|
18251
|
+
options?.onMutationStarted?.();
|
|
18229
18252
|
|
|
18230
18253
|
const isTemporaryOperation = options?.cause === undefined || options.cause === "temporary-operation";
|
|
18231
18254
|
const autoCreateScope = isTemporaryOperation && !suppliedScope;
|
|
@@ -18277,13 +18300,18 @@ export class AgentSession {
|
|
|
18277
18300
|
model: Model | undefined,
|
|
18278
18301
|
thinkingLevel: ThinkingLevel | undefined,
|
|
18279
18302
|
): Promise<void> {
|
|
18303
|
+
const previousEditMode = this.#resolveActiveEditMode();
|
|
18280
18304
|
if (model) {
|
|
18281
|
-
|
|
18282
|
-
|
|
18305
|
+
// Restoring a captured live model is compensation, not a new selection:
|
|
18306
|
+
// the old credential may have disappeared after the forward mutation.
|
|
18307
|
+
this.#setModelAuthoritatively(model, "rollback");
|
|
18308
|
+
this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, "temporary");
|
|
18309
|
+
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
18310
|
+
} else {
|
|
18311
|
+
this.#clearActiveRetryFallback();
|
|
18312
|
+
this.#setModelWithProviderSessionReset(undefined);
|
|
18313
|
+
this.#syncAppendOnlyContext(undefined);
|
|
18283
18314
|
}
|
|
18284
|
-
const previousEditMode = this.#resolveActiveEditMode();
|
|
18285
|
-
this.#clearActiveRetryFallback();
|
|
18286
|
-
this.#setModelWithProviderSessionReset(undefined);
|
|
18287
18315
|
this.setThinkingLevel(thinkingLevel);
|
|
18288
18316
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
18289
18317
|
}
|
|
@@ -1852,6 +1852,16 @@ function receiptMatches(
|
|
|
1852
1852
|
tree?: unknown;
|
|
1853
1853
|
};
|
|
1854
1854
|
};
|
|
1855
|
+
// A receipt is keyed by its source and names exactly one destination. The
|
|
1856
|
+
// resume listing probes every (legacy source, v2 destination) pair, so reject
|
|
1857
|
+
// on these cheap receipt fields before hashing both transcripts and walking
|
|
1858
|
+
// the artifact manifest; the full conjunction below would be false anyway.
|
|
1859
|
+
if (
|
|
1860
|
+
record.destination?.path !== destination.path ||
|
|
1861
|
+
record.source?.path !== source.path ||
|
|
1862
|
+
record.source?.sha256 !== source.identity.sha256
|
|
1863
|
+
)
|
|
1864
|
+
return false;
|
|
1855
1865
|
const exact = (
|
|
1856
1866
|
recorded: { dev?: unknown; ino?: unknown; size?: unknown; mtimeNs?: unknown } | undefined,
|
|
1857
1867
|
candidate: ManagedCandidate,
|
|
@@ -1178,11 +1178,40 @@ export function retainManagedDirectoryAuthority(
|
|
|
1178
1178
|
)
|
|
1179
1179
|
throw new Error("Managed root authority changed");
|
|
1180
1180
|
const relative = path.relative(root.canonicalPath, resolved).split(path.sep).join("/");
|
|
1181
|
-
|
|
1181
|
+
const rootRecovery = rootAuthority.recoveryReaperMetrics();
|
|
1182
|
+
const rootRecoveryUnhealthy = !rootRecovery.ok || Boolean(rootRecovery.code);
|
|
1183
|
+
if (rootRecoveryUnhealthy)
|
|
1184
|
+
logger.warn("Managed recovery sidecar reaping", {
|
|
1185
|
+
ok: rootRecovery.ok,
|
|
1186
|
+
code: rootRecovery.code,
|
|
1187
|
+
reapedCount: rootRecovery.totalReapedFiles,
|
|
1188
|
+
reapedBytes: rootRecovery.totalReapedBytes,
|
|
1189
|
+
failureCount: rootRecovery.totalFailures,
|
|
1190
|
+
scanLimited: rootRecovery.scanLimited,
|
|
1191
|
+
});
|
|
1192
|
+
const authority = rootAuthority.retainManagedDirectory(
|
|
1182
1193
|
relative,
|
|
1183
1194
|
canonicalFileId(named.dev).toString(),
|
|
1184
1195
|
canonicalFileId(named.ino).toString(),
|
|
1185
1196
|
);
|
|
1197
|
+
const recovery = authority.recoveryReaperMetrics();
|
|
1198
|
+
if (
|
|
1199
|
+
!rootRecoveryUnhealthy &&
|
|
1200
|
+
(!recovery.ok ||
|
|
1201
|
+
recovery.code ||
|
|
1202
|
+
recovery.totalReapedFiles !== "0" ||
|
|
1203
|
+
recovery.totalFailures !== "0" ||
|
|
1204
|
+
recovery.scanLimited)
|
|
1205
|
+
)
|
|
1206
|
+
logger.warn("Managed recovery sidecar reaping", {
|
|
1207
|
+
ok: recovery.ok,
|
|
1208
|
+
code: recovery.code,
|
|
1209
|
+
reapedCount: recovery.totalReapedFiles,
|
|
1210
|
+
reapedBytes: recovery.totalReapedBytes,
|
|
1211
|
+
failureCount: recovery.totalFailures,
|
|
1212
|
+
scanLimited: recovery.scanLimited,
|
|
1213
|
+
});
|
|
1214
|
+
return authority;
|
|
1186
1215
|
} finally {
|
|
1187
1216
|
rootAuthority.close();
|
|
1188
1217
|
}
|
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
{"id":"cline-pass","aliases":["clinepass","cline"],"name":"ClinePass","description":"ClinePass subscription models discovered from Cline's upstream models.dev catalog","compatibility":"openai","api":"openai-completions","providerId":"cline-pass","baseUrl":"https://api.cline.bot/api/v1","apiKeyEnv":"CLINE_API_KEY","discovery":{"type":"models-dev","modelsDevProvider":"cline-pass"},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning"}},
|
|
7
7
|
{"id":"commandcode-goat","aliases":["commandcode","command-code","goat"],"name":"Command Code GOAT","description":"Command Code GOAT coding-plan models discovered from its OpenAI-compatible Provider API","compatibility":"openai","api":"openai-completions","providerId":"commandcode-goat","baseUrl":"https://api.commandcode.ai/provider/v1","apiKeyEnv":"CMD_API_KEY","discovery":{"type":"openai-models-list","apiByModelPrefix":{"claude":"anthropic-messages","anthropic/claude":"anthropic-messages"}},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","reasoningContentField":"reasoning_content"}},
|
|
8
8
|
{"id":"litellm","aliases":["litellm-proxy"],"name":"LiteLLM Proxy","description":"OpenAI-compatible LiteLLM proxy endpoint (user-supplied base URL) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"litellm","parameterized":true,"apiKeyEnv":"LITELLM_API_KEY","discovery":{"type":"openai-models-list"}},
|
|
9
|
-
{"id":"openai-compatible-proxy","aliases":["openai-proxy","compatible-proxy","custom-proxy"],"name":"OpenAI-Compatible Proxy","description":"Generic OpenAI-compatible gateway (OpenRouter, OpenCode Go, custom) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"openai-compatible-proxy","parameterized":true,"apiKeyEnv":"OPENAI_COMPATIBLE_API_KEY","discovery":{"type":"openai-models-list"}}
|
|
9
|
+
{"id":"openai-compatible-proxy","aliases":["openai-proxy","compatible-proxy","custom-proxy"],"name":"OpenAI-Compatible Proxy","description":"Generic OpenAI-compatible gateway (OpenRouter, OpenCode Go, custom) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"openai-compatible-proxy","parameterized":true,"apiKeyEnv":"OPENAI_COMPATIBLE_API_KEY","discovery":{"type":"openai-models-list"}},
|
|
10
|
+
{"id":"ionet","aliases":["io-net","io-intelligence"],"name":"IO Intelligence (io.net)","description":"OpenAI-compatible IO Intelligence endpoint by io.net with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"ionet","baseUrl":"https://api.intelligence.io.solutions/api/v1","apiKeyEnv":"IONET_API_KEY","discovery":{"type":"openai-models-list"},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"supportsUsageInStreaming":false,"maxTokensField":"max_tokens","reasoningContentField":"reasoning_content","extraBody":{"tool_choice":"auto"}}}
|
|
10
11
|
]
|
|
@@ -286,7 +286,7 @@ function providerSetupUsage(): string {
|
|
|
286
286
|
return [
|
|
287
287
|
"Provider onboarding",
|
|
288
288
|
"Presets: /provider add --preset <id> [--force]",
|
|
289
|
-
"Aliases include minimax, zai, alibaba, cline, command-code, and
|
|
289
|
+
"Aliases include minimax, zai, alibaba, cline, command-code, goat, and ionet.",
|
|
290
290
|
`API providers: ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} [--force]`,
|
|
291
291
|
`Available presets:\n${formatProviderPresetList()}`,
|
|
292
292
|
"OAuth/subscription providers: /provider login [provider-id] or /login [provider-id]",
|
package/src/task/executor.ts
CHANGED
|
@@ -37,6 +37,7 @@ import { ModelRegistry } from "../config/model-registry";
|
|
|
37
37
|
import {
|
|
38
38
|
formatModelString,
|
|
39
39
|
isExplicitProviderModelOverride,
|
|
40
|
+
refreshMissingQualifiedModelProviders,
|
|
40
41
|
resolveModelOverrideWithAuthFallback,
|
|
41
42
|
} from "../config/model-resolver";
|
|
42
43
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
@@ -1418,6 +1419,7 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1418
1419
|
forwardSubagentEvent(event);
|
|
1419
1420
|
|
|
1420
1421
|
const now = Date.now();
|
|
1422
|
+
progress.lastActivityMs = now;
|
|
1421
1423
|
let flushProgress = false;
|
|
1422
1424
|
// A retry is recovered at the first assistant provider event, before the
|
|
1423
1425
|
// session emits auto_retry_end after message completion.
|
|
@@ -1719,19 +1721,20 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1719
1721
|
checkAbort();
|
|
1720
1722
|
// Pin authStorage to modelRegistry.authStorage — mirrors the createAgentSession invariant.
|
|
1721
1723
|
const registryFromParent = options.modelRegistry !== undefined;
|
|
1724
|
+
const effectiveAgentDir = options.agentDir ?? settings.getAgentDir();
|
|
1722
1725
|
const registryAuthStorage =
|
|
1723
1726
|
options.authStorage ??
|
|
1724
1727
|
options.modelRegistry?.authStorage ??
|
|
1725
|
-
(await awaitAbortable(discoverAuthStorage(
|
|
1728
|
+
(await awaitAbortable(discoverAuthStorage(effectiveAgentDir)));
|
|
1726
1729
|
if (options.modelRegistry === undefined && options.authStorage === undefined)
|
|
1727
1730
|
ownedAuthStorage = registryAuthStorage;
|
|
1728
1731
|
ownedModelRegistry = options.modelRegistry
|
|
1729
1732
|
? undefined
|
|
1730
|
-
: new ModelRegistry(registryAuthStorage, path.join(
|
|
1731
|
-
agentDir:
|
|
1733
|
+
: new ModelRegistry(registryAuthStorage, path.join(effectiveAgentDir, "models.yml"), settings, {
|
|
1734
|
+
agentDir: effectiveAgentDir,
|
|
1732
1735
|
automaticRefresh: false,
|
|
1733
1736
|
});
|
|
1734
|
-
|
|
1737
|
+
let modelRegistry = options.modelRegistry ?? ownedModelRegistry!;
|
|
1735
1738
|
const authStorage = modelRegistry.authStorage;
|
|
1736
1739
|
if (options.authStorage && options.authStorage !== authStorage) {
|
|
1737
1740
|
throw new Error(
|
|
@@ -1754,17 +1757,7 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1754
1757
|
const canonicalChildScope = getSubagentCanonicalScope(options.parentSessionId, options.subagentId ?? id);
|
|
1755
1758
|
|
|
1756
1759
|
if (options.preflightProbe || options.preflightDurable) preflightOperation = "auth_resolve";
|
|
1757
|
-
const
|
|
1758
|
-
model,
|
|
1759
|
-
thinkingLevel: resolvedThinkingLevel,
|
|
1760
|
-
explicitThinkingLevel,
|
|
1761
|
-
authFallbackUsed: resolvedAuthFallbackUsed,
|
|
1762
|
-
requestedModel,
|
|
1763
|
-
fallbackReason,
|
|
1764
|
-
activeIndex,
|
|
1765
|
-
parentFallbackSelector,
|
|
1766
|
-
skips,
|
|
1767
|
-
} = await awaitAbortable(
|
|
1760
|
+
const resolveConfiguredModel = () =>
|
|
1768
1761
|
resolveModelOverrideWithAuthFallback(
|
|
1769
1762
|
modelPatterns,
|
|
1770
1763
|
options.preflightProbe || options.preflightDurable ? undefined : options.parentActiveModelPattern,
|
|
@@ -1776,8 +1769,44 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1776
1769
|
...(options.parentActiveModelProfile ? { aliasIntent: "preset-equivalent" } : {}),
|
|
1777
1770
|
},
|
|
1778
1771
|
canonicalChildScope,
|
|
1779
|
-
)
|
|
1780
|
-
);
|
|
1772
|
+
);
|
|
1773
|
+
let resolution = await awaitAbortable(resolveConfiguredModel());
|
|
1774
|
+
if (
|
|
1775
|
+
!resolution.model &&
|
|
1776
|
+
registryFromParent &&
|
|
1777
|
+
isExplicitProviderModelOverride(modelPatterns) &&
|
|
1778
|
+
resolution.skips.some(skip => skip.reason === "unknown_model")
|
|
1779
|
+
) {
|
|
1780
|
+
ownedModelRegistry = new ModelRegistry(
|
|
1781
|
+
registryAuthStorage,
|
|
1782
|
+
path.join(effectiveAgentDir, "models.yml"),
|
|
1783
|
+
settings,
|
|
1784
|
+
{
|
|
1785
|
+
agentDir: effectiveAgentDir,
|
|
1786
|
+
automaticRefresh: false,
|
|
1787
|
+
},
|
|
1788
|
+
);
|
|
1789
|
+
await awaitAbortable(
|
|
1790
|
+
refreshMissingQualifiedModelProviders(
|
|
1791
|
+
modelPatterns,
|
|
1792
|
+
ownedModelRegistry,
|
|
1793
|
+
options.parentCredentialSessionId ?? options.parentSessionId,
|
|
1794
|
+
),
|
|
1795
|
+
);
|
|
1796
|
+
modelRegistry = ownedModelRegistry;
|
|
1797
|
+
resolution = await awaitAbortable(resolveConfiguredModel());
|
|
1798
|
+
}
|
|
1799
|
+
const {
|
|
1800
|
+
model,
|
|
1801
|
+
thinkingLevel: resolvedThinkingLevel,
|
|
1802
|
+
explicitThinkingLevel,
|
|
1803
|
+
authFallbackUsed: resolvedAuthFallbackUsed,
|
|
1804
|
+
requestedModel,
|
|
1805
|
+
fallbackReason,
|
|
1806
|
+
activeIndex,
|
|
1807
|
+
parentFallbackSelector,
|
|
1808
|
+
skips,
|
|
1809
|
+
} = resolution;
|
|
1781
1810
|
if (!model && isExplicitProviderModelOverride(modelPatterns)) {
|
|
1782
1811
|
const skipReasons = [...new Set(skips.map(skip => skip.reason))];
|
|
1783
1812
|
if ((options.preflightProbe || options.preflightDurable) && skipReasons.includes("unauthenticated")) {
|
package/src/task/types.ts
CHANGED
|
@@ -285,6 +285,8 @@ export interface AgentProgress {
|
|
|
285
285
|
recentTools: Array<{ tool: string; args: string; endMs: number }>;
|
|
286
286
|
recentOutput: string[];
|
|
287
287
|
toolCount: number;
|
|
288
|
+
/** Epoch ms of the latest event observed from this subagent (any agent event). */
|
|
289
|
+
lastActivityMs?: number;
|
|
288
290
|
/** Cumulative input + output + cacheWrite tokens across all turns. Excludes cacheRead (re-reads cached context every turn, making cumulative sum misleading). */
|
|
289
291
|
tokens: number;
|
|
290
292
|
/**
|
|
@@ -331,6 +331,7 @@ export async function runInteractiveBashPty(
|
|
|
331
331
|
timeoutMs: number;
|
|
332
332
|
signal?: AbortSignal;
|
|
333
333
|
env?: Record<string, string>;
|
|
334
|
+
unsetEnv?: string[];
|
|
334
335
|
artifactPath?: string;
|
|
335
336
|
artifactId?: string;
|
|
336
337
|
artifactPublisher?: TerminalArtifactPublisher;
|
|
@@ -422,6 +423,7 @@ export async function runInteractiveBashPty(
|
|
|
422
423
|
cwd: options.cwd,
|
|
423
424
|
timeoutMs: options.timeoutMs,
|
|
424
425
|
env: { ...NON_INTERACTIVE_ENV, ...options.env },
|
|
426
|
+
unsetEnv: options.unsetEnv,
|
|
425
427
|
signal: processAbortController.signal,
|
|
426
428
|
cols: initialPtySize.cols,
|
|
427
429
|
rows: initialPtySize.rows,
|
package/src/tools/bash.ts
CHANGED
|
@@ -94,6 +94,15 @@ const ARTIFACT_SAVE_DIAGNOSTIC_MAX_BYTES = 256;
|
|
|
94
94
|
const BASH_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
95
95
|
const MASTER_CAPABILITY_ENV = "GJC_MASTER_CAPABILITY";
|
|
96
96
|
const MASTER_OWNER_SESSION_ENV = "GJC_MASTER_OWNER_SESSION_ID";
|
|
97
|
+
const COORDINATOR_ONLY_BASH_ENV = [
|
|
98
|
+
"GJC_COORDINATOR_SESSION_STATE_FILE",
|
|
99
|
+
"GJC_COORDINATOR_SESSION_ID",
|
|
100
|
+
"GJC_COORDINATOR_SESSION_BRANCH",
|
|
101
|
+
"GJC_COORDINATOR_SESSION_LAUNCH_ID",
|
|
102
|
+
"GJC_COORDINATOR_SESSION_READINESS_FILE",
|
|
103
|
+
"GJC_COORDINATOR_SIDECAR_SIGNATURE_REQUIRED",
|
|
104
|
+
"GJC_COORDINATOR_SIDECAR_KEY_ID",
|
|
105
|
+
] as const;
|
|
97
106
|
const DEFAULT_AUTO_BACKGROUND_THRESHOLD_MS = 60_000;
|
|
98
107
|
const ACP_RELEASE_TIMEOUT_MS = 1_000;
|
|
99
108
|
const READ_ONLY_BASH_ENV: Record<string, string> = {
|
|
@@ -1051,6 +1060,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1051
1060
|
notices?: readonly string[];
|
|
1052
1061
|
|
|
1053
1062
|
resolvedEnv?: Record<string, string>;
|
|
1063
|
+
unsetEnv: string[];
|
|
1054
1064
|
directMasterSpawn: boolean;
|
|
1055
1065
|
onUpdate?: AgentToolUpdateCallback<BashToolDetails>;
|
|
1056
1066
|
startBackgrounded: boolean;
|
|
@@ -1108,6 +1118,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1108
1118
|
timeout: options.timeoutMs,
|
|
1109
1119
|
signal: runSignal,
|
|
1110
1120
|
env: options.resolvedEnv,
|
|
1121
|
+
unsetEnv: options.unsetEnv,
|
|
1111
1122
|
artifactPath,
|
|
1112
1123
|
artifactId,
|
|
1113
1124
|
artifactPublisher,
|
|
@@ -1290,6 +1301,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1290
1301
|
command: string;
|
|
1291
1302
|
commandCwd: string;
|
|
1292
1303
|
resolvedEnv: Record<string, string>;
|
|
1304
|
+
unsetEnv: string[];
|
|
1293
1305
|
directMasterSpawn: boolean;
|
|
1294
1306
|
requestedTimeoutSec: number;
|
|
1295
1307
|
timeoutSec: number;
|
|
@@ -1462,6 +1474,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1462
1474
|
...(this.session.bashRestrictionProfile === "read-only" ? READ_ONLY_BASH_ENV : {}),
|
|
1463
1475
|
...(allowedPrefixes && allowedPrefixes.length > 0 ? { [GJC_RESTRICTED_ROLE_AGENT_BASH_ENV]: "1" } : {}),
|
|
1464
1476
|
};
|
|
1477
|
+
const unsetEnv = COORDINATOR_ONLY_BASH_ENV.filter(name => !Object.hasOwn(expandedEnv ?? {}, name));
|
|
1465
1478
|
|
|
1466
1479
|
if (cwd?.includes("://") || cwd?.includes("local:/")) {
|
|
1467
1480
|
cwd = await expandInternalUrls(cwd, { ...internalUrlOptions, noEscape: true });
|
|
@@ -1494,6 +1507,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1494
1507
|
command,
|
|
1495
1508
|
commandCwd,
|
|
1496
1509
|
resolvedEnv,
|
|
1510
|
+
unsetEnv,
|
|
1497
1511
|
directMasterSpawn,
|
|
1498
1512
|
requestedTimeoutSec,
|
|
1499
1513
|
timeoutSec,
|
|
@@ -1657,6 +1671,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1657
1671
|
timeout: monitorTimeoutMs,
|
|
1658
1672
|
signal,
|
|
1659
1673
|
env: prepared.resolvedEnv,
|
|
1674
|
+
unsetEnv: prepared.unsetEnv,
|
|
1660
1675
|
artifactPath,
|
|
1661
1676
|
artifactId,
|
|
1662
1677
|
artifactPublisher,
|
|
@@ -1743,6 +1758,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1743
1758
|
command,
|
|
1744
1759
|
commandCwd,
|
|
1745
1760
|
resolvedEnv,
|
|
1761
|
+
unsetEnv,
|
|
1746
1762
|
directMasterSpawn,
|
|
1747
1763
|
requestedTimeoutSec,
|
|
1748
1764
|
timeoutSec,
|
|
@@ -1782,6 +1798,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1782
1798
|
notices: pendingNotices,
|
|
1783
1799
|
|
|
1784
1800
|
resolvedEnv,
|
|
1801
|
+
unsetEnv,
|
|
1785
1802
|
directMasterSpawn,
|
|
1786
1803
|
onUpdate,
|
|
1787
1804
|
startBackgrounded: true,
|
|
@@ -1834,6 +1851,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1834
1851
|
notices: pendingNotices,
|
|
1835
1852
|
|
|
1836
1853
|
resolvedEnv,
|
|
1854
|
+
unsetEnv,
|
|
1837
1855
|
directMasterSpawn,
|
|
1838
1856
|
onUpdate,
|
|
1839
1857
|
startBackgrounded,
|
|
@@ -2606,6 +2624,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2606
2624
|
timeoutMs,
|
|
2607
2625
|
signal,
|
|
2608
2626
|
env: resolvedEnv,
|
|
2627
|
+
unsetEnv,
|
|
2609
2628
|
artifactPath,
|
|
2610
2629
|
artifactId,
|
|
2611
2630
|
artifactPublisher,
|
|
@@ -2774,6 +2793,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2774
2793
|
timeout: timeoutMs,
|
|
2775
2794
|
signal,
|
|
2776
2795
|
env: resolvedEnv,
|
|
2796
|
+
unsetEnv,
|
|
2777
2797
|
artifactPath,
|
|
2778
2798
|
artifactId,
|
|
2779
2799
|
artifactPublisher,
|
package/src/tools/fetch.ts
CHANGED
|
@@ -11,15 +11,20 @@ import { type Theme, theme } from "../modes/theme/theme";
|
|
|
11
11
|
import type { ToolSession } from "../sdk";
|
|
12
12
|
import type { AgentStorage } from "../session/agent-storage";
|
|
13
13
|
import { DEFAULT_MAX_BYTES, type TruncationDirection, truncateContent } from "../session/streaming-output";
|
|
14
|
-
import { renderStatusLine } from "../tui";
|
|
15
14
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
15
|
+
import { renderStatusLine } from "../tui/status-line";
|
|
16
16
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
17
17
|
import { parseHtmlLazy } from "../utils/linkedom";
|
|
18
18
|
import { INSANE_NOTES } from "../web/insane/bridge";
|
|
19
19
|
import { validatePublicHttpUrl } from "../web/insane/url-guard";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
import {
|
|
21
|
+
finalizeOutput,
|
|
22
|
+
loadPage,
|
|
23
|
+
looksLikeHtml,
|
|
24
|
+
MAX_OUTPUT_CHARS,
|
|
25
|
+
type RenderResult,
|
|
26
|
+
type SpecialHandler,
|
|
27
|
+
} from "../web/scrapers/types";
|
|
23
28
|
import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
|
|
24
29
|
import { applyListLimit } from "./list-limit";
|
|
25
30
|
import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
|
|
@@ -30,6 +35,12 @@ import { clampTimeout } from "./tool-timeouts";
|
|
|
30
35
|
|
|
31
36
|
type NativeHtmlBindings = { htmlToMarkdown: typeof htmlToMarkdownFn };
|
|
32
37
|
let nativeHtmlBindings: NativeHtmlBindings | undefined;
|
|
38
|
+
let specialHandlersCache: SpecialHandler[] | undefined;
|
|
39
|
+
|
|
40
|
+
function getSpecialHandlers(): SpecialHandler[] {
|
|
41
|
+
specialHandlersCache ??= (require("../web/scrapers") as { specialHandlers: SpecialHandler[] }).specialHandlers;
|
|
42
|
+
return specialHandlersCache;
|
|
43
|
+
}
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
46
|
* Lazy native access for HTML conversion. The module is cached, never the
|
|
@@ -659,7 +670,7 @@ async function handleSpecialUrls(
|
|
|
659
670
|
signal: AbortSignal | undefined,
|
|
660
671
|
storage: AgentStorage | null,
|
|
661
672
|
): Promise<FetchRenderResult | null> {
|
|
662
|
-
for (const handler of
|
|
673
|
+
for (const handler of getSpecialHandlers()) {
|
|
663
674
|
if (signal?.aborted) {
|
|
664
675
|
throw new ToolAbortError();
|
|
665
676
|
}
|
|
@@ -669,6 +680,11 @@ async function handleSpecialUrls(
|
|
|
669
680
|
return null;
|
|
670
681
|
}
|
|
671
682
|
|
|
683
|
+
/** Test-only seam for verifying lazy special-handler loading and dispatch. */
|
|
684
|
+
export const fetchSpecialHandlerTestHooks = {
|
|
685
|
+
dispatch: handleSpecialUrls,
|
|
686
|
+
};
|
|
687
|
+
|
|
672
688
|
// =============================================================================
|
|
673
689
|
// Main Render Function
|
|
674
690
|
// =============================================================================
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Component } from "@gajae-code/tui";
|
|
11
11
|
import { Text } from "@gajae-code/tui";
|
|
12
|
+
import { formatNumber } from "@gajae-code/utils";
|
|
12
13
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
14
|
import type { Theme } from "../modes/theme/theme";
|
|
14
15
|
import { providerRetryPhaseLabel } from "../task/provider-retry-status";
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
truncateToWidth,
|
|
23
24
|
} from "./render-utils";
|
|
24
25
|
import {
|
|
26
|
+
SUBAGENT_ACTIVITY_BUCKET_MS,
|
|
25
27
|
type SubagentLiveProgress,
|
|
26
28
|
type SubagentSnapshot,
|
|
27
29
|
type SubagentToolDetails,
|
|
@@ -186,7 +188,28 @@ function renderSubagentStatusLine(snapshot: SubagentSnapshot, theme: Theme, spin
|
|
|
186
188
|
)
|
|
187
189
|
: theme.fg("dim", snapshot.status);
|
|
188
190
|
const duration = theme.fg("dim", formatDuration(snapshot.durationMs));
|
|
189
|
-
|
|
191
|
+
const live = snapshot.liveProgressAvailable !== false ? snapshot.progress : undefined;
|
|
192
|
+
return `${icon} ${id} ${status} ${duration}${live ? formatLiveStats(live, theme, Date.now()) : ""}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Live stats ride the cheap status line (rebuilt every render), never the cached
|
|
196
|
+
// body. Quantize age to the producer cadence so incidental renders do not churn it.
|
|
197
|
+
function formatLiveStats(progress: SubagentLiveProgress, theme: Theme, nowMs: number): string {
|
|
198
|
+
const parts: string[] = [];
|
|
199
|
+
if (progress.toolCount) parts.push(`${progress.toolCount} ${progress.toolCount === 1 ? "tool" : "tools"}`);
|
|
200
|
+
if (progress.contextTokens) {
|
|
201
|
+
parts.push(
|
|
202
|
+
progress.contextWindow
|
|
203
|
+
? `${formatNumber(progress.contextTokens)}/${formatNumber(progress.contextWindow)} ctx`
|
|
204
|
+
: `${formatNumber(progress.contextTokens)} ctx`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (progress.lastActivityMs !== undefined && progress.status === "running") {
|
|
208
|
+
const ageMs = Math.max(0, nowMs - progress.lastActivityMs);
|
|
209
|
+
const ageBucketMs = Math.floor(ageMs / SUBAGENT_ACTIVITY_BUCKET_MS) * SUBAGENT_ACTIVITY_BUCKET_MS;
|
|
210
|
+
parts.push(`last activity ${formatDuration(ageBucketMs)} ago`);
|
|
211
|
+
}
|
|
212
|
+
return parts.map(part => `${theme.sep.dot}${theme.fg("dim", part)}`).join("");
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
function renderSubagentLiveProgress(
|
package/src/tools/subagent.ts
CHANGED
|
@@ -120,6 +120,10 @@ export interface SubagentLiveProgress {
|
|
|
120
120
|
currentTool?: string;
|
|
121
121
|
recentTool?: string;
|
|
122
122
|
recentOutputSummary?: { lineCount: number };
|
|
123
|
+
toolCount?: number;
|
|
124
|
+
contextTokens?: number;
|
|
125
|
+
contextWindow?: number;
|
|
126
|
+
lastActivityMs?: number;
|
|
123
127
|
fastMode?: boolean;
|
|
124
128
|
retryState?: {
|
|
125
129
|
attempt: number;
|
|
@@ -145,6 +149,14 @@ function toSubagentLiveProgress(progress: AgentProgress): SubagentLiveProgress {
|
|
|
145
149
|
...(progress.recentOutput.length > 0
|
|
146
150
|
? { recentOutputSummary: { lineCount: Math.min(progress.recentOutput.length, 6) } }
|
|
147
151
|
: {}),
|
|
152
|
+
...(progress.toolCount > 0 ? { toolCount: progress.toolCount } : {}),
|
|
153
|
+
...(progress.contextTokens !== undefined && progress.contextTokens > 0
|
|
154
|
+
? { contextTokens: progress.contextTokens }
|
|
155
|
+
: {}),
|
|
156
|
+
...(progress.contextWindow !== undefined && progress.contextWindow > 0
|
|
157
|
+
? { contextWindow: progress.contextWindow }
|
|
158
|
+
: {}),
|
|
159
|
+
...(progress.lastActivityMs !== undefined ? { lastActivityMs: progress.lastActivityMs } : {}),
|
|
148
160
|
...(progress.fastMode ? { fastMode: true } : {}),
|
|
149
161
|
...(progress.retryState
|
|
150
162
|
? {
|
|
@@ -1104,11 +1116,15 @@ function previewJobOutput(
|
|
|
1104
1116
|
* unsafe.
|
|
1105
1117
|
*
|
|
1106
1118
|
* Time-derived fields are intentionally excluded so the panel does not churn while
|
|
1107
|
-
* idle
|
|
1119
|
+
* idle (the child's last-event timestamp is included; it moves only on real
|
|
1120
|
+
* activity): raw durations (`durationMs`), current-tool elapsed (`currentToolStartMs`),
|
|
1108
1121
|
* and retry countdowns (`retryState.startedAtMs`) are omitted. Idle duration and
|
|
1109
1122
|
* countdown ticking is sacrificed by design; every real transition still changes
|
|
1110
1123
|
* the signature.
|
|
1111
1124
|
*/
|
|
1125
|
+
/** Shared cadence for await progress refreshes and rendered last-activity age. */
|
|
1126
|
+
export const SUBAGENT_ACTIVITY_BUCKET_MS = 5_000;
|
|
1127
|
+
|
|
1112
1128
|
export function subagentAwaitRenderedStateSignature(
|
|
1113
1129
|
subagents: readonly SubagentSnapshot[],
|
|
1114
1130
|
receipt?: Pick<SubagentToolDetails, "awaitOutcome" | "interrupted">,
|
|
@@ -1156,6 +1172,16 @@ function canonicalizeProgressForSignature(progress: SubagentLiveProgress): unkno
|
|
|
1156
1172
|
currentTool: progress.currentTool ?? null,
|
|
1157
1173
|
recentTool: progress.recentTool ?? null,
|
|
1158
1174
|
recentOutputSummary: progress.recentOutputSummary ?? null,
|
|
1175
|
+
toolCount: progress.toolCount ?? 0,
|
|
1176
|
+
contextTokens: progress.contextTokens ?? null,
|
|
1177
|
+
contextWindow: progress.contextWindow ?? null,
|
|
1178
|
+
// Event timestamp, bucketed: it only moves when the child actually does
|
|
1179
|
+
// something (idle gating holds), and the bucket keeps a streaming child from
|
|
1180
|
+
// re-emitting on every delta. The status-line age is quantized to this cadence.
|
|
1181
|
+
lastActivityBucket:
|
|
1182
|
+
progress.lastActivityMs === undefined
|
|
1183
|
+
? null
|
|
1184
|
+
: Math.floor(progress.lastActivityMs / SUBAGENT_ACTIVITY_BUCKET_MS),
|
|
1159
1185
|
fastMode: progress.fastMode ?? false,
|
|
1160
1186
|
retryFailure: progress.retryFailure ?? null,
|
|
1161
1187
|
retryState: progress.retryState
|
package/src/utils/mupdf.ts
CHANGED
|
@@ -122,8 +122,15 @@ function formatMuPdfCauseChain(error: unknown): string {
|
|
|
122
122
|
// internal inspection and operational debug logging. Conservatively redact the
|
|
123
123
|
// rest of a path-bearing field so spaces in install paths cannot leak suffixes.
|
|
124
124
|
export function sanitizeMuPdfDiagnostic(message: string): string {
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
return (
|
|
126
|
+
util
|
|
127
|
+
.stripVTControlCharacters(message)
|
|
128
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, "")
|
|
129
|
+
// The prefix run is boundary anchored. Unbounded, `[^\s"'`]*` is re-tried at
|
|
130
|
+
// every offset of a long slash-free run before failing to find a separator,
|
|
131
|
+
// which is quadratic in the message length: 100 KB cost about 13s. A MuPDF
|
|
132
|
+
// diagnostic is derived from the document being converted, and
|
|
133
|
+
// `normalizeError` runs this once per link in the cause chain.
|
|
134
|
+
.replace(/(?<![^\s"'`])[^\s"'`]*(?:[/\\]|%2f|%5c)[^"'`]*(?=["'`]|$)/gi, "[path redacted]")
|
|
135
|
+
);
|
|
129
136
|
}
|