@gajae-code/coding-agent 0.16.3 → 0.16.4
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 +59 -0
- package/dist/types/cli/args.d.ts +2 -2
- 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/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/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- 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/runtime-skill-discovery.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- 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 +1 -0
- package/dist/types/modes/types.d.ts +1 -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/bus/chat-daemon-runtime.d.ts +12 -0
- 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/host/session-runtime.d.ts +23 -0
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/setup/paseo/announce.d.ts +111 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +7 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/root-flags.ts +5 -3
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock.ts +181 -17
- package/src/config/model-preset-registry.ts +5 -3
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +57 -11
- 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/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/discovery/builtin.ts +13 -4
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +10 -2
- package/src/extensibility/gjc-plugins/compiler.ts +208 -58
- 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/runtime-adapters.ts +758 -16
- package/src/extensibility/runtime-skill-discovery.ts +77 -151
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +44 -16
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +92 -2
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/model-selector.ts +166 -33
- 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 +393 -21
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +148 -5
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +47 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +171 -14
- package/src/runtime-mcp/pool.test.ts +209 -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 +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +37 -1
- package/src/sdk/bus/slack-daemon.ts +92 -2
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/cli/session-cli.ts +9 -6
- package/src/sdk/host/session-runtime.test.ts +2 -2
- package/src/sdk/host/session-runtime.ts +275 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -29
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +123 -38
- package/src/session/contribution-prep.ts +214 -21
- package/src/setup/paseo/announce.ts +450 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +2 -1
- package/src/task/index.ts +17 -4
- package/src/tools/bash.ts +35 -8
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +8 -2
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/search.ts +116 -58
- package/src/tools/skill.ts +22 -2
- package/src/tools/subagent.ts +3 -1
- package/src/tools/tool-catalog.generated.ts +8 -1
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLoop calls `getApiKey(provider)` before `streamFn`.
|
|
3
|
+
* Resolve/stream already use `getApiKey(model)`. If those two lookups diverge,
|
|
4
|
+
* the child dies locally (~8ms, 0 tokens, sanitized "Agent run failed.")
|
|
5
|
+
* and never reaches the working model-scoped path (#5081).
|
|
6
|
+
*/
|
|
7
|
+
export type SessionApiKeyModel = {
|
|
8
|
+
provider: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SessionApiKeyRegistry = {
|
|
13
|
+
getApiKey(
|
|
14
|
+
model: SessionApiKeyModel,
|
|
15
|
+
sessionId?: string,
|
|
16
|
+
options?: { signal?: AbortSignal },
|
|
17
|
+
): Promise<string | undefined>;
|
|
18
|
+
getApiKeyForProvider(
|
|
19
|
+
provider: string,
|
|
20
|
+
sessionId?: string,
|
|
21
|
+
baseUrl?: string,
|
|
22
|
+
options?: { signal?: AbortSignal },
|
|
23
|
+
): Promise<string | undefined>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type SessionApiKeyResolution = {
|
|
27
|
+
apiKey: string;
|
|
28
|
+
credentialSessionId: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type SessionApiKeyCredentialType = "api_key" | "oauth" | undefined;
|
|
32
|
+
|
|
33
|
+
export function isStableSessionApiKeyCredentialType(
|
|
34
|
+
previous: SessionApiKeyCredentialType,
|
|
35
|
+
replacement: SessionApiKeyCredentialType,
|
|
36
|
+
): boolean {
|
|
37
|
+
return previous === replacement;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveLiveSessionApiKeyModel(
|
|
41
|
+
live: SessionApiKeyModel | undefined,
|
|
42
|
+
captured: SessionApiKeyModel | undefined,
|
|
43
|
+
provider: string,
|
|
44
|
+
): SessionApiKeyModel | undefined {
|
|
45
|
+
if (live?.provider === provider) return live;
|
|
46
|
+
if (captured?.provider === provider) return captured;
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function lookupOnce(
|
|
51
|
+
registry: SessionApiKeyRegistry,
|
|
52
|
+
provider: string,
|
|
53
|
+
sessionId: string | undefined,
|
|
54
|
+
model: SessionApiKeyModel | undefined,
|
|
55
|
+
signal: AbortSignal | undefined,
|
|
56
|
+
): Promise<string | undefined> {
|
|
57
|
+
signal?.throwIfAborted();
|
|
58
|
+
const matchingModel = model?.provider === provider ? model : undefined;
|
|
59
|
+
const options = signal ? { signal } : undefined;
|
|
60
|
+
let key: string | undefined;
|
|
61
|
+
if (matchingModel) {
|
|
62
|
+
key = await registry.getApiKey(matchingModel, sessionId, options);
|
|
63
|
+
signal?.throwIfAborted();
|
|
64
|
+
}
|
|
65
|
+
if (!key) {
|
|
66
|
+
key = await registry.getApiKeyForProvider(provider, sessionId, matchingModel?.baseUrl, options);
|
|
67
|
+
signal?.throwIfAborted();
|
|
68
|
+
}
|
|
69
|
+
return key;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function lookupSessionApiKey(
|
|
73
|
+
registry: SessionApiKeyRegistry,
|
|
74
|
+
provider: string,
|
|
75
|
+
sessionId: string | undefined,
|
|
76
|
+
model: SessionApiKeyModel | undefined,
|
|
77
|
+
allowUnscopedRetry = true,
|
|
78
|
+
signal?: AbortSignal,
|
|
79
|
+
): Promise<SessionApiKeyResolution> {
|
|
80
|
+
let key = await lookupOnce(registry, provider, sessionId, model, signal);
|
|
81
|
+
// Architect children inherit the parent SID. Direct `-p --no-session` does
|
|
82
|
+
// not. Scoped miss then global/broker hit is the remaining 0-token death
|
|
83
|
+
// after #5105 (jsonl: Agent run failed / output 0).
|
|
84
|
+
if (!key && sessionId && allowUnscopedRetry) {
|
|
85
|
+
key = await lookupOnce(registry, provider, undefined, model, signal);
|
|
86
|
+
if (key) return { apiKey: key, credentialSessionId: undefined };
|
|
87
|
+
}
|
|
88
|
+
if (!key) {
|
|
89
|
+
throw Object.assign(new Error(`No API key found for provider "${provider}"`), {
|
|
90
|
+
code: "provider_unavailable",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return { apiKey: key, credentialSessionId: sessionId };
|
|
94
|
+
}
|
package/src/sdk/session.ts
CHANGED
|
@@ -197,6 +197,11 @@ import { EventBus } from "../utils/event-bus";
|
|
|
197
197
|
import { buildNamedToolChoice, buildNamedToolChoiceResult } from "../utils/tool-choice";
|
|
198
198
|
import type { WorkspaceTree } from "../workspace-tree";
|
|
199
199
|
import { createSessionLifecycleService } from "./lifecycle/client";
|
|
200
|
+
import {
|
|
201
|
+
isStableSessionApiKeyCredentialType,
|
|
202
|
+
lookupSessionApiKey,
|
|
203
|
+
resolveLiveSessionApiKeyModel,
|
|
204
|
+
} from "./session-api-key";
|
|
200
205
|
import {
|
|
201
206
|
attachLifecycleStartupCapability,
|
|
202
207
|
lifecycleMcpStartupTimeoutOption,
|
|
@@ -2338,15 +2343,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2338
2343
|
}
|
|
2339
2344
|
if (options.skills === undefined && settings.get("skills.enabled")) {
|
|
2340
2345
|
try {
|
|
2341
|
-
|
|
2342
|
-
...settings.getGroup("skills"),
|
|
2343
|
-
agentDir,
|
|
2344
|
-
cwd: to,
|
|
2345
|
-
disabledExtensions: settings.get("disabledExtensions"),
|
|
2346
|
-
});
|
|
2347
|
-
skills = withEmbeddedDefaultGjcSkills(reloaded.skills);
|
|
2348
|
-
if (!options.parentTaskPrefix) setActiveSkills(skills);
|
|
2349
|
-
await session?.replaceSkills(skills);
|
|
2346
|
+
await session?.reloadSkills(to);
|
|
2350
2347
|
} catch (error) {
|
|
2351
2348
|
logger.warn("Failed to reload skills after session rescope", { error: safeErrorForLog(error) });
|
|
2352
2349
|
}
|
|
@@ -2708,13 +2705,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2708
2705
|
else signal.addEventListener("abort", () => resolve(), { once: true });
|
|
2709
2706
|
return promise;
|
|
2710
2707
|
},
|
|
2708
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
2711
2709
|
// Subagents inherit the parent's manager; its registered endpoint is
|
|
2712
2710
|
// authoritative for owned-registration keying and endpoint-first
|
|
2713
2711
|
// manager lookup (the child's own id is never registered), so tools
|
|
2714
2712
|
// pass the same endpoint the manager's completion callback resolves
|
|
2715
2713
|
// (review thread P1). For a top-level session this equals the
|
|
2716
2714
|
// session id.
|
|
2717
|
-
|
|
2715
|
+
getAsyncEndpointId: () => AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
|
|
2718
2716
|
registerForegroundFoldParticipant: adapter =>
|
|
2719
2717
|
session?.registerForegroundFoldParticipant(adapter) ?? (() => {}),
|
|
2720
2718
|
hasForegroundBashBackgroundRequestHandler: () => session?.hasForegroundBashBackgroundRequestHandler() ?? false,
|
|
@@ -3924,6 +3922,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3924
3922
|
appendSystemPrompt: appendPrompt,
|
|
3925
3923
|
pluginAppendices: pluginSystemAppendices,
|
|
3926
3924
|
repeatToolDescriptions,
|
|
3925
|
+
reasoningLanguage: settings.get("reasoningLanguage"),
|
|
3927
3926
|
intentField,
|
|
3928
3927
|
toolDiscoveryActive: effectiveDiscoveryMode === "all" || mcpDiscoveryEnabled,
|
|
3929
3928
|
eagerTasks: resolveEagerTasks(),
|
|
@@ -4257,6 +4256,32 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4257
4256
|
).seedNormalizedMessages(options.forkContextSeed.messages);
|
|
4258
4257
|
}
|
|
4259
4258
|
|
|
4259
|
+
const resolvedCredentialScopes = new Map<string, string | undefined>();
|
|
4260
|
+
const resolveAgentApiKey = async (provider: string, signal?: AbortSignal): Promise<string> => {
|
|
4261
|
+
const liveModel = resolveLiveSessionApiKeyModel(sessionAgent?.state.model, model, provider);
|
|
4262
|
+
const hasExplicitScopePolicy =
|
|
4263
|
+
modelRegistry.authStorage.hasSessionCredentialSelector(provider, credentialSessionId) ||
|
|
4264
|
+
modelRegistry.authStorage.hasSessionCredentialAuto(provider, credentialSessionId);
|
|
4265
|
+
const resolution = await lookupSessionApiKey(
|
|
4266
|
+
modelRegistry,
|
|
4267
|
+
provider,
|
|
4268
|
+
credentialSessionId,
|
|
4269
|
+
liveModel,
|
|
4270
|
+
!hasExplicitScopePolicy,
|
|
4271
|
+
signal,
|
|
4272
|
+
);
|
|
4273
|
+
resolvedCredentialScopes.set(provider, resolution.credentialSessionId);
|
|
4274
|
+
return resolution.apiKey;
|
|
4275
|
+
};
|
|
4276
|
+
const resolvedCredentialType = (provider: string): "api_key" | "oauth" | undefined => {
|
|
4277
|
+
const scope = resolvedCredentialScopes.has(provider)
|
|
4278
|
+
? resolvedCredentialScopes.get(provider)
|
|
4279
|
+
: credentialSessionId;
|
|
4280
|
+
return modelRegistry.authStorage.getEffectiveCredentialType(provider, scope, {
|
|
4281
|
+
owner: modelRegistry.getAuthStorageOwner(),
|
|
4282
|
+
});
|
|
4283
|
+
};
|
|
4284
|
+
|
|
4260
4285
|
agent = new Agent({
|
|
4261
4286
|
initialState: {
|
|
4262
4287
|
systemPrompt,
|
|
@@ -4295,44 +4320,42 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4295
4320
|
shouldPause: options.shouldPause,
|
|
4296
4321
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
4297
4322
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
4298
|
-
getApiKey:
|
|
4323
|
+
getApiKey: provider => {
|
|
4299
4324
|
// AgentLoop asks by provider, but the active model carries the
|
|
4300
4325
|
// model-scoped credential selector. Read it at call time so model
|
|
4301
4326
|
// changes are honored after /new, fork, resume, or branch switches.
|
|
4302
|
-
|
|
4303
|
-
const key =
|
|
4304
|
-
liveModel?.provider === provider
|
|
4305
|
-
? await modelRegistry.getApiKey(liveModel, credentialSessionId)
|
|
4306
|
-
: undefined;
|
|
4307
|
-
const providerKey = key ?? (await modelRegistry.getApiKeyForProvider(provider, credentialSessionId));
|
|
4308
|
-
if (!providerKey) {
|
|
4309
|
-
throw new Error(`No API key found for provider "${provider}"`);
|
|
4310
|
-
}
|
|
4311
|
-
return providerKey;
|
|
4327
|
+
return resolveAgentApiKey(provider);
|
|
4312
4328
|
},
|
|
4313
|
-
getAuthCredentialType:
|
|
4329
|
+
getAuthCredentialType: resolvedCredentialType,
|
|
4314
4330
|
streamFn: async (streamModel, context, streamOptions) => {
|
|
4315
4331
|
const requestStartedAt = performance.now();
|
|
4316
4332
|
let stream: Awaited<ReturnType<typeof streamSimple>>;
|
|
4317
4333
|
try {
|
|
4318
|
-
const effectiveApiKey = await modelRegistry.getApiKey(streamModel, credentialSessionId);
|
|
4319
4334
|
stream = await streamSimple(streamModel, context, {
|
|
4320
4335
|
...streamOptions,
|
|
4321
|
-
apiKey: effectiveApiKey,
|
|
4322
|
-
authCredentialType: modelRegistry.getSessionCredentialType(streamModel.provider, credentialSessionId),
|
|
4323
4336
|
onAuthError: async (provider, oldKey, error) => {
|
|
4337
|
+
const resolvedScope = resolvedCredentialScopes.has(provider)
|
|
4338
|
+
? resolvedCredentialScopes.get(provider)
|
|
4339
|
+
: credentialSessionId;
|
|
4324
4340
|
await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
|
|
4325
4341
|
signal: streamOptions?.signal,
|
|
4326
|
-
sessionId:
|
|
4342
|
+
sessionId: resolvedScope,
|
|
4327
4343
|
owner: modelRegistry.getAuthStorageOwner(),
|
|
4328
4344
|
});
|
|
4329
4345
|
logger.debug("Retrying provider request after credential invalidation", {
|
|
4330
4346
|
provider,
|
|
4331
4347
|
error: error instanceof Error ? error.message : String(error),
|
|
4332
4348
|
});
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4349
|
+
const replacementApiKey = await resolveAgentApiKey(provider, streamOptions?.signal);
|
|
4350
|
+
if (
|
|
4351
|
+
!isStableSessionApiKeyCredentialType(
|
|
4352
|
+
streamOptions?.authCredentialType,
|
|
4353
|
+
resolvedCredentialType(provider),
|
|
4354
|
+
)
|
|
4355
|
+
) {
|
|
4356
|
+
return undefined;
|
|
4357
|
+
}
|
|
4358
|
+
return replacementApiKey;
|
|
4336
4359
|
},
|
|
4337
4360
|
});
|
|
4338
4361
|
} catch (error) {
|
|
@@ -4459,6 +4482,27 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4459
4482
|
customCommands: customCommandsResult.commands,
|
|
4460
4483
|
skills,
|
|
4461
4484
|
skillWarnings,
|
|
4485
|
+
reloadSkills:
|
|
4486
|
+
options.skills === undefined
|
|
4487
|
+
? async reloadCwd => {
|
|
4488
|
+
if (!settings.get("skills.enabled")) {
|
|
4489
|
+
return { skills: getEmbeddedDefaultGjcSkills(), warnings: [] };
|
|
4490
|
+
} else {
|
|
4491
|
+
const reloaded = await loadSkills({
|
|
4492
|
+
...settings.getGroup("skills"),
|
|
4493
|
+
agentDir,
|
|
4494
|
+
cwd: reloadCwd,
|
|
4495
|
+
disabledExtensions: settings.get("disabledExtensions"),
|
|
4496
|
+
});
|
|
4497
|
+
return { skills: withEmbeddedDefaultGjcSkills(reloaded.skills), warnings: reloaded.warnings };
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
: undefined,
|
|
4501
|
+
onSkillsReloaded: (reloadedSkills, reloadedWarnings) => {
|
|
4502
|
+
skills = reloadedSkills;
|
|
4503
|
+
skillWarnings = reloadedWarnings;
|
|
4504
|
+
if (!options.parentTaskPrefix) setActiveSkills(reloadedSkills);
|
|
4505
|
+
},
|
|
4462
4506
|
skillsSettings: settings.getGroup("skills"),
|
|
4463
4507
|
modelRegistry,
|
|
4464
4508
|
taskDepth,
|
|
@@ -92,10 +92,10 @@ function brokerSessionRows(sessions: readonly unknown[]): BrokerSessionRow[] {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** Exhausts strict broker `session.list` pages into one full session snapshot. */
|
|
95
|
-
export async function listBrokerSessions(broker: SdkClient): Promise<BrokerSessionRow[]> {
|
|
95
|
+
export async function listBrokerSessions(broker: SdkClient, explicitSessionId?: string): Promise<BrokerSessionRow[]> {
|
|
96
96
|
try {
|
|
97
97
|
const pages = await traverseSessionList(
|
|
98
|
-
{},
|
|
98
|
+
{ ...(explicitSessionId ? { resolveSessionId: explicitSessionId } : {}) },
|
|
99
99
|
async input => await broker.global("session.list", input),
|
|
100
100
|
response => {
|
|
101
101
|
brokerResult(response);
|
|
@@ -147,7 +147,7 @@ export async function runSdkServe(argv: string[]): Promise<void> {
|
|
|
147
147
|
throw new Error("broker_unavailable: SDK broker is not reachable");
|
|
148
148
|
}
|
|
149
149
|
try {
|
|
150
|
-
const sessionId = selectBrokerSession(await listBrokerSessions(broker), parsed.sessionId);
|
|
150
|
+
const sessionId = selectBrokerSession(await listBrokerSessions(broker, parsed.sessionId), parsed.sessionId);
|
|
151
151
|
const endpoint = brokerResult(await broker.global("session.get_endpoint", { sessionId }));
|
|
152
152
|
const url = typeof endpoint.url === "string" && endpoint.url ? endpoint.url : undefined;
|
|
153
153
|
const token = typeof endpoint.token === "string" ? endpoint.token : "";
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
canContinuePersistedHistory,
|
|
37
37
|
dispatchedToolIdentity,
|
|
38
38
|
getAgentTerminalOwnerContext,
|
|
39
|
+
isContinuingMidRunMaintenanceOutcome,
|
|
39
40
|
isNonDispatchedToolEvent,
|
|
40
41
|
type ManagedAttemptContinuationOwnership,
|
|
41
42
|
type ManagedAttemptDecision,
|
|
@@ -882,6 +883,10 @@ export interface AgentSessionConfig {
|
|
|
882
883
|
skills?: Skill[];
|
|
883
884
|
/** Skill loading warnings (already captured by SDK) */
|
|
884
885
|
skillWarnings?: SkillWarning[];
|
|
886
|
+
/** Re-enumerate filesystem and marketplace skills after plugin lifecycle changes. */
|
|
887
|
+
reloadSkills?: (cwd: string) => Promise<{ skills: Skill[]; warnings: SkillWarning[] }>;
|
|
888
|
+
/** Publish a successfully committed skill catalog to SDK/process snapshots. */
|
|
889
|
+
onSkillsReloaded?: (skills: Skill[], warnings: SkillWarning[]) => void;
|
|
885
890
|
/** Custom commands (TypeScript slash commands) */
|
|
886
891
|
customCommands?: LoadedCustomCommand[];
|
|
887
892
|
skillsSettings?: SkillsSettings;
|
|
@@ -1044,7 +1049,7 @@ type AutoCompactionTerminalStatus =
|
|
|
1044
1049
|
| { kind: "compacted"; continuationScheduled?: boolean }
|
|
1045
1050
|
| { kind: "aborted"; source: "signal" | "hook" }
|
|
1046
1051
|
| { kind: "skipped"; continuationScheduled?: boolean }
|
|
1047
|
-
| { kind: "failed" };
|
|
1052
|
+
| { kind: "failed"; errorMessage: string };
|
|
1048
1053
|
|
|
1049
1054
|
type ToolOutputPruneResult = {
|
|
1050
1055
|
prunedCount: number;
|
|
@@ -3124,6 +3129,8 @@ export class AgentSession {
|
|
|
3124
3129
|
|
|
3125
3130
|
#skills: Skill[];
|
|
3126
3131
|
#skillWarnings: SkillWarning[];
|
|
3132
|
+
#reloadSkills: AgentSessionConfig["reloadSkills"];
|
|
3133
|
+
#onSkillsReloaded: AgentSessionConfig["onSkillsReloaded"];
|
|
3127
3134
|
|
|
3128
3135
|
// Custom commands (TypeScript slash commands)
|
|
3129
3136
|
#customCommands: LoadedCustomCommand[] = [];
|
|
@@ -4556,6 +4563,8 @@ export class AgentSession {
|
|
|
4556
4563
|
this.agent.setMainAttemptScopeObserver(scope => this.#bindAttemptScope(scope));
|
|
4557
4564
|
this.#skills = config.skills ?? [];
|
|
4558
4565
|
this.#skillWarnings = config.skillWarnings ?? [];
|
|
4566
|
+
this.#reloadSkills = config.reloadSkills;
|
|
4567
|
+
this.#onSkillsReloaded = config.onSkillsReloaded;
|
|
4559
4568
|
this.#customCommands = config.customCommands ?? [];
|
|
4560
4569
|
this.#skillsSettings = config.skillsSettings;
|
|
4561
4570
|
this.#modelRegistry = config.modelRegistry;
|
|
@@ -5638,6 +5647,27 @@ export class AgentSession {
|
|
|
5638
5647
|
if (failures.length > 0) throw new AggregateError(failures, "Tool session cleanup failed.");
|
|
5639
5648
|
}
|
|
5640
5649
|
|
|
5650
|
+
#claimToolSessionTransitionCleanups(): Promise<void> {
|
|
5651
|
+
const cleanups = Array.from(this.#toolSessionTransitionCleanups);
|
|
5652
|
+
this.#toolSessionTransitionCleanups.clear();
|
|
5653
|
+
return this.#settleClaimedToolSessionCleanups(cleanups, "Tool session transition cleanup failed.");
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
#claimToolSessionCleanups(): Promise<void> {
|
|
5657
|
+
const cleanups = Array.from(this.#toolSessionCleanups);
|
|
5658
|
+
this.#toolSessionCleanups.clear();
|
|
5659
|
+
return this.#settleClaimedToolSessionCleanups(cleanups, "Tool session cleanup failed.");
|
|
5660
|
+
}
|
|
5661
|
+
|
|
5662
|
+
async #settleClaimedToolSessionCleanups(
|
|
5663
|
+
cleanups: readonly (() => Promise<void> | void)[],
|
|
5664
|
+
message: string,
|
|
5665
|
+
): Promise<void> {
|
|
5666
|
+
const results = await Promise.allSettled(cleanups.map(async cleanup => await cleanup()));
|
|
5667
|
+
const failures = results.flatMap(result => (result.status === "rejected" ? [result.reason] : []));
|
|
5668
|
+
if (failures.length > 0) throw new AggregateError(failures, message);
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5641
5671
|
getAsyncJobSnapshot(options?: { recentLimit?: number }): AsyncJobSnapshot | null {
|
|
5642
5672
|
// The session's OWNED manager first: /jobs, the status header, and the
|
|
5643
5673
|
// active-background count must reflect THIS session's jobs — with
|
|
@@ -5918,7 +5948,9 @@ export class AgentSession {
|
|
|
5918
5948
|
const canonicalAdmission = this.#reserveCanonicalMessageAdmission(event);
|
|
5919
5949
|
const terminalOwner = event.type === "agent_end" ? getAgentTerminalOwnerContext(event) : undefined;
|
|
5920
5950
|
const maintenanceCheckpoint =
|
|
5921
|
-
event.type === "agent_end" &&
|
|
5951
|
+
event.type === "agent_end" &&
|
|
5952
|
+
event.stopReason === "maintenance" &&
|
|
5953
|
+
isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome);
|
|
5922
5954
|
const terminalClaim =
|
|
5923
5955
|
maintenanceCheckpoint || !terminalOwner
|
|
5924
5956
|
? undefined
|
|
@@ -6196,9 +6228,13 @@ export class AgentSession {
|
|
|
6196
6228
|
this.#requestWorkerIntegrationAttempt();
|
|
6197
6229
|
}
|
|
6198
6230
|
// A maintenance agent_end is an internal checkpoint only while another
|
|
6199
|
-
// continuation will follow.
|
|
6200
|
-
//
|
|
6201
|
-
if (
|
|
6231
|
+
// continuation will follow. Failed or aborted maintenance is terminal and
|
|
6232
|
+
// must reach public subscribers.
|
|
6233
|
+
if (
|
|
6234
|
+
event.type === "agent_end" &&
|
|
6235
|
+
event.stopReason === "maintenance" &&
|
|
6236
|
+
isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome)
|
|
6237
|
+
)
|
|
6202
6238
|
return;
|
|
6203
6239
|
|
|
6204
6240
|
// Hold agent_end until the prompt's finally and all earlier async event work
|
|
@@ -6983,7 +7019,7 @@ export class AgentSession {
|
|
|
6983
7019
|
|
|
6984
7020
|
if (
|
|
6985
7021
|
event.type === "agent_end" &&
|
|
6986
|
-
!(event.stopReason === "maintenance" && event.maintenanceOutcome
|
|
7022
|
+
!(event.stopReason === "maintenance" && isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome))
|
|
6987
7023
|
) {
|
|
6988
7024
|
this.#releaseDeferredSdkFollowUps();
|
|
6989
7025
|
}
|
|
@@ -6996,28 +7032,33 @@ export class AgentSession {
|
|
|
6996
7032
|
// the same run on the rewritten context (no synthetic prompt), skipping
|
|
6997
7033
|
// the goal-runtime / skill-state / retry / compaction finalization a
|
|
6998
7034
|
// normal agent_end runs — none of that applies to an in-progress run.
|
|
6999
|
-
//
|
|
7000
|
-
// continuation if a newer prompt/abort
|
|
7035
|
+
// Failed and aborted outcomes fall through to terminal handling. The
|
|
7036
|
+
// generation guard drops a successful continuation if a newer prompt/abort
|
|
7037
|
+
// has moved the run on.
|
|
7001
7038
|
if (event.stopReason === "maintenance") {
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7039
|
+
if (isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome)) {
|
|
7040
|
+
this.#lastAssistantMessage = undefined;
|
|
7041
|
+
if (
|
|
7042
|
+
!maintenanceWasDisposed &&
|
|
7043
|
+
!this.#isDisposed &&
|
|
7044
|
+
maintenanceGeneration !== undefined &&
|
|
7045
|
+
this.#promptGeneration === maintenanceGeneration
|
|
7046
|
+
) {
|
|
7047
|
+
this.#scheduleAgentContinue({
|
|
7048
|
+
generation: maintenanceGeneration,
|
|
7049
|
+
skipCompactionCheck: true,
|
|
7050
|
+
resourceRunId: activePromptHandle,
|
|
7051
|
+
maintenanceContinuation: true,
|
|
7052
|
+
sdkRunToken: attemptScope ? this.#sdkRunTokensByAttemptScope.get(attemptScope) : undefined,
|
|
7053
|
+
});
|
|
7054
|
+
}
|
|
7055
|
+
return;
|
|
7019
7056
|
}
|
|
7020
|
-
|
|
7057
|
+
// Cancellation already owns teardown and terminal publication. Running
|
|
7058
|
+
// normal post-turn finalization here can deadlock abort/dispose while the
|
|
7059
|
+
// maintenance barrier is unwinding. Failed maintenance alone carries the
|
|
7060
|
+
// synthetic assistant error that must flow through terminal handling.
|
|
7061
|
+
if (event.maintenanceOutcome === "aborted") return;
|
|
7021
7062
|
}
|
|
7022
7063
|
const usage = this.getSessionStats().tokens;
|
|
7023
7064
|
await this.#goalRuntime.onAgentEnd({
|
|
@@ -7083,6 +7124,13 @@ export class AgentSession {
|
|
|
7083
7124
|
return;
|
|
7084
7125
|
}
|
|
7085
7126
|
this.#lastSuccessfulYieldToolCallId = undefined;
|
|
7127
|
+
if (event.stopReason === "maintenance" && event.maintenanceOutcome === "failed") {
|
|
7128
|
+
// The maintenance error already terminalized the run. Retrying this
|
|
7129
|
+
// synthetic assistant failure would resubmit the unchanged context that
|
|
7130
|
+
// maintenance failed to rewrite.
|
|
7131
|
+
this.#resolveRetry();
|
|
7132
|
+
return;
|
|
7133
|
+
}
|
|
7086
7134
|
|
|
7087
7135
|
// Check for retryable errors first (overloaded, rate limit, server errors)
|
|
7088
7136
|
if (this.#isRetryableError(msg)) {
|
|
@@ -8580,7 +8628,8 @@ export class AgentSession {
|
|
|
8580
8628
|
? this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope)
|
|
8581
8629
|
: undefined;
|
|
8582
8630
|
const isTerminalAgentEnd =
|
|
8583
|
-
event.type === "agent_end" &&
|
|
8631
|
+
event.type === "agent_end" &&
|
|
8632
|
+
!(event.stopReason === "maintenance" && isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome));
|
|
8584
8633
|
const finishAttempt = () => {
|
|
8585
8634
|
if (!isTerminalAgentEnd) return;
|
|
8586
8635
|
// A merged wake batch whose flush fired while still streaming cleared its
|
|
@@ -9371,10 +9420,10 @@ export class AgentSession {
|
|
|
9371
9420
|
// one here left signal exit orphaning the subprocess. Each runner clears
|
|
9372
9421
|
// its set, so a graceful dispose running first makes this a no-op rather
|
|
9373
9422
|
// than a double free.
|
|
9374
|
-
this.#
|
|
9423
|
+
this.#claimToolSessionCleanups().catch((error: unknown) =>
|
|
9375
9424
|
logger.warn("signal teardown: tool session cleanups failed", { error }),
|
|
9376
9425
|
),
|
|
9377
|
-
this.#
|
|
9426
|
+
this.#claimToolSessionTransitionCleanups().catch((error: unknown) =>
|
|
9378
9427
|
logger.warn("signal teardown: tool session transition cleanups failed", { error }),
|
|
9379
9428
|
),
|
|
9380
9429
|
]);
|
|
@@ -14200,6 +14249,25 @@ export class AgentSession {
|
|
|
14200
14249
|
await this.refreshBaseSystemPrompt();
|
|
14201
14250
|
}
|
|
14202
14251
|
|
|
14252
|
+
/** Refresh the live registered-skill catalog after install/remove/reload. */
|
|
14253
|
+
async reloadSkills(cwd = this.sessionManager.getCwd()): Promise<void> {
|
|
14254
|
+
if (!this.#reloadSkills) return;
|
|
14255
|
+
const reloaded = await this.#reloadSkills(cwd);
|
|
14256
|
+
const previousSkills = this.#skills;
|
|
14257
|
+
const previousWarnings = this.#skillWarnings;
|
|
14258
|
+
this.#skills = reloaded.skills;
|
|
14259
|
+
this.#skillWarnings = reloaded.warnings;
|
|
14260
|
+
try {
|
|
14261
|
+
await this.refreshBaseSystemPrompt();
|
|
14262
|
+
} catch (error) {
|
|
14263
|
+
this.#skills = previousSkills;
|
|
14264
|
+
this.#skillWarnings = previousWarnings;
|
|
14265
|
+
await this.refreshBaseSystemPrompt().catch(() => {});
|
|
14266
|
+
throw error;
|
|
14267
|
+
}
|
|
14268
|
+
this.#onSkillsReloaded?.(reloaded.skills, reloaded.warnings);
|
|
14269
|
+
}
|
|
14270
|
+
|
|
14203
14271
|
/**
|
|
14204
14272
|
* Retire the cached workspace tree after a rescope so the next turn re-scans
|
|
14205
14273
|
* at the new cwd instead of re-presenting the abandoned launcher root.
|
|
@@ -18407,18 +18475,23 @@ export class AgentSession {
|
|
|
18407
18475
|
* prune → promote → compact machinery and returns an explicit outcome.
|
|
18408
18476
|
*
|
|
18409
18477
|
* A non-"not-needed" outcome ends the current run losslessly
|
|
18410
|
-
* (`agent_end.stopReason === "maintenance"`)
|
|
18411
|
-
*
|
|
18412
|
-
* This method never self-continues, runs goal-runtime
|
|
18413
|
-
* state, or emits a user-facing pause.
|
|
18478
|
+
* (`agent_end.stopReason === "maintenance"`). The `agent_end` handler resumes
|
|
18479
|
+
* committed maintenance with no synthetic prompt and terminalizes failed or
|
|
18480
|
+
* aborted maintenance. This method never self-continues, runs goal-runtime
|
|
18481
|
+
* hooks, clears skill state, or emits a user-facing pause.
|
|
18414
18482
|
*/
|
|
18415
18483
|
async #runMidRunMaintenance(
|
|
18416
18484
|
context: AgentContext,
|
|
18417
18485
|
lifecycle: MidRunMaintenanceLifecycle,
|
|
18418
18486
|
): Promise<ContextMaintenanceResult> {
|
|
18419
|
-
const result = (
|
|
18487
|
+
const result = (
|
|
18488
|
+
outcome: MidRunMaintenanceOutcome,
|
|
18489
|
+
releaseCurrentContext = false,
|
|
18490
|
+
errorMessage?: string,
|
|
18491
|
+
): ContextMaintenanceResult => ({
|
|
18420
18492
|
outcome,
|
|
18421
18493
|
...(releaseCurrentContext ? { releaseCurrentContext: true } : {}),
|
|
18494
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
18422
18495
|
});
|
|
18423
18496
|
if (this.#isDisposed) return { outcome: "aborted" };
|
|
18424
18497
|
const invocationController = new AbortController();
|
|
@@ -18529,7 +18602,13 @@ export class AgentSession {
|
|
|
18529
18602
|
if (compactionStatus.kind === "aborted") {
|
|
18530
18603
|
return compactionStatus.source === "hook" ? result("not-needed") : result("aborted");
|
|
18531
18604
|
}
|
|
18532
|
-
return result(
|
|
18605
|
+
return result(
|
|
18606
|
+
"failed",
|
|
18607
|
+
false,
|
|
18608
|
+
compactionStatus.kind === "failed"
|
|
18609
|
+
? compactionStatus.errorMessage
|
|
18610
|
+
: "Context maintenance could not commit a compaction before the next model request.",
|
|
18611
|
+
);
|
|
18533
18612
|
} finally {
|
|
18534
18613
|
this.#activeMidRunBarrierControllers.delete(invocationController);
|
|
18535
18614
|
}
|
|
@@ -19643,12 +19722,15 @@ export class AgentSession {
|
|
|
19643
19722
|
*/
|
|
19644
19723
|
#maintenanceProviderTransport(): {
|
|
19645
19724
|
sessionId: string | undefined;
|
|
19725
|
+
providerSessionId: string | undefined;
|
|
19646
19726
|
providerSessionState: Map<string, ProviderSessionState>;
|
|
19647
19727
|
preferWebsockets: boolean | undefined;
|
|
19648
19728
|
remoteCompactionFallbackHealth: RemoteCompactionFallbackHealthHooks;
|
|
19649
19729
|
} {
|
|
19730
|
+
const providerSessionId = this.agent.providerSessionId ?? this.agent.sessionId;
|
|
19650
19731
|
return {
|
|
19651
|
-
sessionId:
|
|
19732
|
+
sessionId: providerSessionId,
|
|
19733
|
+
providerSessionId,
|
|
19652
19734
|
providerSessionState: this.#providerSessionState,
|
|
19653
19735
|
preferWebsockets: this.agent.preferWebsockets,
|
|
19654
19736
|
remoteCompactionFallbackHealth: this.#remoteCompactionFallbackHealth,
|
|
@@ -20286,7 +20368,7 @@ export class AgentSession {
|
|
|
20286
20368
|
? `Context overflow recovery failed: ${errorMessage}`
|
|
20287
20369
|
: `Auto-compaction failed: ${errorMessage}`,
|
|
20288
20370
|
});
|
|
20289
|
-
return { kind: "failed" };
|
|
20371
|
+
return { kind: "failed", errorMessage };
|
|
20290
20372
|
} finally {
|
|
20291
20373
|
if (this.#autoCompactionAbortController === autoCompactionAbortController) {
|
|
20292
20374
|
this.#autoCompactionAbortController = undefined;
|
|
@@ -22350,7 +22432,10 @@ export class AgentSession {
|
|
|
22350
22432
|
onChunk,
|
|
22351
22433
|
signal: abortController.signal,
|
|
22352
22434
|
});
|
|
22353
|
-
|
|
22435
|
+
// A retained kernel can finish after strict session close has fenced
|
|
22436
|
+
// persistence. The caller still receives its completed result, but it
|
|
22437
|
+
// must not reopen a closing session to append history.
|
|
22438
|
+
if (!this.#evalExecutionDisposing) this.recordPythonResult(code, result, options);
|
|
22354
22439
|
return result;
|
|
22355
22440
|
})();
|
|
22356
22441
|
return await this.trackEvalExecution(execution, abortController);
|