@axhub/genie 0.2.11 → 0.2.12
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/dist/api-docs.html +2 -2
- package/dist/assets/App-Clb2COtW.js +274 -0
- package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
- package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
- package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
- package/dist/assets/{_basePickBy-BDnj7-0Z.js → _basePickBy-jUZsM51q.js} +1 -1
- package/dist/assets/{_baseUniq-Bl0JKOyl.js → _baseUniq-BXglE6_v.js} +1 -1
- package/dist/assets/{arc-DY-4Kev3.js → arc-D-oFCFBv.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-qw7crNVd.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-B9xg7ep3.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-H9xp3ytb.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
- package/dist/assets/channel-BF4woPXX.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-B3EVDUxI.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CGv945ef.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-uAT4CKWM.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-Cbvlpkf7.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-CcqIuGat.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CgrcsRuX.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Cx0APOoV.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-BbZirvBk.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
- package/dist/assets/clone-CqBvwCJW.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CrvmGFLD.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-C-W6VPjS.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-IP2q3bL0.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CQaL-XyV.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-BxBLThfv.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-Dyl7bJTt.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-B7NFMgFK.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-hReWSDu2.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-gVgcr0ST.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
- package/dist/assets/{graph-DNDiJhTn.js → graph-Dkeg9oys.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-DclLmTou.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
- package/dist/assets/index-DgGmiqsP.css +1 -0
- package/dist/assets/index-DvA901Vs.js +2 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-CqQOOzDA.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-CZ0iLiHg.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-DdfYKfNh.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-C5Vf32u6.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
- package/dist/assets/{layout-rvTEu2KS.js → layout-Xr9Z2VGF.js} +1 -1
- package/dist/assets/{linear-CD9SiYze.js → linear-JBmzAJtl.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-OZ8qWWwa.js → mermaid-O7DHMXV3-fDuyNLKe.js} +230 -222
- package/dist/assets/{mindmap-definition-YRQLILUH-CQxrLNVc.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-XgAUByWg.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-CH16ls7G.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-B_kQO06L.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-ofe78CyS.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-Ckbxwny6.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-DNtzCk14.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-zT6CklKt.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-y0U2c3xG.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
- package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
- package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-xKj3SjYG.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-Da_qyEoX.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +25 -6
- package/scripts/refresh-acp-default-capabilities.mjs +160 -0
- package/server/acp-runtime/client.js +1137 -181
- package/server/acp-runtime/command-overrides.js +48 -0
- package/server/acp-runtime/index.js +576 -16
- package/server/acp-runtime/registry.js +6 -4
- package/server/acp-runtime/session-store.js +235 -92
- package/server/database/db.js +12 -3
- package/server/external-agent/ws.js +212 -11
- package/server/index.js +145 -52
- package/server/projects-watcher-config.js +4 -0
- package/server/projects.js +466 -125
- package/server/routes/cc-connect.js +5 -4
- package/server/routes/codex.js +24 -0
- package/server/routes/commands.js +144 -1
- package/server/routes/runs.js +641 -0
- package/server/routes/session-core.js +357 -109
- package/server/session-core/eventStore.js +0 -121
- package/server/session-core/providerAdapters.js +644 -163
- package/server/session-core/providerDiscovery.js +66 -38
- package/server/session-core/runRegistry.js +244 -0
- package/server/session-core/runtimeState.js +75 -3
- package/server/session-core/runtimeWriter.js +132 -10
- package/server/utils/codexImagePlayground.js +479 -0
- package/server/utils/localTerminal.js +56 -0
- package/server/utils/shellCommand.js +70 -0
- package/shared/acpCapabilities.js +393 -0
- package/shared/acpDefaultCapabilities.generated.json +141 -0
- package/shared/conversationEvents.js +425 -121
- package/dist/assets/App-VH1wNUHs.js +0 -259
- package/dist/assets/ReviewApp-D_9EN4TM.js +0 -1
- package/dist/assets/channel-CyNUnRfc.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-DxBtyz2A.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-DxBtyz2A.js +0 -1
- package/dist/assets/clone-C341l3d0.js +0 -1
- package/dist/assets/index-DBkz_W_P.css +0 -1
- package/dist/assets/index-DdRyoXKh.js +0 -2
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B3VPhiE1.js +0 -1
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
1
|
import { SUPPORTED_PROVIDERS } from '../../shared/conversationEvents.js';
|
|
5
2
|
import {
|
|
6
|
-
detectProviderInstallationStatus
|
|
7
|
-
checkClaudeCredentials, checkCodexCredentials,
|
|
8
|
-
checkGeminiCredentials,
|
|
9
|
-
checkOpencodeCredentials
|
|
3
|
+
detectProviderInstallationStatus
|
|
10
4
|
} from '../routes/cli-auth.js';
|
|
11
5
|
import { listOpencodeModels } from '../opencode-cli.js';
|
|
12
6
|
import { spawnCommand } from '../utils/spawnCommand.js';
|
|
13
7
|
import {
|
|
14
8
|
CLAUDE_MODELS,
|
|
15
|
-
|
|
9
|
+
GEMINI_MODELS,
|
|
16
10
|
OPENCODE_MODELS
|
|
17
11
|
} from '../../shared/modelConstants.js';
|
|
18
12
|
|
|
@@ -21,6 +15,9 @@ const PROVIDER_CAPABILITIES = {
|
|
|
21
15
|
gemini: { modes: ['default', 'acceptEdits', 'bypassPermissions'], thinking: false, resume: true },
|
|
22
16
|
opencode: { modes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'], thinking: false, resume: true }
|
|
23
17
|
};
|
|
18
|
+
const MODEL_DISCOVERY_CACHE_TTL_MS = 30 * 60 * 1000;
|
|
19
|
+
const modelDiscoveryCache = new Map();
|
|
20
|
+
const modelDiscoveryInFlight = new Map();
|
|
24
21
|
|
|
25
22
|
function runCommand(command, args = [], options = {}) {
|
|
26
23
|
return new Promise((resolve) => {
|
|
@@ -160,64 +157,92 @@ async function discoverClaudeModels() {
|
|
|
160
157
|
}
|
|
161
158
|
|
|
162
159
|
async function discoverCodexModels() {
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
166
|
-
const content = await fs.readFile(configPath, 'utf8');
|
|
167
|
-
const match = content.match(/^model\s*=\s*["']([^"']+)["']/m);
|
|
168
|
-
if (match) currentModel = match[1];
|
|
169
|
-
} catch {
|
|
170
|
-
}
|
|
171
|
-
return { models: mapModelOptions(CODEX_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: currentModel || null };
|
|
160
|
+
return { models: [], source: 'acp-only', error: null, currentModel: null };
|
|
172
161
|
}
|
|
173
162
|
|
|
174
163
|
async function discoverGeminiModels() {
|
|
175
164
|
return { models: mapModelOptions(GEMINI_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: null };
|
|
176
165
|
}
|
|
177
166
|
|
|
178
|
-
const AUTH_CHECKERS = {
|
|
179
|
-
claude: checkClaudeCredentials, codex: checkCodexCredentials,
|
|
180
|
-
gemini: checkGeminiCredentials,
|
|
181
|
-
opencode: checkOpencodeCredentials
|
|
182
|
-
};
|
|
183
|
-
|
|
184
167
|
const MODEL_DISCOVERY = {
|
|
185
168
|
claude: discoverClaudeModels, codex: discoverCodexModels,
|
|
186
169
|
gemini: discoverGeminiModels,
|
|
187
170
|
opencode: discoverOpenCodeModels
|
|
188
171
|
};
|
|
189
172
|
|
|
173
|
+
function createModelDiscoveryCacheKey(provider, projectPath) {
|
|
174
|
+
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
175
|
+
const normalizedProjectPath = String(projectPath || '').trim();
|
|
176
|
+
return `${normalizedProvider}:${normalizedProjectPath || 'global'}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function cloneJsonValue(value) {
|
|
180
|
+
if (value === undefined) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return JSON.parse(JSON.stringify(value));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function discoverProviderModels(provider, projectPath, options = {}) {
|
|
188
|
+
const cacheKey = createModelDiscoveryCacheKey(provider, projectPath);
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const force = options.force === true || String(options.force || '').toLowerCase() === 'true';
|
|
191
|
+
|
|
192
|
+
if (!force) {
|
|
193
|
+
const cached = modelDiscoveryCache.get(cacheKey);
|
|
194
|
+
if (cached && cached.expiresAt > now) {
|
|
195
|
+
return cloneJsonValue(cached.result);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (modelDiscoveryInFlight.has(cacheKey)) {
|
|
199
|
+
return modelDiscoveryInFlight.get(cacheKey);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const discoveryPromise = MODEL_DISCOVERY[provider](projectPath)
|
|
204
|
+
.then((result) => {
|
|
205
|
+
const normalizedResult = result && typeof result === 'object'
|
|
206
|
+
? result
|
|
207
|
+
: { models: [], source: 'unavailable', error: 'Model discovery failed' };
|
|
208
|
+
|
|
209
|
+
modelDiscoveryCache.set(cacheKey, {
|
|
210
|
+
result: cloneJsonValue(normalizedResult),
|
|
211
|
+
expiresAt: Date.now() + MODEL_DISCOVERY_CACHE_TTL_MS
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return normalizedResult;
|
|
215
|
+
})
|
|
216
|
+
.finally(() => {
|
|
217
|
+
modelDiscoveryInFlight.delete(cacheKey);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
modelDiscoveryInFlight.set(cacheKey, discoveryPromise);
|
|
221
|
+
return discoveryPromise;
|
|
222
|
+
}
|
|
223
|
+
|
|
190
224
|
export async function discoverProvider(provider, options = {}) {
|
|
191
225
|
const normalizedProvider = String(provider || '').toLowerCase();
|
|
192
226
|
if (!SUPPORTED_PROVIDERS.includes(normalizedProvider)) {
|
|
193
227
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
194
228
|
}
|
|
195
229
|
|
|
196
|
-
const
|
|
197
|
-
detectProviderInstallationStatus(normalizedProvider),
|
|
198
|
-
AUTH_CHECKERS[normalizedProvider]().catch((error) => ({ authenticated: false, email: null, error: error.message }))
|
|
199
|
-
]);
|
|
230
|
+
const installation = await detectProviderInstallationStatus(normalizedProvider);
|
|
200
231
|
|
|
201
232
|
const modelsResult = installation.installed
|
|
202
|
-
? await
|
|
233
|
+
? await discoverProviderModels(normalizedProvider, options.projectPath, options)
|
|
203
234
|
: { models: [], source: 'unavailable', error: installation.reason || 'Provider not installed' };
|
|
204
235
|
|
|
205
|
-
let availability = 'unavailable';
|
|
206
|
-
if (installation.installed && auth.authenticated) availability = 'available';
|
|
207
|
-
else if (installation.installed && !auth.authenticated) availability = 'unauthenticated';
|
|
208
|
-
|
|
209
236
|
return {
|
|
210
237
|
provider: normalizedProvider,
|
|
211
|
-
availability,
|
|
238
|
+
availability: installation.installed ? 'available' : 'unavailable',
|
|
212
239
|
installed: !!installation.installed,
|
|
213
|
-
authenticated: !!auth.authenticated,
|
|
214
|
-
auth,
|
|
215
240
|
installation,
|
|
216
241
|
capabilities: PROVIDER_CAPABILITIES[normalizedProvider],
|
|
217
242
|
models: modelsResult.models,
|
|
218
243
|
modelSource: modelsResult.source,
|
|
219
244
|
discoveryError: modelsResult.error || null,
|
|
220
|
-
currentModel: modelsResult.currentModel ||
|
|
245
|
+
currentModel: modelsResult.currentModel || null
|
|
221
246
|
};
|
|
222
247
|
}
|
|
223
248
|
|
|
@@ -227,8 +252,6 @@ export async function discoverAllProviders(options = {}) {
|
|
|
227
252
|
provider,
|
|
228
253
|
availability: 'probe_failed',
|
|
229
254
|
installed: false,
|
|
230
|
-
authenticated: false,
|
|
231
|
-
auth: { authenticated: false, email: null, error: error.message },
|
|
232
255
|
installation: null,
|
|
233
256
|
capabilities: PROVIDER_CAPABILITIES[provider],
|
|
234
257
|
models: [],
|
|
@@ -238,3 +261,8 @@ export async function discoverAllProviders(options = {}) {
|
|
|
238
261
|
}))
|
|
239
262
|
));
|
|
240
263
|
}
|
|
264
|
+
|
|
265
|
+
export function resetProviderDiscoveryCachesForTests() {
|
|
266
|
+
modelDiscoveryCache.clear();
|
|
267
|
+
modelDiscoveryInFlight.clear();
|
|
268
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export const RUN_ACTIVE_STATUSES = new Set([
|
|
4
|
+
'queued',
|
|
5
|
+
'running',
|
|
6
|
+
'awaiting_approval',
|
|
7
|
+
'awaiting_input',
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const RUN_TERMINAL_STATUSES = new Set([
|
|
11
|
+
'succeeded',
|
|
12
|
+
'failed',
|
|
13
|
+
'canceled',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const RUN_STATUSES = new Set([
|
|
17
|
+
...RUN_ACTIVE_STATUSES,
|
|
18
|
+
...RUN_TERMINAL_STATUSES,
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const DEFAULT_TERMINAL_TTL_MS = 30 * 60 * 1000;
|
|
22
|
+
const SUPPORTED_PROVIDERS = new Set(['claude', 'codex', 'gemini', 'opencode']);
|
|
23
|
+
|
|
24
|
+
function normalizeProvider(provider) {
|
|
25
|
+
const normalized = String(provider || '').trim().toLowerCase();
|
|
26
|
+
return SUPPORTED_PROVIDERS.has(normalized) ? normalized : 'claude';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeNullableString(value) {
|
|
30
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createRunId() {
|
|
34
|
+
if (typeof crypto.randomUUID === 'function') {
|
|
35
|
+
return `run_${crypto.randomUUID()}`;
|
|
36
|
+
}
|
|
37
|
+
return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clone(value) {
|
|
41
|
+
return value && typeof value === 'object'
|
|
42
|
+
? structuredClone(value)
|
|
43
|
+
: value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createRunRegistry({
|
|
47
|
+
now = () => new Date().toISOString(),
|
|
48
|
+
ttlMs = DEFAULT_TERMINAL_TTL_MS,
|
|
49
|
+
} = {}) {
|
|
50
|
+
const runs = new Map();
|
|
51
|
+
const eventsByRunId = new Map();
|
|
52
|
+
const terminalExpiresAtByRunId = new Map();
|
|
53
|
+
const subscribersByRunId = new Map();
|
|
54
|
+
|
|
55
|
+
const currentTimestamp = () => now();
|
|
56
|
+
const currentTimeMs = () => Date.parse(currentTimestamp());
|
|
57
|
+
|
|
58
|
+
const pruneExpiredRun = (runId) => {
|
|
59
|
+
const expiresAt = terminalExpiresAtByRunId.get(runId);
|
|
60
|
+
if (!Number.isFinite(expiresAt)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (currentTimeMs() <= expiresAt) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
runs.delete(runId);
|
|
69
|
+
eventsByRunId.delete(runId);
|
|
70
|
+
terminalExpiresAtByRunId.delete(runId);
|
|
71
|
+
subscribersByRunId.delete(runId);
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const notifyRunEventSubscribers = (event) => {
|
|
76
|
+
const subscribers = subscribersByRunId.get(event.runId);
|
|
77
|
+
if (!subscribers || subscribers.size === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
subscribers.forEach((subscriber) => {
|
|
82
|
+
try {
|
|
83
|
+
subscriber(clone(event));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn('[WARN] Run event subscriber failed:', error?.message || error);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getMutableRun = (runId) => {
|
|
91
|
+
const normalizedRunId = normalizeNullableString(runId);
|
|
92
|
+
if (!normalizedRunId || pruneExpiredRun(normalizedRunId)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return runs.get(normalizedRunId) || null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const rememberTerminalExpiry = (run) => {
|
|
99
|
+
if (RUN_TERMINAL_STATUSES.has(run.status)) {
|
|
100
|
+
terminalExpiresAtByRunId.set(run.runId, currentTimeMs() + ttlMs);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
createRun({ provider, sessionId = null, clientRequestId = null } = {}) {
|
|
106
|
+
const timestamp = currentTimestamp();
|
|
107
|
+
const run = {
|
|
108
|
+
runId: createRunId(),
|
|
109
|
+
provider: normalizeProvider(provider),
|
|
110
|
+
sessionId: normalizeNullableString(sessionId),
|
|
111
|
+
clientRequestId: normalizeNullableString(clientRequestId),
|
|
112
|
+
status: 'queued',
|
|
113
|
+
createdAt: timestamp,
|
|
114
|
+
updatedAt: timestamp,
|
|
115
|
+
lastEventSeq: 0,
|
|
116
|
+
};
|
|
117
|
+
runs.set(run.runId, run);
|
|
118
|
+
eventsByRunId.set(run.runId, []);
|
|
119
|
+
return clone(run);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
getRun(runId) {
|
|
123
|
+
const run = getMutableRun(runId);
|
|
124
|
+
return run ? clone(run) : null;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
updateRun(runId, patch = {}) {
|
|
128
|
+
const run = getMutableRun(runId);
|
|
129
|
+
if (!run) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (patch.sessionId !== undefined) {
|
|
134
|
+
run.sessionId = normalizeNullableString(patch.sessionId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (patch.clientRequestId !== undefined) {
|
|
138
|
+
run.clientRequestId = normalizeNullableString(patch.clientRequestId);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (patch.status !== undefined) {
|
|
142
|
+
const nextStatus = String(patch.status || '').trim();
|
|
143
|
+
if (RUN_STATUSES.has(nextStatus) && !RUN_TERMINAL_STATUSES.has(run.status)) {
|
|
144
|
+
run.status = nextStatus;
|
|
145
|
+
rememberTerminalExpiry(run);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
run.updatedAt = currentTimestamp();
|
|
150
|
+
return clone(run);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
appendRunEvent(runId, eventInput = {}) {
|
|
154
|
+
const run = getMutableRun(runId);
|
|
155
|
+
if (!run) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const nextSeq = run.lastEventSeq + 1;
|
|
160
|
+
const event = {
|
|
161
|
+
runId: run.runId,
|
|
162
|
+
seq: nextSeq,
|
|
163
|
+
type: eventInput.type || 'conversation-event',
|
|
164
|
+
provider: String(eventInput.provider || run.provider || '').trim() || run.provider,
|
|
165
|
+
sessionId: normalizeNullableString(eventInput.sessionId) || run.sessionId,
|
|
166
|
+
clientRequestId: normalizeNullableString(eventInput.clientRequestId) || run.clientRequestId,
|
|
167
|
+
event: clone(eventInput.event ?? null),
|
|
168
|
+
createdAt: currentTimestamp(),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
run.lastEventSeq = nextSeq;
|
|
172
|
+
run.updatedAt = event.createdAt;
|
|
173
|
+
if (event.sessionId && !run.sessionId) {
|
|
174
|
+
run.sessionId = event.sessionId;
|
|
175
|
+
}
|
|
176
|
+
if (event.clientRequestId && !run.clientRequestId) {
|
|
177
|
+
run.clientRequestId = event.clientRequestId;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
eventsByRunId.get(run.runId).push(event);
|
|
181
|
+
notifyRunEventSubscribers(event);
|
|
182
|
+
return clone(event);
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
listRunEvents(runId, { after = 0 } = {}) {
|
|
186
|
+
const run = getMutableRun(runId);
|
|
187
|
+
if (!run) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const minSeq = Number.isFinite(Number(after)) ? Number(after) : 0;
|
|
192
|
+
return (eventsByRunId.get(run.runId) || [])
|
|
193
|
+
.filter((event) => event.seq > minSeq)
|
|
194
|
+
.map(clone);
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
listSessionRuns({ provider, sessionId, status = null } = {}) {
|
|
198
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
199
|
+
const normalizedSessionId = normalizeNullableString(sessionId);
|
|
200
|
+
if (!normalizedSessionId) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return Array.from(runs.values())
|
|
205
|
+
.filter((run) => {
|
|
206
|
+
if (pruneExpiredRun(run.runId)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (run.provider !== normalizedProvider || run.sessionId !== normalizedSessionId) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (status === 'active') {
|
|
213
|
+
return RUN_ACTIVE_STATUSES.has(run.status);
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
})
|
|
217
|
+
.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt))
|
|
218
|
+
.map(clone);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
subscribeRunEvents(runId, callback) {
|
|
222
|
+
const run = getMutableRun(runId);
|
|
223
|
+
if (!run || typeof callback !== 'function') {
|
|
224
|
+
return () => {};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!subscribersByRunId.has(run.runId)) {
|
|
228
|
+
subscribersByRunId.set(run.runId, new Set());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const subscribers = subscribersByRunId.get(run.runId);
|
|
232
|
+
subscribers.add(callback);
|
|
233
|
+
|
|
234
|
+
return () => {
|
|
235
|
+
subscribers.delete(callback);
|
|
236
|
+
if (subscribers.size === 0) {
|
|
237
|
+
subscribersByRunId.delete(run.runId);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const runRegistry = createRunRegistry();
|
|
@@ -15,6 +15,7 @@ export const AGENT_RUNTIME_PHASES = {
|
|
|
15
15
|
QUEUED: 'queued',
|
|
16
16
|
STREAMING: 'streaming',
|
|
17
17
|
AWAITING_APPROVAL: 'awaiting_approval',
|
|
18
|
+
AWAITING_INPUT: 'awaiting_input',
|
|
18
19
|
COMPLETED: 'completed',
|
|
19
20
|
ABORTED: 'aborted',
|
|
20
21
|
ERRORED: 'errored'
|
|
@@ -23,7 +24,8 @@ export const AGENT_RUNTIME_PHASES = {
|
|
|
23
24
|
const ACTIVE_PHASES = new Set([
|
|
24
25
|
AGENT_RUNTIME_PHASES.QUEUED,
|
|
25
26
|
AGENT_RUNTIME_PHASES.STREAMING,
|
|
26
|
-
AGENT_RUNTIME_PHASES.AWAITING_APPROVAL
|
|
27
|
+
AGENT_RUNTIME_PHASES.AWAITING_APPROVAL,
|
|
28
|
+
AGENT_RUNTIME_PHASES.AWAITING_INPUT
|
|
27
29
|
]);
|
|
28
30
|
const TERMINAL_PHASES = new Set([
|
|
29
31
|
AGENT_RUNTIME_PHASES.COMPLETED,
|
|
@@ -188,11 +190,21 @@ function inferPhaseFromEvents(events = []) {
|
|
|
188
190
|
continue;
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
if (event.kind === CONVERSATION_EVENT_KINDS.ELICITATION_REQUEST) {
|
|
194
|
+
inferredPhase = AGENT_RUNTIME_PHASES.AWAITING_INPUT;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
if (event.kind === CONVERSATION_EVENT_KINDS.APPROVAL_RESOLVED) {
|
|
192
199
|
inferredPhase = AGENT_RUNTIME_PHASES.STREAMING;
|
|
193
200
|
continue;
|
|
194
201
|
}
|
|
195
202
|
|
|
203
|
+
if (event.kind === CONVERSATION_EVENT_KINDS.ELICITATION_RESOLVED) {
|
|
204
|
+
inferredPhase = AGENT_RUNTIME_PHASES.STREAMING;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
196
208
|
if (
|
|
197
209
|
event.kind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START ||
|
|
198
210
|
event.kind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA ||
|
|
@@ -252,6 +264,46 @@ function inferPendingApproval(events = []) {
|
|
|
252
264
|
return pendingRequests.size > 0;
|
|
253
265
|
}
|
|
254
266
|
|
|
267
|
+
function inferPendingElicitation(events = []) {
|
|
268
|
+
const pendingRequests = new Set();
|
|
269
|
+
|
|
270
|
+
for (const event of events) {
|
|
271
|
+
if (!event || typeof event !== 'object') {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const requestId = normalizeNonEmptyString(event.payload?.requestId) || normalizeNonEmptyString(event.extensions?.requestId);
|
|
276
|
+
|
|
277
|
+
if (event.kind === CONVERSATION_EVENT_KINDS.ELICITATION_REQUEST) {
|
|
278
|
+
if (requestId) {
|
|
279
|
+
pendingRequests.add(requestId);
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (event.kind === CONVERSATION_EVENT_KINDS.ELICITATION_RESOLVED) {
|
|
285
|
+
if (requestId) {
|
|
286
|
+
pendingRequests.delete(requestId);
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (event.kind === CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED) {
|
|
292
|
+
const sessionState = normalizePhase(event.payload?.state);
|
|
293
|
+
if (
|
|
294
|
+
sessionState === AGENT_RUNTIME_PHASES.IDLE ||
|
|
295
|
+
sessionState === AGENT_RUNTIME_PHASES.COMPLETED ||
|
|
296
|
+
sessionState === AGENT_RUNTIME_PHASES.ABORTED ||
|
|
297
|
+
sessionState === AGENT_RUNTIME_PHASES.ERRORED
|
|
298
|
+
) {
|
|
299
|
+
pendingRequests.clear();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return pendingRequests.size > 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
255
307
|
function getLatestEventTimestamp(events = []) {
|
|
256
308
|
const latestEvent = [...events].reverse().find((event) => normalizeNonEmptyString(event?.timestamp));
|
|
257
309
|
return latestEvent?.timestamp || null;
|
|
@@ -294,7 +346,17 @@ export async function buildSessionRuntimeSnapshot(provider, sessionId, options =
|
|
|
294
346
|
}
|
|
295
347
|
|
|
296
348
|
const recentEvents = Array.isArray(options.recentEvents) ? options.recentEvents : [];
|
|
297
|
-
|
|
349
|
+
let persistedEvents = [];
|
|
350
|
+
let context = null;
|
|
351
|
+
let error = null;
|
|
352
|
+
|
|
353
|
+
if (!options.skipPersistedEvents) {
|
|
354
|
+
const loaded = await loadProviderEvents(normalizedProvider, normalizedSessionId);
|
|
355
|
+
persistedEvents = loaded.events;
|
|
356
|
+
context = loaded.context;
|
|
357
|
+
error = loaded.error;
|
|
358
|
+
}
|
|
359
|
+
|
|
298
360
|
const mergedEvents = mergeConversationEventHistories(
|
|
299
361
|
persistedEvents,
|
|
300
362
|
recentEvents.filter((event) => event?.provider === normalizedProvider && event?.sessionId === normalizedSessionId)
|
|
@@ -308,11 +370,14 @@ export async function buildSessionRuntimeSnapshot(provider, sessionId, options =
|
|
|
308
370
|
|
|
309
371
|
let phase = inferPhaseFromEvents(mergedEvents);
|
|
310
372
|
const hasPendingApproval = active ? inferPendingApproval(mergedEvents) : false;
|
|
373
|
+
const hasPendingElicitation = active ? inferPendingElicitation(mergedEvents) : false;
|
|
311
374
|
|
|
312
375
|
if (TERMINAL_PHASES.has(phase)) {
|
|
313
376
|
// Keep terminal phase as-is.
|
|
314
377
|
} else if (hasPendingApproval) {
|
|
315
378
|
phase = AGENT_RUNTIME_PHASES.AWAITING_APPROVAL;
|
|
379
|
+
} else if (hasPendingElicitation) {
|
|
380
|
+
phase = AGENT_RUNTIME_PHASES.AWAITING_INPUT;
|
|
316
381
|
} else if (active) {
|
|
317
382
|
phase = ACTIVE_PHASES.has(phase) ? phase : AGENT_RUNTIME_PHASES.STREAMING;
|
|
318
383
|
} else {
|
|
@@ -346,6 +411,10 @@ export function subscribeToSessionRuntimeStateChanges(listener) {
|
|
|
346
411
|
}
|
|
347
412
|
|
|
348
413
|
export async function publishSessionRuntimeStateChanges(events = []) {
|
|
414
|
+
if (sessionRuntimeListeners.size === 0) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
349
418
|
const groupedRecentEvents = new Map();
|
|
350
419
|
|
|
351
420
|
events.forEach((event) => {
|
|
@@ -363,7 +432,10 @@ export async function publishSessionRuntimeStateChanges(events = []) {
|
|
|
363
432
|
|
|
364
433
|
await Promise.all(Array.from(groupedRecentEvents.entries()).map(async ([subscriptionKey, recentEvents]) => {
|
|
365
434
|
const [provider, sessionId] = subscriptionKey.split(':');
|
|
366
|
-
const snapshot = await buildSessionRuntimeSnapshot(provider, sessionId, {
|
|
435
|
+
const snapshot = await buildSessionRuntimeSnapshot(provider, sessionId, {
|
|
436
|
+
recentEvents,
|
|
437
|
+
skipPersistedEvents: true
|
|
438
|
+
});
|
|
367
439
|
if (!snapshot) {
|
|
368
440
|
return;
|
|
369
441
|
}
|