@gajae-code/coding-agent 0.2.2 → 0.2.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 +45 -8600
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +41 -0
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +56 -0
- package/dist/types/defaults/gjc-defaults.d.ts +19 -6
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +18 -0
- package/dist/types/hooks/skill-state.d.ts +5 -0
- package/dist/types/memories/index.d.ts +1 -1
- package/dist/types/memory-backend/local-backend.d.ts +3 -3
- package/dist/types/modes/components/hook-selector.d.ts +7 -0
- package/dist/types/modes/components/settings-selector.d.ts +3 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/defaults/index.d.ts +126 -0
- package/dist/types/modes/theme/theme.d.ts +5 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -2
- package/dist/types/sdk.d.ts +6 -2
- package/dist/types/session/agent-session.d.ts +45 -1
- package/dist/types/session/session-manager.d.ts +3 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -0
- package/dist/types/setup/provider-onboarding.d.ts +29 -5
- package/dist/types/skill-state/active-state.d.ts +26 -1
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/types.d.ts +11 -0
- package/dist/types/tools/index.d.ts +20 -1
- package/dist/types/tools/skill.d.ts +47 -0
- package/dist/types/utils/changelog.d.ts +18 -2
- package/package.json +7 -7
- package/src/cli/setup-cli.ts +26 -12
- package/src/cli/update-cli.ts +67 -16
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +25 -2
- package/src/commands/setup.ts +2 -0
- package/src/commands/state.ts +1 -0
- package/src/config/settings-schema.ts +63 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +58 -5
- package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -0
- package/src/defaults/gjc/skills/team/SKILL.md +10 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +19 -6
- package/src/defaults/gjc-defaults.ts +68 -16
- package/src/discovery/helpers.ts +24 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +312 -1
- package/src/gjc-runtime/state-runtime.ts +175 -5
- package/src/goals/tools/goal-tool.ts +5 -1
- package/src/hooks/skill-state.ts +8 -6
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/internal-urls/memory-protocol.ts +3 -2
- package/src/main.ts +2 -3
- package/src/memories/index.ts +6 -4
- package/src/memory-backend/local-backend.ts +14 -6
- package/src/modes/components/hook-selector.ts +156 -1
- package/src/modes/components/settings-selector.ts +16 -12
- package/src/modes/controllers/command-controller.ts +3 -4
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/selector-controller.ts +69 -9
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/theme/defaults/blue-crab.json +126 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/theme.ts +40 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/context-usage.ts +66 -17
- package/src/prompts/agents/architect.md +3 -0
- package/src/prompts/agents/executor.md +2 -0
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/memories/unavailable.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +6 -0
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/sdk.ts +54 -10
- package/src/session/agent-session.ts +204 -21
- package/src/session/session-manager.ts +9 -1
- package/src/setup/model-onboarding-guidance.ts +6 -3
- package/src/setup/provider-onboarding.ts +177 -16
- package/src/skill-state/active-state.ts +150 -25
- package/src/skill-state/deep-interview-mutation-guard.ts +11 -24
- package/src/skill-state/initial-phase.ts +17 -0
- package/src/slash-commands/builtin-registry.ts +62 -14
- package/src/slash-commands/helpers/context-report.ts +123 -13
- package/src/task/agents.ts +1 -0
- package/src/task/executor.ts +9 -1
- package/src/task/index.ts +91 -4
- package/src/task/types.ts +6 -0
- package/src/tools/ask.ts +2 -0
- package/src/tools/index.ts +23 -1
- package/src/tools/skill.ts +153 -0
- package/src/utils/changelog.ts +67 -44
package/src/sdk.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type CredentialDisabledEvent,
|
|
13
13
|
type Message,
|
|
14
14
|
type Model,
|
|
15
|
+
type ProviderSessionState,
|
|
15
16
|
type SimpleStreamOptions,
|
|
16
17
|
streamSimple,
|
|
17
18
|
} from "@gajae-code/ai";
|
|
@@ -82,7 +83,7 @@ import {
|
|
|
82
83
|
obfuscateMessages,
|
|
83
84
|
SecretObfuscator,
|
|
84
85
|
} from "./secrets";
|
|
85
|
-
import { AgentSession } from "./session/agent-session";
|
|
86
|
+
import { AgentSession, type ForkContextSeed } from "./session/agent-session";
|
|
86
87
|
import { resolveAuthBrokerConfig } from "./session/auth-broker-config";
|
|
87
88
|
import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "./session/auth-storage";
|
|
88
89
|
import { type CustomMessage, convertToLlm } from "./session/messages";
|
|
@@ -320,6 +321,10 @@ export interface CreateAgentSessionOptions {
|
|
|
320
321
|
* `@opentelemetry/api` package returns a no-op tracer in that case.
|
|
321
322
|
*/
|
|
322
323
|
telemetry?: AgentTelemetryConfig;
|
|
324
|
+
/** Optional fork-context seed used to initialize a child session before its first prompt. */
|
|
325
|
+
forkContextSeed?: ForkContextSeed;
|
|
326
|
+
/** Optional provider state override. Fork-context children should omit this by default. */
|
|
327
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
323
328
|
}
|
|
324
329
|
|
|
325
330
|
/** Result from createAgentSession */
|
|
@@ -861,7 +866,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
861
866
|
logger.time("sessionManager", () =>
|
|
862
867
|
SessionManager.create(cwd, SessionManager.getDefaultSessionDir(cwd, agentDir)),
|
|
863
868
|
);
|
|
864
|
-
const
|
|
869
|
+
const logicalSessionId = sessionManager.getSessionId();
|
|
870
|
+
const providerSessionId = options.providerSessionId ?? options.forkContextSeed?.cacheIdentity ?? logicalSessionId;
|
|
865
871
|
const modelApiKeyAvailability = new Map<string, boolean>();
|
|
866
872
|
const getModelAvailabilityKey = (candidate: Model): string =>
|
|
867
873
|
`${candidate.provider}\u0000${candidate.baseUrl ?? ""}`;
|
|
@@ -1153,7 +1159,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1153
1159
|
trackEvalExecution: (execution, abortController) =>
|
|
1154
1160
|
session ? session.trackEvalExecution(execution, abortController) : execution,
|
|
1155
1161
|
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
1162
|
+
getActiveSkillState: () => session?.getActiveSkillState(),
|
|
1163
|
+
getActiveSkillPhase: () => session?.getActiveSkillPhase(),
|
|
1156
1164
|
getHindsightSessionState: () => session?.getHindsightSessionState(),
|
|
1165
|
+
get model() {
|
|
1166
|
+
return agent?.state.model ?? model;
|
|
1167
|
+
},
|
|
1157
1168
|
getAgentId: () => resolvedAgentId,
|
|
1158
1169
|
getToolByName: name => session?.getToolByName(name),
|
|
1159
1170
|
agentRegistry,
|
|
@@ -1195,6 +1206,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1195
1206
|
attribution: "agent",
|
|
1196
1207
|
timestamp: Date.now(),
|
|
1197
1208
|
}),
|
|
1209
|
+
sendCustomMessage: (msg, opts) => session.sendCustomMessage(msg, opts),
|
|
1198
1210
|
peekQueueInvoker: () => session.peekQueueInvoker(),
|
|
1199
1211
|
peekStandingResolveHandler: () => session.peekStandingResolveHandler(),
|
|
1200
1212
|
setStandingResolveHandler: handler => session.setStandingResolveHandler(handler),
|
|
@@ -1210,6 +1222,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1210
1222
|
authStorage,
|
|
1211
1223
|
modelRegistry,
|
|
1212
1224
|
getTelemetry: () => agent?.telemetry,
|
|
1225
|
+
buildForkContextSeed: forkOptions => session.buildForkContextSeed(forkOptions),
|
|
1213
1226
|
};
|
|
1214
1227
|
|
|
1215
1228
|
// Wire process-wide internal URL singletons owned by their real classes.
|
|
@@ -1719,6 +1732,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1719
1732
|
const preferOpenAICodexWebsockets =
|
|
1720
1733
|
openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
|
|
1721
1734
|
const serviceTierSetting = settings.get("serviceTier");
|
|
1735
|
+
const retrySettings = settings.getGroup("retry");
|
|
1722
1736
|
|
|
1723
1737
|
const initialServiceTier = hasServiceTierEntry
|
|
1724
1738
|
? existingSession.serviceTier
|
|
@@ -1726,17 +1740,43 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1726
1740
|
? undefined
|
|
1727
1741
|
: serviceTierSetting;
|
|
1728
1742
|
|
|
1743
|
+
const appendOnlyContext =
|
|
1744
|
+
model && resolveAppendOnlyMode(settings.get("provider.appendOnlyContext"), model.provider)
|
|
1745
|
+
? new AppendOnlyContextManager()
|
|
1746
|
+
: undefined;
|
|
1747
|
+
if (appendOnlyContext && options.forkContextSeed && !hasExistingSession) {
|
|
1748
|
+
if (options.forkContextSeed.appendOnlyPrefixSnapshot) {
|
|
1749
|
+
(
|
|
1750
|
+
appendOnlyContext.prefix as typeof appendOnlyContext.prefix & {
|
|
1751
|
+
importSnapshot(
|
|
1752
|
+
snapshot: NonNullable<ForkContextSeed["appendOnlyPrefixSnapshot"]>,
|
|
1753
|
+
options: { intentTracing: boolean },
|
|
1754
|
+
): void;
|
|
1755
|
+
}
|
|
1756
|
+
).importSnapshot(options.forkContextSeed.appendOnlyPrefixSnapshot, { intentTracing: !!intentField });
|
|
1757
|
+
}
|
|
1758
|
+
(
|
|
1759
|
+
appendOnlyContext as AppendOnlyContextManager & {
|
|
1760
|
+
seedNormalizedMessages(messages: readonly Message[]): void;
|
|
1761
|
+
}
|
|
1762
|
+
).seedNormalizedMessages(options.forkContextSeed.messages);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1729
1765
|
agent = new Agent({
|
|
1730
1766
|
initialState: {
|
|
1731
1767
|
systemPrompt,
|
|
1732
1768
|
model,
|
|
1733
1769
|
thinkingLevel: toReasoningEffort(thinkingLevel),
|
|
1734
1770
|
tools: initialTools,
|
|
1771
|
+
...(options.forkContextSeed && !hasExistingSession
|
|
1772
|
+
? { messages: options.forkContextSeed.agentMessages }
|
|
1773
|
+
: {}),
|
|
1735
1774
|
},
|
|
1736
1775
|
convertToLlm: convertToLlmFinal,
|
|
1737
1776
|
onPayload,
|
|
1738
1777
|
onResponse,
|
|
1739
|
-
sessionId:
|
|
1778
|
+
sessionId: logicalSessionId,
|
|
1779
|
+
providerSessionId,
|
|
1740
1780
|
transformContext,
|
|
1741
1781
|
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
1742
1782
|
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
@@ -1750,19 +1790,23 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1750
1790
|
repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
|
|
1751
1791
|
serviceTier: initialServiceTier,
|
|
1752
1792
|
hideThinkingSummary: settings.get("hideThinkingBlock"),
|
|
1793
|
+
maxRetryDelayMs: retrySettings.maxDelayMs,
|
|
1794
|
+
requestMaxRetries: retrySettings.requestMaxRetries,
|
|
1795
|
+
streamMaxRetries: retrySettings.streamMaxRetries,
|
|
1753
1796
|
kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
|
|
1754
1797
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1755
1798
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
1756
1799
|
getApiKey: async provider => {
|
|
1757
1800
|
// Read agent.sessionId at call time so credential selection stays aligned
|
|
1758
1801
|
// with metadataResolver after /new, fork, resume, or branch switches.
|
|
1759
|
-
const key = await modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
|
|
1802
|
+
const key = await modelRegistry.getApiKeyForProvider(provider, agent.providerSessionId ?? agent.sessionId);
|
|
1760
1803
|
if (!key) {
|
|
1761
1804
|
throw new Error(`No API key found for provider "${provider}"`);
|
|
1762
1805
|
}
|
|
1763
1806
|
return key;
|
|
1764
1807
|
},
|
|
1765
|
-
getAuthCredentialType: provider =>
|
|
1808
|
+
getAuthCredentialType: provider =>
|
|
1809
|
+
modelRegistry.getSessionCredentialType(provider, agent.providerSessionId ?? agent.sessionId),
|
|
1766
1810
|
streamFn: (streamModel, context, streamOptions) =>
|
|
1767
1811
|
streamSimple(streamModel, context, {
|
|
1768
1812
|
...streamOptions,
|
|
@@ -1793,11 +1837,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1793
1837
|
intentTracing: !!intentField,
|
|
1794
1838
|
getToolChoice: () => session?.nextToolChoice(),
|
|
1795
1839
|
telemetry: options.telemetry,
|
|
1796
|
-
appendOnlyContext
|
|
1797
|
-
? resolveAppendOnlyMode(settings.get("provider.appendOnlyContext"), model.provider)
|
|
1798
|
-
? new AppendOnlyContextManager()
|
|
1799
|
-
: undefined
|
|
1800
|
-
: undefined,
|
|
1840
|
+
appendOnlyContext,
|
|
1801
1841
|
});
|
|
1802
1842
|
|
|
1803
1843
|
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
@@ -1836,6 +1876,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1836
1876
|
skillWarnings,
|
|
1837
1877
|
skillsSettings: settings.getGroup("skills"),
|
|
1838
1878
|
modelRegistry,
|
|
1879
|
+
taskDepth,
|
|
1839
1880
|
toolRegistry,
|
|
1840
1881
|
transformContext,
|
|
1841
1882
|
onPayload,
|
|
@@ -1868,6 +1909,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1868
1909
|
agentId: resolvedAgentId,
|
|
1869
1910
|
agentRegistry,
|
|
1870
1911
|
providerSessionId: options.providerSessionId,
|
|
1912
|
+
providerCacheSessionId: providerSessionId,
|
|
1913
|
+
forkContextSeed: options.forkContextSeed,
|
|
1914
|
+
providerSessionState: options.providerSessionState,
|
|
1871
1915
|
});
|
|
1872
1916
|
hasSession = true;
|
|
1873
1917
|
if (asyncJobManager) {
|
|
@@ -28,8 +28,10 @@ import {
|
|
|
28
28
|
type AgentTool,
|
|
29
29
|
AppendOnlyContextManager,
|
|
30
30
|
resolveTelemetry,
|
|
31
|
+
type StablePrefixSnapshot,
|
|
31
32
|
ThinkingLevel,
|
|
32
33
|
} from "@gajae-code/agent-core";
|
|
34
|
+
import { normalizeMessagesForProvider } from "@gajae-code/agent-core/agent-loop";
|
|
33
35
|
import {
|
|
34
36
|
AUTO_HANDOFF_THRESHOLD_FOCUS,
|
|
35
37
|
CompactionCancelledError,
|
|
@@ -75,6 +77,33 @@ import {
|
|
|
75
77
|
resolveServiceTier,
|
|
76
78
|
streamSimple,
|
|
77
79
|
} from "@gajae-code/ai";
|
|
80
|
+
|
|
81
|
+
export interface ForkContextSeedMetadata {
|
|
82
|
+
sourceSessionId: string;
|
|
83
|
+
parentMessageCount: number;
|
|
84
|
+
includedMessages: number;
|
|
85
|
+
skippedMessages: number;
|
|
86
|
+
approximateTokens: number;
|
|
87
|
+
maxMessages: number;
|
|
88
|
+
maxTokens: number;
|
|
89
|
+
skippedReasons: Record<string, number>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ForkContextSeed {
|
|
93
|
+
messages: Message[];
|
|
94
|
+
agentMessages: AgentMessage[];
|
|
95
|
+
metadata: ForkContextSeedMetadata;
|
|
96
|
+
cacheIdentity?: string;
|
|
97
|
+
appendOnlyPrefixSnapshot?: StablePrefixSnapshot;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ForkContextSeedOptions {
|
|
101
|
+
maxMessages: number;
|
|
102
|
+
maxTokens: number;
|
|
103
|
+
cacheIdentity?: string;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}
|
|
106
|
+
|
|
78
107
|
import { MacOSPowerAssertion } from "@gajae-code/natives";
|
|
79
108
|
import {
|
|
80
109
|
extractRetryHint,
|
|
@@ -168,7 +197,7 @@ import {
|
|
|
168
197
|
} from "../runtime-mcp/discoverable-tool-metadata";
|
|
169
198
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
170
199
|
import { formatNoCredentialOnboardingError, formatNoModelOnboardingError } from "../setup/model-onboarding-guidance";
|
|
171
|
-
import { isCanonicalGjcWorkflowSkill
|
|
200
|
+
import { isCanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
172
201
|
import { assertDeepInterviewMutationAllowed } from "../skill-state/deep-interview-mutation-guard";
|
|
173
202
|
import { invalidateHostMetadata } from "../ssh/connection-manager";
|
|
174
203
|
import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
|
|
@@ -246,6 +275,13 @@ export type AgentSessionEvent =
|
|
|
246
275
|
| { type: "thinking_level_changed"; thinkingLevel: ThinkingLevel | undefined }
|
|
247
276
|
| { type: "goal_updated"; goal: Goal | null; state?: GoalModeState };
|
|
248
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Safe path component pattern used to validate session-id segments before
|
|
280
|
+
* joining them into `.gjc/state` paths. Mirrors the regex used by the
|
|
281
|
+
* `gjc state` runtime selector resolver.
|
|
282
|
+
*/
|
|
283
|
+
const SAFE_PATH_COMPONENT = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
|
|
284
|
+
|
|
249
285
|
/** Listener function for agent session events */
|
|
250
286
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
251
287
|
export type AsyncJobSnapshotItem = Pick<AsyncJob, "id" | "type" | "status" | "label" | "startTime">;
|
|
@@ -283,6 +319,8 @@ export interface AgentSessionConfig {
|
|
|
283
319
|
skillsSettings?: SkillsSettings;
|
|
284
320
|
/** Model registry for API key resolution and model discovery */
|
|
285
321
|
modelRegistry: ModelRegistry;
|
|
322
|
+
/** Task recursion depth for nested sessions. Top-level sessions use 0. */
|
|
323
|
+
taskDepth?: number;
|
|
286
324
|
/** Tool registry for LSP and settings */
|
|
287
325
|
toolRegistry?: Map<string, AgentTool>;
|
|
288
326
|
/** Current session pre-LLM message transform pipeline */
|
|
@@ -331,6 +369,10 @@ export interface AgentSessionConfig {
|
|
|
331
369
|
* **MUST NOT** dispose it on their own teardown.
|
|
332
370
|
*/
|
|
333
371
|
ownedAsyncJobManager?: AsyncJobManager;
|
|
372
|
+
/** Optional fork-context seed used to initialize a child session before its first prompt. */
|
|
373
|
+
forkContextSeed?: ForkContextSeed;
|
|
374
|
+
/** Optional provider state override. Fork-context children should omit this by default. */
|
|
375
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
334
376
|
/** Agent identity (registry id like "0-Main" or "3-Alice") used for IRC routing. */
|
|
335
377
|
agentId?: string;
|
|
336
378
|
/** Shared agent registry (for forwarding IRC observations to the main session UI). */
|
|
@@ -342,6 +384,8 @@ export interface AgentSessionConfig {
|
|
|
342
384
|
* so that credential sticky selection is consistent with the session's streaming calls.
|
|
343
385
|
*/
|
|
344
386
|
providerSessionId?: string;
|
|
387
|
+
/** Optional provider-facing cache identity, distinct from logical session identity. */
|
|
388
|
+
providerCacheSessionId?: string;
|
|
345
389
|
}
|
|
346
390
|
|
|
347
391
|
/** Options for AgentSession.prompt() */
|
|
@@ -749,6 +793,7 @@ export class AgentSession {
|
|
|
749
793
|
readonly agent: Agent;
|
|
750
794
|
readonly sessionManager: SessionManager;
|
|
751
795
|
readonly settings: Settings;
|
|
796
|
+
readonly taskDepth: number;
|
|
752
797
|
readonly yieldQueue: YieldQueue;
|
|
753
798
|
|
|
754
799
|
#powerAssertion: MacOSPowerAssertion | undefined;
|
|
@@ -836,6 +881,7 @@ export class AgentSession {
|
|
|
836
881
|
#agentId: string | undefined;
|
|
837
882
|
#agentRegistry: AgentRegistry | undefined;
|
|
838
883
|
#providerSessionId: string | undefined;
|
|
884
|
+
#providerCacheSessionId: string | undefined;
|
|
839
885
|
#isDisposed = false;
|
|
840
886
|
// Extension system
|
|
841
887
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
@@ -1007,6 +1053,7 @@ export class AgentSession {
|
|
|
1007
1053
|
this.agent = config.agent;
|
|
1008
1054
|
this.sessionManager = config.sessionManager;
|
|
1009
1055
|
this.settings = config.settings;
|
|
1056
|
+
this.taskDepth = config.taskDepth ?? 0;
|
|
1010
1057
|
// Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
|
|
1011
1058
|
this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
|
|
1012
1059
|
this.#ownedAsyncJobManager = config.ownedAsyncJobManager;
|
|
@@ -1020,6 +1067,9 @@ export class AgentSession {
|
|
|
1020
1067
|
this.#customCommands = config.customCommands ?? [];
|
|
1021
1068
|
this.#skillsSettings = config.skillsSettings;
|
|
1022
1069
|
this.#modelRegistry = config.modelRegistry;
|
|
1070
|
+
if (config.providerSessionState) {
|
|
1071
|
+
this.#providerSessionState = config.providerSessionState;
|
|
1072
|
+
}
|
|
1023
1073
|
this.#validateRetryFallbackChains();
|
|
1024
1074
|
this.#toolRegistry = config.toolRegistry ?? new Map();
|
|
1025
1075
|
this.#requestedToolNames = config.requestedToolNames;
|
|
@@ -1099,6 +1149,7 @@ export class AgentSession {
|
|
|
1099
1149
|
this.#agentId = config.agentId;
|
|
1100
1150
|
this.#agentRegistry = config.agentRegistry;
|
|
1101
1151
|
this.#providerSessionId = config.providerSessionId;
|
|
1152
|
+
this.#providerCacheSessionId = config.providerCacheSessionId;
|
|
1102
1153
|
this.agent.setAssistantMessageEventInterceptor((message, assistantMessageEvent) => {
|
|
1103
1154
|
const event: AgentEvent = {
|
|
1104
1155
|
type: "message_update",
|
|
@@ -1196,6 +1247,46 @@ export class AgentSession {
|
|
|
1196
1247
|
return this.#toolChoiceQueue;
|
|
1197
1248
|
}
|
|
1198
1249
|
|
|
1250
|
+
/** Current skill prompt executing in this session, if any. */
|
|
1251
|
+
getActiveSkillState(): { skill: string; session_id?: string } | undefined {
|
|
1252
|
+
if (!this.#activeSkillState) return undefined;
|
|
1253
|
+
return {
|
|
1254
|
+
skill: this.#activeSkillState.skill,
|
|
1255
|
+
...(this.#activeSkillState.sessionId ? { session_id: this.#activeSkillState.sessionId } : {}),
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/** Best-effort accessor for the active skill's `current_phase` field from
|
|
1260
|
+
* its persisted mode-state file. Used by the `skill` tool to enforce the
|
|
1261
|
+
* terminal-phase chain guard. Returns undefined when no active skill is
|
|
1262
|
+
* recorded or the mode-state file is missing/unreadable; callers should
|
|
1263
|
+
* treat undefined as a non-terminal phase (refuses to chain). */
|
|
1264
|
+
getActiveSkillPhase(): string | undefined {
|
|
1265
|
+
const active = this.#activeSkillState;
|
|
1266
|
+
if (!active) return undefined;
|
|
1267
|
+
// Path safety: refuse to read mode-state files when the skill or
|
|
1268
|
+
// session-id are not safe path components. The `skill` tool
|
|
1269
|
+
// interprets undefined as a non-terminal phase, so chaining is
|
|
1270
|
+
// refused — there is no risk of bypassing the guard via a custom
|
|
1271
|
+
// skill name with `..` or a session-id with separators.
|
|
1272
|
+
if (!isCanonicalGjcWorkflowSkill(active.skill)) return undefined;
|
|
1273
|
+
if (active.sessionId !== undefined && !SAFE_PATH_COMPONENT.test(active.sessionId)) {
|
|
1274
|
+
return undefined;
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1277
|
+
const stateDir = path.join(this.sessionManager.getCwd(), ".gjc", "state");
|
|
1278
|
+
const segments = active.sessionId
|
|
1279
|
+
? [stateDir, "sessions", encodeURIComponent(active.sessionId).replaceAll(".", "%2E")]
|
|
1280
|
+
: [stateDir];
|
|
1281
|
+
const filePath = path.join(...segments, `${active.skill}-state.json`);
|
|
1282
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1283
|
+
const parsed = JSON.parse(raw) as { current_phase?: unknown };
|
|
1284
|
+
return typeof parsed.current_phase === "string" ? parsed.current_phase : undefined;
|
|
1285
|
+
} catch {
|
|
1286
|
+
return undefined;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1199
1290
|
/** Peek the in-flight directive's invocation handler for use by the resolve tool. */
|
|
1200
1291
|
peekQueueInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined {
|
|
1201
1292
|
return this.#toolChoiceQueue.peekInFlightInvoker();
|
|
@@ -1219,6 +1310,100 @@ export class AgentSession {
|
|
|
1219
1310
|
return this.#providerSessionState;
|
|
1220
1311
|
}
|
|
1221
1312
|
|
|
1313
|
+
async buildForkContextSeed(options: ForkContextSeedOptions): Promise<ForkContextSeed> {
|
|
1314
|
+
const transformedMessages = await this.#transformContext([...this.messages], options.signal);
|
|
1315
|
+
const convertedMessages = await this.#convertToLlm(transformedMessages);
|
|
1316
|
+
const providerMessages = this.model
|
|
1317
|
+
? normalizeMessagesForProvider(convertedMessages, this.model)
|
|
1318
|
+
: convertedMessages;
|
|
1319
|
+
const maxMessages = Math.min(500, Math.max(0, Math.trunc(options.maxMessages)));
|
|
1320
|
+
const maxTokens = Math.max(0, Math.trunc(options.maxTokens));
|
|
1321
|
+
const selected: Message[] = [];
|
|
1322
|
+
const skippedReasons: Record<string, number> = {};
|
|
1323
|
+
let skippedMessages = 0;
|
|
1324
|
+
let approximateTokens = 0;
|
|
1325
|
+
|
|
1326
|
+
const recordSkip = (reason: string) => {
|
|
1327
|
+
skippedMessages++;
|
|
1328
|
+
skippedReasons[reason] = (skippedReasons[reason] ?? 0) + 1;
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
const sanitizeMessage = (message: Message): Message | undefined => {
|
|
1332
|
+
if (message.role === "developer") {
|
|
1333
|
+
recordSkip("developer-role");
|
|
1334
|
+
return undefined;
|
|
1335
|
+
}
|
|
1336
|
+
if (message.role === "toolResult") {
|
|
1337
|
+
recordSkip("tool-result-role");
|
|
1338
|
+
return undefined;
|
|
1339
|
+
}
|
|
1340
|
+
if (message.role !== "user" && message.role !== "assistant") {
|
|
1341
|
+
recordSkip("unsupported-role");
|
|
1342
|
+
return undefined;
|
|
1343
|
+
}
|
|
1344
|
+
const cloned = structuredClone(message) as Message;
|
|
1345
|
+
if ("providerPayload" in cloned) {
|
|
1346
|
+
delete (cloned as { providerPayload?: unknown }).providerPayload;
|
|
1347
|
+
}
|
|
1348
|
+
if (Array.isArray(cloned.content)) {
|
|
1349
|
+
const sanitizedContent: TextContent[] = [];
|
|
1350
|
+
for (const block of cloned.content) {
|
|
1351
|
+
if (block.type === "text") {
|
|
1352
|
+
sanitizedContent.push(block);
|
|
1353
|
+
} else if (block.type === "image") {
|
|
1354
|
+
sanitizedContent.push({ type: "text", text: "[Image omitted from fork-context seed]" });
|
|
1355
|
+
} else if (block.type !== "thinking") {
|
|
1356
|
+
recordSkip(`unsupported-content-${block.type}`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return { ...cloned, content: sanitizedContent } as Message;
|
|
1360
|
+
}
|
|
1361
|
+
return cloned;
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
for (let i = providerMessages.length - 1; i >= 0; i--) {
|
|
1365
|
+
if (selected.length >= maxMessages) {
|
|
1366
|
+
recordSkip("message-limit");
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
const sanitized = sanitizeMessage(providerMessages[i]!);
|
|
1370
|
+
if (!sanitized) continue;
|
|
1371
|
+
const messageTokens = estimateTokens(sanitized);
|
|
1372
|
+
if (maxTokens > 0 && approximateTokens + messageTokens > maxTokens) {
|
|
1373
|
+
recordSkip("token-limit");
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
selected.unshift(sanitized);
|
|
1377
|
+
approximateTokens += messageTokens;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const messages = selected;
|
|
1381
|
+
let appendOnlyPrefixSnapshot: StablePrefixSnapshot | undefined;
|
|
1382
|
+
const appendOnly = this.agent.appendOnlyContext;
|
|
1383
|
+
if (appendOnly) {
|
|
1384
|
+
if (!appendOnly.prefix.built) {
|
|
1385
|
+
appendOnly.prefix.build(this.agent.state, { intentTracing: this.agent.intentTracing });
|
|
1386
|
+
}
|
|
1387
|
+
appendOnlyPrefixSnapshot = appendOnly.prefix.exportSnapshot() ?? undefined;
|
|
1388
|
+
}
|
|
1389
|
+
return {
|
|
1390
|
+
messages,
|
|
1391
|
+
agentMessages: messages.map(message => structuredClone(message) as AgentMessage),
|
|
1392
|
+
metadata: {
|
|
1393
|
+
sourceSessionId: this.sessionId,
|
|
1394
|
+
parentMessageCount: providerMessages.length,
|
|
1395
|
+
includedMessages: messages.length,
|
|
1396
|
+
skippedMessages,
|
|
1397
|
+
approximateTokens,
|
|
1398
|
+
maxMessages,
|
|
1399
|
+
maxTokens,
|
|
1400
|
+
skippedReasons,
|
|
1401
|
+
},
|
|
1402
|
+
cacheIdentity: options.cacheIdentity ?? this.sessionId,
|
|
1403
|
+
appendOnlyPrefixSnapshot,
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1222
1407
|
getHindsightSessionState(): HindsightSessionState | undefined {
|
|
1223
1408
|
return this.#hindsightSessionState;
|
|
1224
1409
|
}
|
|
@@ -1555,11 +1740,7 @@ export class AgentSession {
|
|
|
1555
1740
|
}
|
|
1556
1741
|
|
|
1557
1742
|
const targetAssistantIndex = this.#findTtsrAssistantIndex(targetMessageTimestamp);
|
|
1558
|
-
if (
|
|
1559
|
-
!this.#ttsrAbortPending ||
|
|
1560
|
-
this.#promptGeneration !== generation ||
|
|
1561
|
-
targetAssistantIndex === -1
|
|
1562
|
-
) {
|
|
1743
|
+
if (!this.#ttsrAbortPending || this.#promptGeneration !== generation) {
|
|
1563
1744
|
this.#ttsrAbortPending = false;
|
|
1564
1745
|
this.#pendingTtsrInjections = [];
|
|
1565
1746
|
this.#perToolTtsrInjections.clear();
|
|
@@ -1569,8 +1750,8 @@ export class AgentSession {
|
|
|
1569
1750
|
this.#ttsrAbortPending = false;
|
|
1570
1751
|
this.#perToolTtsrInjections.clear();
|
|
1571
1752
|
const ttsrSettings = this.#ttsrManager?.getSettings();
|
|
1572
|
-
if (ttsrSettings?.contextMode === "discard") {
|
|
1573
|
-
// Remove the partial/aborted assistant turn from agent state
|
|
1753
|
+
if (ttsrSettings?.contextMode === "discard" && targetAssistantIndex !== -1) {
|
|
1754
|
+
// Remove the partial/aborted assistant turn from agent state when it was persisted.
|
|
1574
1755
|
this.agent.replaceMessages(this.agent.state.messages.slice(0, targetAssistantIndex));
|
|
1575
1756
|
}
|
|
1576
1757
|
// Inject TTSR rules as system reminder before retry
|
|
@@ -2757,6 +2938,7 @@ export class AgentSession {
|
|
|
2757
2938
|
#syncAgentSessionId(sessionId?: string): void {
|
|
2758
2939
|
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
2759
2940
|
this.agent.sessionId = sid;
|
|
2941
|
+
this.agent.providerSessionId = this.#providerCacheSessionId ?? sid;
|
|
2760
2942
|
this.agent.setMetadataResolver((provider: string) =>
|
|
2761
2943
|
buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage),
|
|
2762
2944
|
);
|
|
@@ -4092,18 +4274,19 @@ export class AgentSession {
|
|
|
4092
4274
|
const details = message.details;
|
|
4093
4275
|
if (!details || typeof details !== "object") return;
|
|
4094
4276
|
const name = (details as { name?: unknown }).name;
|
|
4095
|
-
if (typeof name !== "string" || !
|
|
4096
|
-
const
|
|
4277
|
+
if (typeof name !== "string" || !name.trim()) return;
|
|
4278
|
+
const skill = name.trim();
|
|
4097
4279
|
const sessionId = this.sessionManager.getSessionId();
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4280
|
+
// Canonical GJC workflow skills (deep-interview, ralplan, ultragoal, team)
|
|
4281
|
+
// own their `.gjc/state/skill-active-state.json` row through the
|
|
4282
|
+
// `gjc state handoff` and `gjc state clear` runtime verbs. The prompt
|
|
4283
|
+
// observer here used to overwrite the row with `phase: running` and
|
|
4284
|
+
// later remove it with `active:false`, which clobbered handoff lineage
|
|
4285
|
+
// (`handoff_from`/`handoff_at`) and made the HUD inconsistent with
|
|
4286
|
+
// mode-state. The observational filesystem write is now skipped for
|
|
4287
|
+
// canonical skills; the in-memory `#activeSkillState` tracking below
|
|
4288
|
+
// keeps `getActiveSkillState` accurate for the chain guard.
|
|
4289
|
+
this.#activeSkillState = active ? { skill, sessionId } : undefined;
|
|
4107
4290
|
}
|
|
4108
4291
|
|
|
4109
4292
|
async #syncSkillPromptActiveStateSafely(
|
|
@@ -6147,7 +6330,7 @@ export class AgentSession {
|
|
|
6147
6330
|
|
|
6148
6331
|
#closeCodexProviderSessionsForHistoryRewrite(): void {
|
|
6149
6332
|
const currentModel = this.model;
|
|
6150
|
-
if (
|
|
6333
|
+
if (currentModel?.api !== "openai-codex-responses") return;
|
|
6151
6334
|
this.#closeProviderSessionsForModelSwitch(currentModel, currentModel);
|
|
6152
6335
|
}
|
|
6153
6336
|
|
|
@@ -8228,7 +8411,7 @@ export class AgentSession {
|
|
|
8228
8411
|
const previousSessionFile = this.sessionFile;
|
|
8229
8412
|
const selectedEntry = this.sessionManager.getEntry(entryId);
|
|
8230
8413
|
|
|
8231
|
-
if (
|
|
8414
|
+
if (selectedEntry?.type !== "message" || selectedEntry.message.role !== "user") {
|
|
8232
8415
|
throw new Error("Invalid entry ID for branching");
|
|
8233
8416
|
}
|
|
8234
8417
|
|
|
@@ -176,6 +176,8 @@ export interface SessionInitEntry extends SessionEntryBase {
|
|
|
176
176
|
tools: string[];
|
|
177
177
|
/** Output schema if structured output was requested */
|
|
178
178
|
outputSchema?: unknown;
|
|
179
|
+
/** Fork-context seed metadata for subagent debugging/replay. */
|
|
180
|
+
forkContext?: unknown;
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
/** Mode change entry - tracks agent mode transitions (e.g. plan mode). */
|
|
@@ -2714,7 +2716,13 @@ export class SessionManager {
|
|
|
2714
2716
|
}
|
|
2715
2717
|
|
|
2716
2718
|
/** Append session init metadata (for subagent debugging/replay). Returns entry id. */
|
|
2717
|
-
appendSessionInit(init: {
|
|
2719
|
+
appendSessionInit(init: {
|
|
2720
|
+
systemPrompt: string;
|
|
2721
|
+
task: string;
|
|
2722
|
+
tools: string[];
|
|
2723
|
+
outputSchema?: unknown;
|
|
2724
|
+
forkContext?: unknown;
|
|
2725
|
+
}): string {
|
|
2718
2726
|
const entry: SessionInitEntry = {
|
|
2719
2727
|
type: "session_init",
|
|
2720
2728
|
id: generateId(this.#byId),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const MODEL_ONBOARDING_API_PROVIDER_COMMAND =
|
|
2
2
|
"/provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <model>";
|
|
3
|
+
export const MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND = "/provider add --preset <minimax|minimax-cn|glm>";
|
|
3
4
|
|
|
4
5
|
export const MODEL_ONBOARDING_SETUP_COMMAND = "gjc setup provider";
|
|
5
6
|
export const MODEL_ONBOARDING_OAUTH_COMMAND = "/provider login [provider-id] or /login [provider-id]";
|
|
@@ -9,14 +10,15 @@ export function formatModelOnboardingGuidance(): string {
|
|
|
9
10
|
"Model selection only shows configured providers.",
|
|
10
11
|
"Assignment targets are DEFAULT plus the GJC role agents: EXECUTOR, ARCHITECT, PLANNER, and CRITIC.",
|
|
11
12
|
"Legacy model-role aliases are compatibility-only and are not shown as assignment targets.",
|
|
12
|
-
`
|
|
13
|
+
`Provider presets: ${MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND} --preset <preset>).`,
|
|
14
|
+
`API-compatible custom providers: ${MODEL_ONBOARDING_API_PROVIDER_COMMAND}.`,
|
|
13
15
|
`OAuth/subscription providers: ${MODEL_ONBOARDING_OAUTH_COMMAND}.`,
|
|
14
16
|
"Then run /model to select a configured model or assign it to a target.",
|
|
15
17
|
].join("\n");
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export function formatModelOnboardingInlineHint(): string {
|
|
19
|
-
return `Add API
|
|
21
|
+
return `Add MiniMax/GLM presets with ${MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND}; custom API providers with ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND}); OAuth/subscription with ${MODEL_ONBOARDING_OAUTH_COMMAND}; then run /model for DEFAULT, EXECUTOR, ARCHITECT, PLANNER, and CRITIC.`;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export function formatNoModelOnboardingError(): string {
|
|
@@ -27,7 +29,8 @@ export function formatNoCredentialOnboardingError(providerId: string): string {
|
|
|
27
29
|
return [
|
|
28
30
|
`No credentials found for ${providerId}.`,
|
|
29
31
|
"",
|
|
30
|
-
`For
|
|
32
|
+
`For MiniMax/GLM presets, configure credentials with ${MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND} --preset <preset>).`,
|
|
33
|
+
`For custom API-compatible providers, use ${MODEL_ONBOARDING_API_PROVIDER_COMMAND}.`,
|
|
31
34
|
`For OAuth/subscription providers, use ${MODEL_ONBOARDING_OAUTH_COMMAND}.`,
|
|
32
35
|
"Then run /model to select a configured model or assign it to DEFAULT, EXECUTOR, ARCHITECT, PLANNER, or CRITIC.",
|
|
33
36
|
].join("\n");
|