@shawnstack/quickforge 1.3.18 → 1.3.19
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/README.md +10 -10
- package/bin/quickforge.mjs +258 -49
- package/dist/assets/anthropic-Bj3HAZgj.js +39 -0
- package/dist/assets/azure-openai-responses-IdZZrSrI.js +1 -0
- package/dist/assets/github-copilot-headers-CMb2BbzT.js +1 -0
- package/dist/assets/google-Brt_lS1J.js +1 -0
- package/dist/assets/{google-shared-XhYUKiGZ.js → google-shared-CLc4ziON.js} +3 -3
- package/dist/assets/google-vertex-B6HsoZ34.js +1 -0
- package/dist/assets/{index-Dm7aEWvT.js → index-D0CVLdX_.js} +525 -489
- package/dist/assets/index-D0W9hAl_.css +3 -0
- package/dist/assets/{mistral-DxhS4Wkn.js → mistral-CenXqwPz.js} +3 -3
- package/dist/assets/openai-codex-responses-D9ffGwbj.js +7 -0
- package/dist/assets/openai-completions-eWdeSGBG.js +5 -0
- package/dist/assets/openai-responses-Cavpmjeu.js +1 -0
- package/dist/assets/{openai-responses-shared-f_P3e1nz.js → openai-responses-shared-DF3ZGaUx.js} +5 -3
- package/dist/assets/transform-messages-CmnxG9RB.js +1 -0
- package/dist/index.html +2 -2
- package/node_modules/@anthropic-ai/sdk/CHANGELOG.md +34 -0
- package/node_modules/@anthropic-ai/sdk/bin/migration-config.json +185 -0
- package/node_modules/@anthropic-ai/sdk/package.json +1 -1
- package/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +4 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +4 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/files.js +5 -5
- package/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +5 -5
- package/node_modules/@anthropic-ai/sdk/resources/beta/index.js +11 -9
- package/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +1 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.js +11 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.mjs +5 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.js +130 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.mjs +126 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.js +145 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.mjs +140 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.js +81 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.mjs +77 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.js +6 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.mjs +3 -0
- package/node_modules/@anthropic-ai/sdk/tools/memory/node.js +12 -5
- package/node_modules/@anthropic-ai/sdk/tools/memory/node.mjs +12 -5
- package/node_modules/@anthropic-ai/sdk/version.js +1 -1
- package/node_modules/@anthropic-ai/sdk/version.mjs +1 -1
- package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +5 -5
- package/node_modules/@aws-sdk/core/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
- package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
- package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
- package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
- package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
- package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
- package/node_modules/@aws-sdk/token-providers/package.json +3 -3
- package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
- package/node_modules/@mariozechner/pi-agent-core/README.md +14 -0
- package/node_modules/@mariozechner/pi-agent-core/dist/agent-loop.js +9 -0
- package/node_modules/@mariozechner/pi-agent-core/dist/agent.js +1 -1
- package/node_modules/@mariozechner/pi-agent-core/package.json +2 -2
- package/node_modules/@mariozechner/pi-ai/README.md +20 -31
- package/node_modules/@mariozechner/pi-ai/dist/env-api-keys.js +7 -0
- package/node_modules/@mariozechner/pi-ai/dist/index.js +2 -0
- package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +2420 -1213
- package/node_modules/@mariozechner/pi-ai/dist/models.js +28 -20
- package/node_modules/@mariozechner/pi-ai/dist/providers/amazon-bedrock.js +11 -11
- package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +43 -26
- package/node_modules/@mariozechner/pi-ai/dist/providers/azure-openai-responses.js +12 -6
- package/node_modules/@mariozechner/pi-ai/dist/providers/cloudflare.js +10 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-shared.js +4 -13
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-vertex.js +4 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/google.js +4 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/mistral.js +8 -7
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-codex-responses.js +296 -41
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js +169 -153
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses-shared.js +14 -1
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses.js +22 -8
- package/node_modules/@mariozechner/pi-ai/dist/providers/register-builtins.js +0 -18
- package/node_modules/@mariozechner/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@mariozechner/pi-ai/dist/session-resources.js +22 -0
- package/node_modules/@mariozechner/pi-ai/dist/utils/diagnostics.js +25 -0
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/index.js +0 -10
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js +25 -14
- package/node_modules/@mariozechner/pi-ai/dist/utils/overflow.js +14 -0
- package/node_modules/@mariozechner/pi-ai/package.json +2 -6
- package/package.json +3 -3
- package/server/agent-manager.mjs +279 -12
- package/server/auto-compaction.mjs +1 -2
- package/server/conversation-compaction.mjs +0 -5
- package/server/index.mjs +1 -0
- package/server/routes/static.mjs +1 -0
- package/server/routes/tools.mjs +3 -1
- package/server/session-utils.mjs +6 -1
- package/server/share-store.mjs +27 -4
- package/server/subagents.mjs +101 -0
- package/server/system-prompt.mjs +30 -1
- package/server/tools/definitions.mjs +18 -0
- package/server/tools/index.mjs +1013 -911
- package/dist/assets/anthropic-Ck2DxOfr.js +0 -39
- package/dist/assets/azure-openai-responses-DIoz5q4Z.js +0 -1
- package/dist/assets/github-copilot-headers-CrI0CIJ7.js +0 -1
- package/dist/assets/google-Dau-4ve_.js +0 -1
- package/dist/assets/google-gemini-cli-DttMmbGb.js +0 -2
- package/dist/assets/google-vertex-BeukMl44.js +0 -1
- package/dist/assets/index-DgJVElbv.css +0 -3
- package/dist/assets/openai-codex-responses-X3sTzNAa.js +0 -7
- package/dist/assets/openai-completions-CRB9Vm0w.js +0 -5
- package/dist/assets/openai-responses-DXluu3oi.js +0 -1
- package/dist/assets/transform-messages-CV4kCtBB.js +0 -1
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/LICENSE +0 -201
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/README.md +0 -62
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-cjs/index.js +0 -156
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/constants.js +0 -2
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js +0 -16
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromSso.js +0 -80
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js +0 -8
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js +0 -11
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js +0 -10
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/index.js +0 -4
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js +0 -5
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js +0 -7
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js +0 -7
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js +0 -8
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +0 -69
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-gemini-cli.js +0 -779
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-antigravity.js +0 -377
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-gemini-cli.js +0 -482
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function formatThrownValue(value) {
|
|
2
|
+
if (value instanceof Error)
|
|
3
|
+
return value.message || value.name;
|
|
4
|
+
if (typeof value === "string")
|
|
5
|
+
return value;
|
|
6
|
+
return String(value);
|
|
7
|
+
}
|
|
8
|
+
export function extractDiagnosticError(error) {
|
|
9
|
+
if (!(error instanceof Error))
|
|
10
|
+
return { name: "ThrownValue", message: formatThrownValue(error) };
|
|
11
|
+
const code = error.code;
|
|
12
|
+
return {
|
|
13
|
+
name: error.name || undefined,
|
|
14
|
+
message: error.message || error.name,
|
|
15
|
+
stack: error.stack,
|
|
16
|
+
code: typeof code === "string" || typeof code === "number" ? code : undefined,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createAssistantMessageDiagnostic(type, error, details) {
|
|
20
|
+
return { type, timestamp: Date.now(), error: extractDiagnosticError(error), details };
|
|
21
|
+
}
|
|
22
|
+
export function appendAssistantMessageDiagnostic(message, diagnostic) {
|
|
23
|
+
message.diagnostics = [...(message.diagnostics ?? []), diagnostic];
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -5,17 +5,11 @@
|
|
|
5
5
|
* for OAuth-based providers:
|
|
6
6
|
* - Anthropic (Claude Pro/Max)
|
|
7
7
|
* - GitHub Copilot
|
|
8
|
-
* - Google Cloud Code Assist (Gemini CLI)
|
|
9
|
-
* - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
10
8
|
*/
|
|
11
9
|
// Anthropic
|
|
12
10
|
export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
|
|
13
11
|
// GitHub Copilot
|
|
14
12
|
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
|
|
15
|
-
// Google Antigravity
|
|
16
|
-
export { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from "./google-antigravity.js";
|
|
17
|
-
// Google Gemini CLI
|
|
18
|
-
export { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli.js";
|
|
19
13
|
// OpenAI Codex (ChatGPT OAuth)
|
|
20
14
|
export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.js";
|
|
21
15
|
export * from "./types.js";
|
|
@@ -24,14 +18,10 @@ export * from "./types.js";
|
|
|
24
18
|
// ============================================================================
|
|
25
19
|
import { anthropicOAuthProvider } from "./anthropic.js";
|
|
26
20
|
import { githubCopilotOAuthProvider } from "./github-copilot.js";
|
|
27
|
-
import { antigravityOAuthProvider } from "./google-antigravity.js";
|
|
28
|
-
import { geminiCliOAuthProvider } from "./google-gemini-cli.js";
|
|
29
21
|
import { openaiCodexOAuthProvider } from "./openai-codex.js";
|
|
30
22
|
const BUILT_IN_OAUTH_PROVIDERS = [
|
|
31
23
|
anthropicOAuthProvider,
|
|
32
24
|
githubCopilotOAuthProvider,
|
|
33
|
-
geminiCliOAuthProvider,
|
|
34
|
-
antigravityOAuthProvider,
|
|
35
25
|
openaiCodexOAuthProvider,
|
|
36
26
|
];
|
|
37
27
|
const oauthProviderRegistry = new Map(BUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]));
|
|
@@ -84,13 +84,18 @@ async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_
|
|
|
84
84
|
});
|
|
85
85
|
if (!response.ok) {
|
|
86
86
|
const text = await response.text().catch(() => "");
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
return {
|
|
88
|
+
type: "failed",
|
|
89
|
+
status: response.status,
|
|
90
|
+
message: `OpenAI Codex token exchange failed (${response.status}): ${text || response.statusText}`,
|
|
91
|
+
};
|
|
89
92
|
}
|
|
90
93
|
const json = (await response.json());
|
|
91
94
|
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
return {
|
|
96
|
+
type: "failed",
|
|
97
|
+
message: `OpenAI Codex token exchange response missing fields: ${JSON.stringify(json)}`,
|
|
98
|
+
};
|
|
94
99
|
}
|
|
95
100
|
return {
|
|
96
101
|
type: "success",
|
|
@@ -112,13 +117,18 @@ async function refreshAccessToken(refreshToken) {
|
|
|
112
117
|
});
|
|
113
118
|
if (!response.ok) {
|
|
114
119
|
const text = await response.text().catch(() => "");
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
return {
|
|
121
|
+
type: "failed",
|
|
122
|
+
status: response.status,
|
|
123
|
+
message: `OpenAI Codex token refresh failed (${response.status}): ${text || response.statusText}`,
|
|
124
|
+
};
|
|
117
125
|
}
|
|
118
126
|
const json = (await response.json());
|
|
119
127
|
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
return {
|
|
129
|
+
type: "failed",
|
|
130
|
+
message: `OpenAI Codex token refresh response missing fields: ${JSON.stringify(json)}`,
|
|
131
|
+
};
|
|
122
132
|
}
|
|
123
133
|
return {
|
|
124
134
|
type: "success",
|
|
@@ -128,8 +138,10 @@ async function refreshAccessToken(refreshToken) {
|
|
|
128
138
|
};
|
|
129
139
|
}
|
|
130
140
|
catch (error) {
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
return {
|
|
142
|
+
type: "failed",
|
|
143
|
+
message: `OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
};
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
async function createAuthorizationFlow(originator = "pi") {
|
|
@@ -206,8 +218,7 @@ function startLocalOAuthServer(state) {
|
|
|
206
218
|
waitForCode: () => waitForCodePromise,
|
|
207
219
|
});
|
|
208
220
|
})
|
|
209
|
-
.on("error", (
|
|
210
|
-
console.error(`[openai-codex] Failed to bind http://${CALLBACK_HOST}:1455 (`, err.code, ") Falling back to manual paste.");
|
|
221
|
+
.on("error", (_err) => {
|
|
211
222
|
settleWait?.(null);
|
|
212
223
|
resolve({
|
|
213
224
|
close: () => {
|
|
@@ -316,7 +327,7 @@ export async function loginOpenAICodex(options) {
|
|
|
316
327
|
}
|
|
317
328
|
const tokenResult = await exchangeAuthorizationCode(code, verifier);
|
|
318
329
|
if (tokenResult.type !== "success") {
|
|
319
|
-
throw new Error(
|
|
330
|
+
throw new Error(tokenResult.message);
|
|
320
331
|
}
|
|
321
332
|
const accountId = getAccountId(tokenResult.access);
|
|
322
333
|
if (!accountId) {
|
|
@@ -339,7 +350,7 @@ export async function loginOpenAICodex(options) {
|
|
|
339
350
|
export async function refreshOpenAICodexToken(refreshToken) {
|
|
340
351
|
const result = await refreshAccessToken(refreshToken);
|
|
341
352
|
if (result.type !== "success") {
|
|
342
|
-
throw new Error(
|
|
353
|
+
throw new Error(result.message);
|
|
343
354
|
}
|
|
344
355
|
const accountId = getAccountId(result.access);
|
|
345
356
|
if (!accountId) {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* - Cerebras: "400/413 status code (no body)"
|
|
22
22
|
* - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
|
|
23
23
|
* - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow
|
|
24
|
+
* - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason "length"
|
|
25
|
+
* with output=0 (no room left to generate). Detected via stopReason "length" + zero output +
|
|
26
|
+
* input filling the context window.
|
|
24
27
|
* - Ollama: Some deployments truncate silently, others return errors like "prompt too long; exceeded max context length by X tokens"
|
|
25
28
|
*/
|
|
26
29
|
const OVERFLOW_PATTERNS = [
|
|
@@ -86,6 +89,8 @@ const NON_OVERFLOW_PATTERNS = [
|
|
|
86
89
|
* **Unreliable detection:**
|
|
87
90
|
* - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),
|
|
88
91
|
* sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.
|
|
92
|
+
* - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason "length" with
|
|
93
|
+
* output=0. Pass contextWindow param to detect via the "filled context + zero output" signal.
|
|
89
94
|
* - Ollama: May truncate input silently for some setups, but may also return explicit
|
|
90
95
|
* overflow errors that match the patterns above. Silent truncation still cannot be
|
|
91
96
|
* detected here because we do not know the expected token count.
|
|
@@ -121,6 +126,15 @@ export function isContextOverflow(message, contextWindow) {
|
|
|
121
126
|
return true;
|
|
122
127
|
}
|
|
123
128
|
}
|
|
129
|
+
// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input
|
|
130
|
+
// to fit the context window, leaving no room for output. Returns stopReason "length"
|
|
131
|
+
// with output=0 and input+cacheRead filling the context window.
|
|
132
|
+
if (contextWindow && message.stopReason === "length" && message.usage.output === 0) {
|
|
133
|
+
const inputTokens = message.usage.input + message.usage.cacheRead;
|
|
134
|
+
if (inputTokens >= contextWindow * 0.99) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
124
138
|
return false;
|
|
125
139
|
}
|
|
126
140
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.1",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,10 +22,6 @@
|
|
|
22
22
|
"types": "./dist/providers/google.d.ts",
|
|
23
23
|
"import": "./dist/providers/google.js"
|
|
24
24
|
},
|
|
25
|
-
"./google-gemini-cli": {
|
|
26
|
-
"types": "./dist/providers/google-gemini-cli.d.ts",
|
|
27
|
-
"import": "./dist/providers/google-gemini-cli.js"
|
|
28
|
-
},
|
|
29
25
|
"./google-vertex": {
|
|
30
26
|
"types": "./dist/providers/google-vertex.d.ts",
|
|
31
27
|
"import": "./dist/providers/google-vertex.js"
|
|
@@ -72,7 +68,7 @@
|
|
|
72
68
|
"prepublishOnly": "npm run clean && npm run build"
|
|
73
69
|
},
|
|
74
70
|
"dependencies": {
|
|
75
|
-
"@anthropic-ai/sdk": "^0.
|
|
71
|
+
"@anthropic-ai/sdk": "^0.91.1",
|
|
76
72
|
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
|
|
77
73
|
"@google/genai": "^1.40.0",
|
|
78
74
|
"@mistralai/mistralai": "^2.2.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shawnstack/quickforge",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.19",
|
|
4
4
|
"description": "AI chat application with YOLO-mode local workspace tools. React + Vite + Tailwind CSS frontend, local Node.js storage server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"package.json"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
46
|
-
"@mariozechner/pi-ai": "^0.
|
|
45
|
+
"@mariozechner/pi-agent-core": "^0.73.1",
|
|
46
|
+
"@mariozechner/pi-ai": "^0.73.1",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
48
48
|
"ws": "^8.20.1"
|
|
49
49
|
},
|
package/server/agent-manager.mjs
CHANGED
|
@@ -5,6 +5,11 @@ import { streamSimpleWithAiHttpLogging } from './ai-http-logger.mjs'
|
|
|
5
5
|
import { toolHandlers, loadSkillToolContext, abortRunningCommand } from './tools/index.mjs'
|
|
6
6
|
import { createSkillTools, workspaceTools } from './tools/definitions.mjs'
|
|
7
7
|
import { callMcpTool, createMcpToolDefinitions, isMcpToolName } from './mcp/registry.mjs'
|
|
8
|
+
import {
|
|
9
|
+
composeSubagentSystemPrompt,
|
|
10
|
+
formatSubagentTask,
|
|
11
|
+
getSubagentDefinition,
|
|
12
|
+
} from './subagents.mjs'
|
|
8
13
|
import { projectContextFromId, readProjectConfig } from './project-config.mjs'
|
|
9
14
|
import { readStore, atomicUpdate, readSessionValue, writeSessionValue, deleteSessionValue } from './storage.mjs'
|
|
10
15
|
import { logger } from './utils/logger.mjs'
|
|
@@ -88,7 +93,36 @@ function wrapMcpToolDefinition(definition, toolPermissions) {
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
function wrapSubagentToolDefinition(definition, parentSessionId) {
|
|
97
|
+
return {
|
|
98
|
+
...definition,
|
|
99
|
+
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
100
|
+
const parentSession = agentSessions.get(parentSessionId)
|
|
101
|
+
if (!parentSession) throw new Error('Parent session is no longer active.')
|
|
102
|
+
const result = await runSubagent(parentSession, params || {}, signal, onUpdate)
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: 'text', text: result.content }],
|
|
105
|
+
details: result.details,
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function wrapWorkspaceToolDefinition(definition, context, toolPermissions, options = {}) {
|
|
112
|
+
if (definition.name === 'run_subagent') return wrapSubagentToolDefinition(definition, options.parentSessionId)
|
|
113
|
+
return wrapToolDefinition(definition, context, toolPermissions)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function createServerTools(projectId, projectContext, skillsContext, includeWorkspaceTools, toolPermissions, options = {}) {
|
|
117
|
+
const {
|
|
118
|
+
allowedToolNames = null,
|
|
119
|
+
includeSubagentTool = true,
|
|
120
|
+
includeMcpTools = true,
|
|
121
|
+
parentSessionId = null,
|
|
122
|
+
} = options
|
|
123
|
+
const allowedTools = allowedToolNames ? new Set(allowedToolNames) : null
|
|
124
|
+
const isAllowed = (definition) => !allowedTools || allowedTools.has(definition.name)
|
|
125
|
+
|
|
92
126
|
const skillTools = await createSkillTools({
|
|
93
127
|
globalSkillNames: skillsContext.globalSkillNames,
|
|
94
128
|
projectSkillNames: skillsContext.projectSkillNames,
|
|
@@ -100,14 +134,21 @@ async function createServerTools(projectId, projectContext, skillsContext, inclu
|
|
|
100
134
|
workspaceRoot: projectContext?.workspaceRoot,
|
|
101
135
|
})
|
|
102
136
|
const toolContext = { ...projectContext, ...skillToolContext }
|
|
103
|
-
const tools = skillTools
|
|
137
|
+
const tools = skillTools
|
|
138
|
+
.filter(isAllowed)
|
|
139
|
+
.map((definition) => wrapToolDefinition(definition, toolContext, toolPermissions))
|
|
104
140
|
|
|
105
141
|
if (includeWorkspaceTools && projectId && projectContext) {
|
|
106
|
-
|
|
142
|
+
const definitions = workspaceTools.filter((definition) => includeSubagentTool || definition.name !== 'run_subagent')
|
|
143
|
+
tools.push(...definitions
|
|
144
|
+
.filter(isAllowed)
|
|
145
|
+
.map((definition) => wrapWorkspaceToolDefinition(definition, toolContext, toolPermissions, { parentSessionId })))
|
|
107
146
|
}
|
|
108
147
|
|
|
109
|
-
|
|
110
|
-
|
|
148
|
+
if (includeMcpTools) {
|
|
149
|
+
const mcpTools = await createMcpToolDefinitions()
|
|
150
|
+
tools.push(...mcpTools.filter(isAllowed).map((definition) => wrapMcpToolDefinition(definition, toolPermissions)))
|
|
151
|
+
}
|
|
111
152
|
|
|
112
153
|
return tools
|
|
113
154
|
}
|
|
@@ -126,6 +167,7 @@ async function rebuildSessionTools(session) {
|
|
|
126
167
|
sessionSkillsContext(session),
|
|
127
168
|
!!(session.projectId && session.projectContext),
|
|
128
169
|
createCommandToolPermissions(session),
|
|
170
|
+
{ parentSessionId: session.sessionId },
|
|
129
171
|
)
|
|
130
172
|
}
|
|
131
173
|
|
|
@@ -139,9 +181,10 @@ const agentSessions = new Map()
|
|
|
139
181
|
|
|
140
182
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes
|
|
141
183
|
const APPROVAL_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes for tool approval
|
|
184
|
+
const SUBAGENT_DEFAULT_TIMEOUT_MS = 30 * 60 * 1000
|
|
142
185
|
const commandRestrictedTools = new Set(['write_file', 'edit_file', 'run_command'])
|
|
143
186
|
const safeReadTools = new Set(['read_file', 'grep_files'])
|
|
144
|
-
const pendingApprovals = new Map() // toolCallId → { resolve, reject, sessionId, toolName, args, timeout }
|
|
187
|
+
const pendingApprovals = new Map() // toolCallId → { resolve, reject, sessionId, toolName, args, source, timeout }
|
|
145
188
|
const pendingAutoCompactApprovals = new Map() // approvalId → { resolve, reject, sessionId, timeout }
|
|
146
189
|
|
|
147
190
|
function createCommandToolPermissions(session) {
|
|
@@ -163,7 +206,7 @@ function createCommandToolPermissions(session) {
|
|
|
163
206
|
* The agent loop's `await config.beforeToolCall(...)` pauses on this promise,
|
|
164
207
|
* effectively freezing the agent until the user decides.
|
|
165
208
|
*/
|
|
166
|
-
function createApprovalPromise(session, toolCallId, toolName, args) {
|
|
209
|
+
function createApprovalPromise(session, toolCallId, toolName, args, source) {
|
|
167
210
|
if (!session) return { block: true, reason: 'No active session for tool approval.' }
|
|
168
211
|
return new Promise((resolve, reject) => {
|
|
169
212
|
let settled = false
|
|
@@ -209,6 +252,7 @@ function createApprovalPromise(session, toolCallId, toolName, args) {
|
|
|
209
252
|
sessionId: session.sessionId,
|
|
210
253
|
toolName,
|
|
211
254
|
args,
|
|
255
|
+
source,
|
|
212
256
|
})
|
|
213
257
|
|
|
214
258
|
// Notify the frontend via both the session-level and global event buses.
|
|
@@ -220,6 +264,7 @@ function createApprovalPromise(session, toolCallId, toolName, args) {
|
|
|
220
264
|
toolCallId,
|
|
221
265
|
toolName,
|
|
222
266
|
args,
|
|
267
|
+
source,
|
|
223
268
|
}
|
|
224
269
|
session.eventBus.emit('agent_event', approvalEvent)
|
|
225
270
|
agentEvents.emit('agent_event', approvalEvent)
|
|
@@ -574,6 +619,13 @@ async function resolveCommandState(session, userMessage) {
|
|
|
574
619
|
}
|
|
575
620
|
}
|
|
576
621
|
|
|
622
|
+
function omitDetailsForLlm(message) {
|
|
623
|
+
if (!message || typeof message !== 'object' || message.details === undefined) return message
|
|
624
|
+
const copy = { ...message }
|
|
625
|
+
delete copy.details
|
|
626
|
+
return copy
|
|
627
|
+
}
|
|
628
|
+
|
|
577
629
|
/**
|
|
578
630
|
* Convert AgentMessage[] to LLM-compatible Message[].
|
|
579
631
|
* Handles "user-with-attachments" → "user" with multi-modal content blocks.
|
|
@@ -597,14 +649,197 @@ function serverConvertToLlm(messages) {
|
|
|
597
649
|
}
|
|
598
650
|
}
|
|
599
651
|
}
|
|
600
|
-
return { ...m, role: 'user', content: textContent }
|
|
652
|
+
return omitDetailsForLlm({ ...m, role: 'user', content: textContent })
|
|
601
653
|
}
|
|
602
|
-
if (m.role === 'user' || m.role === 'assistant' || m.role === 'toolResult') return m
|
|
654
|
+
if (m.role === 'user' || m.role === 'assistant' || m.role === 'toolResult') return omitDetailsForLlm(m)
|
|
603
655
|
return null
|
|
604
656
|
})
|
|
605
657
|
.filter(Boolean)
|
|
606
658
|
}
|
|
607
659
|
|
|
660
|
+
function messageText(message) {
|
|
661
|
+
const content = message?.content
|
|
662
|
+
if (typeof content === 'string') return content
|
|
663
|
+
if (Array.isArray(content)) {
|
|
664
|
+
return content
|
|
665
|
+
.filter((block) => block?.type === 'text')
|
|
666
|
+
.map((block) => block.text ?? '')
|
|
667
|
+
.join('\n')
|
|
668
|
+
.trim()
|
|
669
|
+
}
|
|
670
|
+
return ''
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function lastAssistantText(messages) {
|
|
674
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
675
|
+
const message = messages[index]
|
|
676
|
+
if (message?.role !== 'assistant') continue
|
|
677
|
+
const text = messageText(message)
|
|
678
|
+
if (text) return text
|
|
679
|
+
}
|
|
680
|
+
return ''
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
684
|
+
const definition = getSubagentDefinition(params?.subagent)
|
|
685
|
+
if (!definition) {
|
|
686
|
+
const error = new Error(`Unknown subagent: ${params?.subagent || ''}`)
|
|
687
|
+
error.statusCode = 400
|
|
688
|
+
throw error
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const task = String(params?.task || '').trim()
|
|
692
|
+
if (!task) {
|
|
693
|
+
const error = new Error('task is required')
|
|
694
|
+
error.statusCode = 400
|
|
695
|
+
throw error
|
|
696
|
+
}
|
|
697
|
+
if (!parentSession.projectId || !parentSession.projectContext) {
|
|
698
|
+
throw new Error('Subagents require an active project workspace.')
|
|
699
|
+
}
|
|
700
|
+
if (!parentSession.model) {
|
|
701
|
+
throw new Error('No active model is configured for the parent session.')
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const timeoutMs = Math.max(1000, Math.min(Number(definition.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS), 30 * 60 * 1000))
|
|
705
|
+
const subagentSessionId = `${parentSession.sessionId}:subagent:${definition.name}:${randomUUID()}`
|
|
706
|
+
const startedAt = Date.now()
|
|
707
|
+
let toolCalls = 0
|
|
708
|
+
let latestMessages = []
|
|
709
|
+
let latestPendingToolCalls = []
|
|
710
|
+
let toolsForClient = []
|
|
711
|
+
|
|
712
|
+
const tools = await createServerTools(
|
|
713
|
+
parentSession.projectId,
|
|
714
|
+
parentSession.projectContext,
|
|
715
|
+
sessionSkillsContext(parentSession),
|
|
716
|
+
true,
|
|
717
|
+
(toolName) => {
|
|
718
|
+
if (!definition.allowedTools.includes(toolName)) return `Subagent ${definition.name} is not allowed to use ${toolName}.`
|
|
719
|
+
return null
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
allowedToolNames: definition.allowedTools,
|
|
723
|
+
includeSubagentTool: false,
|
|
724
|
+
includeMcpTools: false,
|
|
725
|
+
},
|
|
726
|
+
)
|
|
727
|
+
toolsForClient = tools.map(({ execute, prepareArguments, ...tool }) => tool)
|
|
728
|
+
|
|
729
|
+
const emitSubagentTrace = () => {
|
|
730
|
+
onUpdate?.({
|
|
731
|
+
content: [],
|
|
732
|
+
details: {
|
|
733
|
+
subagent: definition.name,
|
|
734
|
+
label: definition.label,
|
|
735
|
+
sessionId: subagentSessionId,
|
|
736
|
+
parentSessionId: parentSession.sessionId,
|
|
737
|
+
toolCalls,
|
|
738
|
+
allowedTools: definition.allowedTools,
|
|
739
|
+
timeoutMs,
|
|
740
|
+
durationMs: Date.now() - startedAt,
|
|
741
|
+
messages: latestMessages,
|
|
742
|
+
tools: toolsForClient,
|
|
743
|
+
pendingToolCalls: latestPendingToolCalls,
|
|
744
|
+
},
|
|
745
|
+
})
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const systemPrompt = composeSubagentSystemPrompt({
|
|
749
|
+
definition,
|
|
750
|
+
parentSystemPrompt: parentSession.agent.state.systemPrompt,
|
|
751
|
+
projectContext: parentSession.projectContext,
|
|
752
|
+
})
|
|
753
|
+
const userMessage = {
|
|
754
|
+
role: 'user',
|
|
755
|
+
content: [{ type: 'text', text: formatSubagentTask(params) }],
|
|
756
|
+
timestamp: Date.now(),
|
|
757
|
+
}
|
|
758
|
+
const subagent = new Agent({
|
|
759
|
+
initialState: {
|
|
760
|
+
systemPrompt,
|
|
761
|
+
model: parentSession.model,
|
|
762
|
+
thinkingLevel: parentSession.thinkingLevel,
|
|
763
|
+
messages: [],
|
|
764
|
+
tools,
|
|
765
|
+
},
|
|
766
|
+
streamFn: streamSimpleWithAiHttpLogging,
|
|
767
|
+
getApiKey: parentSession.getApiKey,
|
|
768
|
+
sessionId: subagentSessionId,
|
|
769
|
+
convertToLlm: serverConvertToLlm,
|
|
770
|
+
onPayload: (payload) => {
|
|
771
|
+
restoreReasoningContentInPayload(payload, subagent.state.messages, subagent.state.model)
|
|
772
|
+
},
|
|
773
|
+
beforeToolCall: async (context) => {
|
|
774
|
+
const toolName = context.toolCall?.name
|
|
775
|
+
toolCalls += 1
|
|
776
|
+
emitSubagentTrace()
|
|
777
|
+
if (toolCalls > Number(definition.maxToolCalls || 12)) {
|
|
778
|
+
return { block: true, reason: `Subagent ${definition.name} exceeded its tool-call budget.` }
|
|
779
|
+
}
|
|
780
|
+
if (!definition.allowedTools.includes(toolName)) {
|
|
781
|
+
return { block: true, reason: `Subagent ${definition.name} is not allowed to use ${toolName}.` }
|
|
782
|
+
}
|
|
783
|
+
if (!parentSession.yoloMode) {
|
|
784
|
+
if (safeReadTools.has(toolName)) return undefined
|
|
785
|
+
return createApprovalPromise(parentSession, context.toolCall?.id, toolName, context.args, {
|
|
786
|
+
type: 'subagent',
|
|
787
|
+
subagent: definition.name,
|
|
788
|
+
label: definition.label,
|
|
789
|
+
sessionId: subagentSessionId,
|
|
790
|
+
})
|
|
791
|
+
}
|
|
792
|
+
return undefined
|
|
793
|
+
},
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
subagent.subscribe((event) => {
|
|
797
|
+
latestMessages = subagent.state.messages.slice()
|
|
798
|
+
latestPendingToolCalls = Array.from(subagent.state.pendingToolCalls || [])
|
|
799
|
+
if (event.type === 'message_start' || event.type === 'message_update') {
|
|
800
|
+
if (event.message?.role === 'assistant') {
|
|
801
|
+
latestMessages = [...latestMessages, event.message]
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
emitSubagentTrace()
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
let timedOut = false
|
|
808
|
+
const timeout = setTimeout(() => {
|
|
809
|
+
timedOut = true
|
|
810
|
+
subagent.abort()
|
|
811
|
+
}, timeoutMs)
|
|
812
|
+
const onParentAbort = () => subagent.abort()
|
|
813
|
+
parentSignal?.addEventListener?.('abort', onParentAbort, { once: true })
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
await subagent.prompt(userMessage)
|
|
817
|
+
if (timedOut) throw new Error(`Subagent ${definition.name} timed out after ${timeoutMs}ms.`)
|
|
818
|
+
if (parentSignal?.aborted) throw new Error(`Subagent ${definition.name} aborted with parent run.`)
|
|
819
|
+
} finally {
|
|
820
|
+
clearTimeout(timeout)
|
|
821
|
+
parentSignal?.removeEventListener?.('abort', onParentAbort)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const content = lastAssistantText(subagent.state.messages) || `Subagent ${definition.name} completed without a text response.`
|
|
825
|
+
return {
|
|
826
|
+
content,
|
|
827
|
+
details: {
|
|
828
|
+
subagent: definition.name,
|
|
829
|
+
label: definition.label,
|
|
830
|
+
sessionId: subagentSessionId,
|
|
831
|
+
parentSessionId: parentSession.sessionId,
|
|
832
|
+
toolCalls,
|
|
833
|
+
allowedTools: definition.allowedTools,
|
|
834
|
+
timeoutMs,
|
|
835
|
+
durationMs: Date.now() - startedAt,
|
|
836
|
+
messages: latestMessages,
|
|
837
|
+
tools: toolsForClient,
|
|
838
|
+
pendingToolCalls: latestPendingToolCalls,
|
|
839
|
+
},
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
608
843
|
function applyActiveCommandPrompt(messages, commandPrompt) {
|
|
609
844
|
if (!commandPrompt) return messages
|
|
610
845
|
|
|
@@ -717,6 +952,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
717
952
|
systemPrompt = null,
|
|
718
953
|
title = 'New chat',
|
|
719
954
|
createdAt = new Date().toISOString(),
|
|
955
|
+
lastModified = null,
|
|
720
956
|
contextCompaction = null,
|
|
721
957
|
} = config
|
|
722
958
|
|
|
@@ -764,6 +1000,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
764
1000
|
const session = agentSessions.get(sessionId)
|
|
765
1001
|
return session ? createCommandToolPermissions(session)(toolName) : null
|
|
766
1002
|
},
|
|
1003
|
+
{ parentSessionId: sessionId },
|
|
767
1004
|
)
|
|
768
1005
|
|
|
769
1006
|
// Resolve API key
|
|
@@ -798,6 +1035,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
798
1035
|
const isSkillTool = toolName === 'activate_skill' || toolName === 'read_skill_resource'
|
|
799
1036
|
if (isSkillTool) return undefined
|
|
800
1037
|
const currentSession = agentSessions.get(sessionId)
|
|
1038
|
+
if (toolName === 'run_subagent') return undefined
|
|
801
1039
|
if (isMcpToolName(toolName)) {
|
|
802
1040
|
if (!currentSession?.yoloMode) return createApprovalPromise(currentSession, toolCallId, toolName, context.args)
|
|
803
1041
|
return undefined
|
|
@@ -828,6 +1066,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
828
1066
|
scope,
|
|
829
1067
|
title,
|
|
830
1068
|
createdAt,
|
|
1069
|
+
lastModified,
|
|
831
1070
|
globalSkillNames: skillsContext.globalSkillNames,
|
|
832
1071
|
projectSkillNames: skillsContext.projectSkillNames,
|
|
833
1072
|
status: 'idle',
|
|
@@ -902,11 +1141,35 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
902
1141
|
return session
|
|
903
1142
|
}
|
|
904
1143
|
|
|
1144
|
+
function messageTimestampMs(message) {
|
|
1145
|
+
const timestamp = message?.timestamp
|
|
1146
|
+
if (typeof timestamp === 'number' && Number.isFinite(timestamp)) return timestamp
|
|
1147
|
+
if (typeof timestamp === 'string') {
|
|
1148
|
+
const trimmed = timestamp.trim()
|
|
1149
|
+
if (!trimmed) return undefined
|
|
1150
|
+
const numeric = Number(trimmed)
|
|
1151
|
+
if (Number.isFinite(numeric)) return numeric
|
|
1152
|
+
const parsed = Date.parse(trimmed)
|
|
1153
|
+
return Number.isNaN(parsed) ? undefined : parsed
|
|
1154
|
+
}
|
|
1155
|
+
return undefined
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function sessionLastModifiedFromMessages(messages, fallback) {
|
|
1159
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
1160
|
+
const timestamp = messageTimestampMs(messages[index])
|
|
1161
|
+
if (timestamp !== undefined) return new Date(timestamp).toISOString()
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const fallbackMs = Date.parse(fallback)
|
|
1165
|
+
return Number.isNaN(fallbackMs) ? new Date().toISOString() : new Date(fallbackMs).toISOString()
|
|
1166
|
+
}
|
|
1167
|
+
|
|
905
1168
|
/**
|
|
906
1169
|
* Persist session data to storage.
|
|
907
1170
|
*/
|
|
908
1171
|
async function persistSession(session) {
|
|
909
|
-
const { sessionId, agent, scope, projectId, title, createdAt, status, startedAt, finishedAt, model, thinkingLevel, yoloMode, contextCompaction } = session
|
|
1172
|
+
const { sessionId, agent, scope, projectId, title, createdAt, lastModified: storedLastModified, status, startedAt, finishedAt, model, thinkingLevel, yoloMode, contextCompaction } = session
|
|
910
1173
|
const messages = agent.state.messages
|
|
911
1174
|
|
|
912
1175
|
if (messages.length === 0) {
|
|
@@ -923,6 +1186,7 @@ async function persistSession(session) {
|
|
|
923
1186
|
}
|
|
924
1187
|
|
|
925
1188
|
const now = new Date().toISOString()
|
|
1189
|
+
const lastModified = sessionLastModifiedFromMessages(messages, storedLastModified || createdAt || now)
|
|
926
1190
|
const sessionData = {
|
|
927
1191
|
id: sessionId,
|
|
928
1192
|
title,
|
|
@@ -931,7 +1195,7 @@ async function persistSession(session) {
|
|
|
931
1195
|
yoloMode,
|
|
932
1196
|
messages,
|
|
933
1197
|
createdAt: createdAt || now,
|
|
934
|
-
lastModified
|
|
1198
|
+
lastModified,
|
|
935
1199
|
scope,
|
|
936
1200
|
projectId: scope === 'project' ? projectId : undefined,
|
|
937
1201
|
taskStatus: status,
|
|
@@ -939,6 +1203,7 @@ async function persistSession(session) {
|
|
|
939
1203
|
taskFinishedAt: finishedAt,
|
|
940
1204
|
contextCompaction: contextCompaction || undefined,
|
|
941
1205
|
}
|
|
1206
|
+
session.lastModified = lastModified
|
|
942
1207
|
|
|
943
1208
|
// Calculate usage
|
|
944
1209
|
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 }
|
|
@@ -974,7 +1239,7 @@ async function persistSession(session) {
|
|
|
974
1239
|
id: sessionId,
|
|
975
1240
|
title,
|
|
976
1241
|
createdAt: createdAt || now,
|
|
977
|
-
lastModified
|
|
1242
|
+
lastModified,
|
|
978
1243
|
messageCount: messages.length,
|
|
979
1244
|
usage,
|
|
980
1245
|
thinkingLevel,
|
|
@@ -1261,6 +1526,7 @@ export function getSessionState(sessionId) {
|
|
|
1261
1526
|
thinkingLevel: session.thinkingLevel,
|
|
1262
1527
|
title: session.title,
|
|
1263
1528
|
createdAt: session.createdAt,
|
|
1529
|
+
lastModified: session.lastModified,
|
|
1264
1530
|
status: session.status,
|
|
1265
1531
|
startedAt: session.startedAt,
|
|
1266
1532
|
finishedAt: session.finishedAt,
|
|
@@ -1362,6 +1628,7 @@ export async function restoreAgent(sessionId) {
|
|
|
1362
1628
|
messages: sessionData.messages || [],
|
|
1363
1629
|
title: sessionData.title || 'New chat',
|
|
1364
1630
|
createdAt: sessionData.createdAt,
|
|
1631
|
+
lastModified: sessionData.lastModified,
|
|
1365
1632
|
contextCompaction: sessionData.contextCompaction || null,
|
|
1366
1633
|
})
|
|
1367
1634
|
} catch (err) {
|