@a5c-ai/adapters-gateway 5.1.1-staging.52898ebfc24f
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 +20 -0
- package/dist/auth/bootstrap.d.ts +89 -0
- package/dist/auth/bootstrap.d.ts.map +1 -0
- package/dist/auth/bootstrap.js +222 -0
- package/dist/auth/bootstrap.js.map +1 -0
- package/dist/auth/hashing.d.ts +4 -0
- package/dist/auth/hashing.d.ts.map +1 -0
- package/dist/auth/hashing.js +27 -0
- package/dist/auth/hashing.js.map +1 -0
- package/dist/auth/middleware.d.ts +3 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +17 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/tokens.d.ts +45 -0
- package/dist/auth/tokens.d.ts.map +1 -0
- package/dist/auth/tokens.js +186 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/builtin-adapters.d.ts +17 -0
- package/dist/builtin-adapters.d.ts.map +1 -0
- package/dist/builtin-adapters.js +119 -0
- package/dist/builtin-adapters.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/fanout/client-conn.d.ts +20 -0
- package/dist/fanout/client-conn.d.ts.map +1 -0
- package/dist/fanout/client-conn.js +53 -0
- package/dist/fanout/client-conn.js.map +1 -0
- package/dist/fanout/subscriber.d.ts +12 -0
- package/dist/fanout/subscriber.d.ts.map +1 -0
- package/dist/fanout/subscriber.js +40 -0
- package/dist/fanout/subscriber.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/kanban/lib/config-loader.d.ts +29 -0
- package/dist/kanban/lib/config-loader.d.ts.map +1 -0
- package/dist/kanban/lib/config-loader.js +166 -0
- package/dist/kanban/lib/config-loader.js.map +1 -0
- package/dist/kanban/lib/config.d.ts +3 -0
- package/dist/kanban/lib/config.d.ts.map +1 -0
- package/dist/kanban/lib/config.js +6 -0
- package/dist/kanban/lib/config.js.map +1 -0
- package/dist/kanban/lib/create-global-registry.d.ts +28 -0
- package/dist/kanban/lib/create-global-registry.d.ts.map +1 -0
- package/dist/kanban/lib/create-global-registry.js +53 -0
- package/dist/kanban/lib/create-global-registry.js.map +1 -0
- package/dist/kanban/lib/dispatch-context-audit.d.ts +12 -0
- package/dist/kanban/lib/dispatch-context-audit.d.ts.map +1 -0
- package/dist/kanban/lib/dispatch-context-audit.js +44 -0
- package/dist/kanban/lib/dispatch-context-audit.js.map +1 -0
- package/dist/kanban/lib/error-handler.d.ts +28 -0
- package/dist/kanban/lib/error-handler.d.ts.map +1 -0
- package/dist/kanban/lib/error-handler.js +61 -0
- package/dist/kanban/lib/error-handler.js.map +1 -0
- package/dist/kanban/lib/global-registry.d.ts +49 -0
- package/dist/kanban/lib/global-registry.d.ts.map +1 -0
- package/dist/kanban/lib/global-registry.js +18 -0
- package/dist/kanban/lib/global-registry.js.map +1 -0
- package/dist/kanban/lib/parser.d.ts +36 -0
- package/dist/kanban/lib/parser.d.ts.map +1 -0
- package/dist/kanban/lib/parser.js +585 -0
- package/dist/kanban/lib/parser.js.map +1 -0
- package/dist/kanban/lib/path-resolver.d.ts +2 -0
- package/dist/kanban/lib/path-resolver.d.ts.map +1 -0
- package/dist/kanban/lib/path-resolver.js +16 -0
- package/dist/kanban/lib/path-resolver.js.map +1 -0
- package/dist/kanban/lib/review-service.d.ts +63 -0
- package/dist/kanban/lib/review-service.d.ts.map +1 -0
- package/dist/kanban/lib/review-service.js +571 -0
- package/dist/kanban/lib/review-service.js.map +1 -0
- package/dist/kanban/lib/run-cache.d.ts +36 -0
- package/dist/kanban/lib/run-cache.d.ts.map +1 -0
- package/dist/kanban/lib/run-cache.js +313 -0
- package/dist/kanban/lib/run-cache.js.map +1 -0
- package/dist/kanban/lib/server-init.d.ts +26 -0
- package/dist/kanban/lib/server-init.d.ts.map +1 -0
- package/dist/kanban/lib/server-init.js +179 -0
- package/dist/kanban/lib/server-init.js.map +1 -0
- package/dist/kanban/lib/services/automation-rule-service.d.ts +97 -0
- package/dist/kanban/lib/services/automation-rule-service.d.ts.map +1 -0
- package/dist/kanban/lib/services/automation-rule-service.js +806 -0
- package/dist/kanban/lib/services/automation-rule-service.js.map +1 -0
- package/dist/kanban/lib/services/automation-webhook-service.d.ts +44 -0
- package/dist/kanban/lib/services/automation-webhook-service.d.ts.map +1 -0
- package/dist/kanban/lib/services/automation-webhook-service.js +405 -0
- package/dist/kanban/lib/services/automation-webhook-service.js.map +1 -0
- package/dist/kanban/lib/services/backlog-query-service.d.ts +130 -0
- package/dist/kanban/lib/services/backlog-query-service.d.ts.map +1 -0
- package/dist/kanban/lib/services/backlog-query-service.js +1972 -0
- package/dist/kanban/lib/services/backlog-query-service.js.map +1 -0
- package/dist/kanban/lib/services/dispatch-context-label-service.d.ts +39 -0
- package/dist/kanban/lib/services/dispatch-context-label-service.d.ts.map +1 -0
- package/dist/kanban/lib/services/dispatch-context-label-service.js +160 -0
- package/dist/kanban/lib/services/dispatch-context-label-service.js.map +1 -0
- package/dist/kanban/lib/services/kanban-storage.d.ts +36 -0
- package/dist/kanban/lib/services/kanban-storage.d.ts.map +1 -0
- package/dist/kanban/lib/services/kanban-storage.js +26 -0
- package/dist/kanban/lib/services/kanban-storage.js.map +1 -0
- package/dist/kanban/lib/services/run-query-service.d.ts +79 -0
- package/dist/kanban/lib/services/run-query-service.d.ts.map +1 -0
- package/dist/kanban/lib/services/run-query-service.js +202 -0
- package/dist/kanban/lib/services/run-query-service.js.map +1 -0
- package/dist/kanban/lib/services/task-tag-service.d.ts +39 -0
- package/dist/kanban/lib/services/task-tag-service.d.ts.map +1 -0
- package/dist/kanban/lib/services/task-tag-service.js +145 -0
- package/dist/kanban/lib/services/task-tag-service.js.map +1 -0
- package/dist/kanban/lib/settings-section-storage.d.ts +13 -0
- package/dist/kanban/lib/settings-section-storage.d.ts.map +1 -0
- package/dist/kanban/lib/settings-section-storage.js +38 -0
- package/dist/kanban/lib/settings-section-storage.js.map +1 -0
- package/dist/kanban/lib/source-discovery.d.ts +10 -0
- package/dist/kanban/lib/source-discovery.d.ts.map +1 -0
- package/dist/kanban/lib/source-discovery.js +201 -0
- package/dist/kanban/lib/source-discovery.js.map +1 -0
- package/dist/kanban/lib/utils.d.ts +8 -0
- package/dist/kanban/lib/utils.d.ts.map +1 -0
- package/dist/kanban/lib/utils.js +116 -0
- package/dist/kanban/lib/utils.js.map +1 -0
- package/dist/kanban/lib/watcher.d.ts +14 -0
- package/dist/kanban/lib/watcher.d.ts.map +1 -0
- package/dist/kanban/lib/watcher.js +221 -0
- package/dist/kanban/lib/watcher.js.map +1 -0
- package/dist/kanban/lib/workspace-lifecycle.d.ts +68 -0
- package/dist/kanban/lib/workspace-lifecycle.d.ts.map +1 -0
- package/dist/kanban/lib/workspace-lifecycle.js +1085 -0
- package/dist/kanban/lib/workspace-lifecycle.js.map +1 -0
- package/dist/kanban/routes.d.ts +2 -0
- package/dist/kanban/routes.d.ts.map +1 -0
- package/dist/kanban/routes.js +1358 -0
- package/dist/kanban/routes.js.map +1 -0
- package/dist/kanban/types/breakpoint.d.ts +13 -0
- package/dist/kanban/types/breakpoint.d.ts.map +1 -0
- package/dist/kanban/types/breakpoint.js +3 -0
- package/dist/kanban/types/breakpoint.js.map +1 -0
- package/dist/kanban/types/index.d.ts +173 -0
- package/dist/kanban/types/index.d.ts.map +1 -0
- package/dist/kanban/types/index.js +3 -0
- package/dist/kanban/types/index.js.map +1 -0
- package/dist/logging.d.ts +7 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +22 -0
- package/dist/logging.js.map +1 -0
- package/dist/notifications/types.d.ts +18 -0
- package/dist/notifications/types.d.ts.map +1 -0
- package/dist/notifications/types.js +2 -0
- package/dist/notifications/types.js.map +1 -0
- package/dist/notifications/webhook-out.d.ts +3 -0
- package/dist/notifications/webhook-out.d.ts.map +1 -0
- package/dist/notifications/webhook-out.js +55 -0
- package/dist/notifications/webhook-out.js.map +1 -0
- package/dist/pairing/short-code.d.ts +20 -0
- package/dist/pairing/short-code.d.ts.map +1 -0
- package/dist/pairing/short-code.js +50 -0
- package/dist/pairing/short-code.js.map +1 -0
- package/dist/protocol/errors.d.ts +10 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +15 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/frames.d.ts +107 -0
- package/dist/protocol/frames.d.ts.map +1 -0
- package/dist/protocol/frames.js +146 -0
- package/dist/protocol/frames.js.map +1 -0
- package/dist/protocol/v1.d.ts +111 -0
- package/dist/protocol/v1.d.ts.map +1 -0
- package/dist/protocol/v1.js +2 -0
- package/dist/protocol/v1.js.map +1 -0
- package/dist/runs/event-log-index.d.ts +29 -0
- package/dist/runs/event-log-index.d.ts.map +1 -0
- package/dist/runs/event-log-index.js +210 -0
- package/dist/runs/event-log-index.js.map +1 -0
- package/dist/runs/event-log.d.ts +25 -0
- package/dist/runs/event-log.d.ts.map +1 -0
- package/dist/runs/event-log.js +104 -0
- package/dist/runs/event-log.js.map +1 -0
- package/dist/runs/hook-broker.d.ts +18 -0
- package/dist/runs/hook-broker.d.ts.map +1 -0
- package/dist/runs/hook-broker.js +110 -0
- package/dist/runs/hook-broker.js.map +1 -0
- package/dist/runs/manager.d.ts +57 -0
- package/dist/runs/manager.d.ts.map +1 -0
- package/dist/runs/manager.js +757 -0
- package/dist/runs/manager.js.map +1 -0
- package/dist/runs/session-runtime.d.ts +8 -0
- package/dist/runs/session-runtime.d.ts.map +1 -0
- package/dist/runs/session-runtime.js +291 -0
- package/dist/runs/session-runtime.js.map +1 -0
- package/dist/runs/types.d.ts +55 -0
- package/dist/runs/types.d.ts.map +1 -0
- package/dist/runs/types.js +2 -0
- package/dist/runs/types.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +702 -0
- package/dist/server.js.map +1 -0
- package/dist/static/webui-server.d.ts +2 -0
- package/dist/static/webui-server.d.ts.map +1 -0
- package/dist/static/webui-server.js +97 -0
- package/dist/static/webui-server.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { WorkspaceService, detectHostHarness } from '@a5c-ai/comm-adapter';
|
|
3
|
+
import { createGatewayRunClient } from '../builtin-adapters.js';
|
|
4
|
+
import { RunSubscriber } from '../fanout/subscriber.js';
|
|
5
|
+
import { createHookWebhookPayload, emitHookWebhook } from '../notifications/webhook-out.js';
|
|
6
|
+
import { EventLog } from './event-log.js';
|
|
7
|
+
import { HookBroker } from './hook-broker.js';
|
|
8
|
+
import { buildWorkspaceRuntimeSurface } from './session-runtime.js';
|
|
9
|
+
function cloneEntry(entry) {
|
|
10
|
+
return {
|
|
11
|
+
...entry,
|
|
12
|
+
owner: { ...entry.owner },
|
|
13
|
+
error: entry.error ? { ...entry.error } : null,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function classifyStatus(result) {
|
|
17
|
+
switch (result.exitReason) {
|
|
18
|
+
case 'completed':
|
|
19
|
+
return 'completed';
|
|
20
|
+
case 'aborted':
|
|
21
|
+
case 'interrupted':
|
|
22
|
+
case 'killed':
|
|
23
|
+
return 'aborted';
|
|
24
|
+
default:
|
|
25
|
+
return 'failed';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function cloneSession(entry) {
|
|
29
|
+
return { ...entry };
|
|
30
|
+
}
|
|
31
|
+
export class RunManager {
|
|
32
|
+
config;
|
|
33
|
+
logger;
|
|
34
|
+
static NATIVE_SESSIONS_CACHE_TTL_MS = 15_000;
|
|
35
|
+
static NATIVE_SESSION_CONTENT_CACHE_TTL_MS = 30_000;
|
|
36
|
+
client;
|
|
37
|
+
eventLog;
|
|
38
|
+
workspaceService = new WorkspaceService();
|
|
39
|
+
activeRuns = new Map();
|
|
40
|
+
observations = new Set();
|
|
41
|
+
subscribers = new Map();
|
|
42
|
+
sessionSubscribers = new Map();
|
|
43
|
+
hookBroker;
|
|
44
|
+
nativeSessionsCache = null;
|
|
45
|
+
nativeSessionsPromise = null;
|
|
46
|
+
nativeSessionContentCache = new Map();
|
|
47
|
+
nativeSessionContentPromises = new Map();
|
|
48
|
+
constructor(config, logger) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.logger = logger;
|
|
51
|
+
this.client = config.client ?? createGatewayRunClient();
|
|
52
|
+
this.eventLog = new EventLog(config.eventLogDir, config.maxEventsPerRun);
|
|
53
|
+
this.hookBroker = new HookBroker({
|
|
54
|
+
getSubscribers: (runId) => Array.from(this.subscribers.get(runId)?.values() ?? []).map((subscriber) => subscriber.conn),
|
|
55
|
+
send: (frame, recipients) => {
|
|
56
|
+
for (const recipient of recipients) {
|
|
57
|
+
recipient.send(frame);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
persist: async (runId, event) => {
|
|
61
|
+
await this.recordGatewayEvent(runId, event);
|
|
62
|
+
},
|
|
63
|
+
notify: async (frame) => {
|
|
64
|
+
if (!this.config.notificationWebhook) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const payload = createHookWebhookPayload(frame.runId, frame.hookRequestId, frame.hookKind, frame.payload);
|
|
68
|
+
await emitHookWebhook(this.config.notificationWebhook, payload);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async start(input, owner) {
|
|
73
|
+
if (this.activeRuns.size >= this.config.maxConcurrentRuns) {
|
|
74
|
+
throw new Error(`maxConcurrentRuns limit reached (${this.config.maxConcurrentRuns})`);
|
|
75
|
+
}
|
|
76
|
+
const workspace = await this.workspaceService.resolveSessionContext({
|
|
77
|
+
workspaceId: input.workspaceId,
|
|
78
|
+
cwd: typeof input.cwd === 'string' ? input.cwd : undefined,
|
|
79
|
+
});
|
|
80
|
+
let handle;
|
|
81
|
+
const hooks = {
|
|
82
|
+
preToolUse: async (payload) => this.hookBroker.requestDecision(handle.runId, 'preToolUse', payload, this.config.hookDecisionTimeoutMs),
|
|
83
|
+
userPromptSubmit: async (payload) => this.hookBroker.requestDecision(handle.runId, 'userPromptSubmit', payload, this.config.hookDecisionTimeoutMs),
|
|
84
|
+
};
|
|
85
|
+
handle = this.client.run({
|
|
86
|
+
...input,
|
|
87
|
+
runId: input.runId,
|
|
88
|
+
hooks,
|
|
89
|
+
collectEvents: false,
|
|
90
|
+
gracePeriodMs: this.config.shutdownGraceMs,
|
|
91
|
+
});
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
const entry = {
|
|
94
|
+
runId: handle.runId,
|
|
95
|
+
agent: input.agent,
|
|
96
|
+
model: input.model,
|
|
97
|
+
cwd: typeof input.cwd === 'string' ? input.cwd : undefined,
|
|
98
|
+
sessionId: input.sessionId,
|
|
99
|
+
status: 'running',
|
|
100
|
+
createdAt: now,
|
|
101
|
+
startedAt: now,
|
|
102
|
+
endedAt: null,
|
|
103
|
+
owner,
|
|
104
|
+
workspaceId: input.workspaceId ?? workspace?.workspaceId,
|
|
105
|
+
workspace: workspace ?? undefined,
|
|
106
|
+
error: null,
|
|
107
|
+
};
|
|
108
|
+
this.eventLog.index.upsertRun(entry);
|
|
109
|
+
const active = {
|
|
110
|
+
entry,
|
|
111
|
+
handle,
|
|
112
|
+
recentEvents: [],
|
|
113
|
+
};
|
|
114
|
+
this.activeRuns.set(handle.runId, active);
|
|
115
|
+
if (input.sessionId) {
|
|
116
|
+
this.invalidateNativeSessionCaches(input.sessionId);
|
|
117
|
+
if (entry.workspaceId) {
|
|
118
|
+
await this.workspaceService.bindSession({
|
|
119
|
+
workspaceId: entry.workspaceId,
|
|
120
|
+
session: {
|
|
121
|
+
agent: input.agent,
|
|
122
|
+
sessionId: input.sessionId,
|
|
123
|
+
status: 'running',
|
|
124
|
+
cwd: typeof input.cwd === 'string' ? input.cwd : undefined,
|
|
125
|
+
updatedAt: new Date(now).toISOString(),
|
|
126
|
+
activeRunId: handle.runId,
|
|
127
|
+
latestRunId: handle.runId,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
await this.attachSessionSubscribersToRun(input.sessionId, handle.runId);
|
|
132
|
+
}
|
|
133
|
+
await this.recordGatewayEvent(handle.runId, {
|
|
134
|
+
type: 'user_message',
|
|
135
|
+
text: Array.isArray(input.prompt) ? input.prompt.join('\n') : input.prompt,
|
|
136
|
+
});
|
|
137
|
+
const observation = this.observeRun(active).finally(() => {
|
|
138
|
+
this.observations.delete(observation);
|
|
139
|
+
});
|
|
140
|
+
this.observations.add(observation);
|
|
141
|
+
this.logger.info('Gateway run started', {
|
|
142
|
+
runId: handle.runId,
|
|
143
|
+
agent: input.agent,
|
|
144
|
+
});
|
|
145
|
+
return cloneEntry(entry);
|
|
146
|
+
}
|
|
147
|
+
async stop(runId) {
|
|
148
|
+
const active = this.activeRuns.get(runId);
|
|
149
|
+
if (!active) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
await active.handle.abort();
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
async sendInput(runId, input) {
|
|
156
|
+
const active = this.activeRuns.get(runId);
|
|
157
|
+
if (!active) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
await active.handle.send(input);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
async sendSessionInput(sessionId, input, owner, overrides = {}) {
|
|
164
|
+
const session = await this.getSession(sessionId);
|
|
165
|
+
if (!session) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const requestedAgent = typeof overrides.agent === 'string' && overrides.agent.trim().length > 0
|
|
169
|
+
? overrides.agent.trim()
|
|
170
|
+
: session.agent;
|
|
171
|
+
if (session.activeRunId) {
|
|
172
|
+
const adapter = this.client.adapters?.get(session.agent);
|
|
173
|
+
const transport = adapter?.capabilities?.structuredSessionTransport ?? 'none';
|
|
174
|
+
if (transport === 'persistent' && requestedAgent === session.agent) {
|
|
175
|
+
await this.recordGatewayEvent(session.activeRunId, {
|
|
176
|
+
type: 'user_message',
|
|
177
|
+
text: input,
|
|
178
|
+
sessionId,
|
|
179
|
+
});
|
|
180
|
+
const sent = await this.sendInput(session.activeRunId, input);
|
|
181
|
+
if (!sent) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return this.get(session.activeRunId);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return await this.start({
|
|
188
|
+
agent: requestedAgent,
|
|
189
|
+
model: overrides.model,
|
|
190
|
+
prompt: input,
|
|
191
|
+
attachments: overrides.attachments,
|
|
192
|
+
approvalMode: overrides.approvalMode,
|
|
193
|
+
sessionId: requestedAgent === session.agent ? sessionId : undefined,
|
|
194
|
+
forkSessionId: requestedAgent !== session.agent ? sessionId : undefined,
|
|
195
|
+
cwd: session.cwd ?? session.workspace?.currentPath ?? session.workspace?.workspaceDefaultCwd,
|
|
196
|
+
workspaceId: session.workspaceId ?? session.workspace?.workspaceId,
|
|
197
|
+
}, owner);
|
|
198
|
+
}
|
|
199
|
+
get(runId) {
|
|
200
|
+
const active = this.activeRuns.get(runId);
|
|
201
|
+
if (active) {
|
|
202
|
+
return cloneEntry(active.entry);
|
|
203
|
+
}
|
|
204
|
+
const indexed = this.eventLog.index.getRun(runId);
|
|
205
|
+
return indexed ? cloneEntry(indexed) : null;
|
|
206
|
+
}
|
|
207
|
+
list() {
|
|
208
|
+
const seen = new Set();
|
|
209
|
+
const runs = [];
|
|
210
|
+
for (const active of this.activeRuns.values()) {
|
|
211
|
+
seen.add(active.entry.runId);
|
|
212
|
+
runs.push(cloneEntry(active.entry));
|
|
213
|
+
}
|
|
214
|
+
for (const indexed of this.eventLog.index.listRuns()) {
|
|
215
|
+
if (seen.has(indexed.runId))
|
|
216
|
+
continue;
|
|
217
|
+
runs.push(cloneEntry(indexed));
|
|
218
|
+
}
|
|
219
|
+
runs.sort((left, right) => right.startedAt - left.startedAt);
|
|
220
|
+
return runs;
|
|
221
|
+
}
|
|
222
|
+
async getSession(sessionId) {
|
|
223
|
+
return (await this.listSessions()).find((entry) => entry.sessionId === sessionId) ?? null;
|
|
224
|
+
}
|
|
225
|
+
async getSessionContent(sessionId) {
|
|
226
|
+
const cached = this.nativeSessionContentCache.get(sessionId);
|
|
227
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
228
|
+
const session = await this.getSession(sessionId);
|
|
229
|
+
return {
|
|
230
|
+
...cached.value,
|
|
231
|
+
workspace: session?.workspace,
|
|
232
|
+
workspaceId: session?.workspaceId,
|
|
233
|
+
runtime: session?.runtime,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const inFlight = this.nativeSessionContentPromises.get(sessionId);
|
|
237
|
+
if (inFlight) {
|
|
238
|
+
return await inFlight;
|
|
239
|
+
}
|
|
240
|
+
const session = await this.getSession(sessionId);
|
|
241
|
+
if (!session) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const load = this.loadSessionContent(session.agent, sessionId)
|
|
245
|
+
.then((result) => {
|
|
246
|
+
if (result) {
|
|
247
|
+
this.nativeSessionContentCache.set(sessionId, {
|
|
248
|
+
value: result,
|
|
249
|
+
expiresAt: Date.now() + RunManager.NATIVE_SESSION_CONTENT_CACHE_TTL_MS,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
})
|
|
254
|
+
.finally(() => {
|
|
255
|
+
this.nativeSessionContentPromises.delete(sessionId);
|
|
256
|
+
});
|
|
257
|
+
this.nativeSessionContentPromises.set(sessionId, load);
|
|
258
|
+
const result = await load;
|
|
259
|
+
if (!result) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const latest = await this.getSession(sessionId);
|
|
263
|
+
return {
|
|
264
|
+
...result,
|
|
265
|
+
workspace: latest?.workspace ?? result.workspace,
|
|
266
|
+
workspaceId: latest?.workspaceId ?? result.workspaceId,
|
|
267
|
+
runtime: latest?.runtime,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async listSessions() {
|
|
271
|
+
const grouped = new Map();
|
|
272
|
+
for (const run of this.list()) {
|
|
273
|
+
if (!run.sessionId) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const bucket = grouped.get(run.sessionId) ?? [];
|
|
277
|
+
bucket.push(run);
|
|
278
|
+
grouped.set(run.sessionId, bucket);
|
|
279
|
+
}
|
|
280
|
+
const sessions = new Map();
|
|
281
|
+
for (const [sessionId, runs] of grouped.entries()) {
|
|
282
|
+
runs.sort((left, right) => right.startedAt - left.startedAt);
|
|
283
|
+
const latest = runs[0];
|
|
284
|
+
const active = runs.find((run) => run.status === 'running') ?? null;
|
|
285
|
+
sessions.set(sessionId, {
|
|
286
|
+
sessionId,
|
|
287
|
+
agent: latest.agent,
|
|
288
|
+
status: active ? 'active' : 'inactive',
|
|
289
|
+
activeRunId: active?.runId ?? null,
|
|
290
|
+
latestRunId: latest.runId,
|
|
291
|
+
createdAt: runs.reduce((min, run) => Math.min(min, run.createdAt), latest.createdAt),
|
|
292
|
+
updatedAt: active?.startedAt ?? latest.endedAt ?? latest.startedAt,
|
|
293
|
+
latestRunStartedAt: latest.startedAt,
|
|
294
|
+
latestRunEndedAt: latest.endedAt,
|
|
295
|
+
latestExitReason: latest.exitReason,
|
|
296
|
+
model: latest.model,
|
|
297
|
+
cwd: active?.cwd ?? latest.cwd,
|
|
298
|
+
workspaceId: active?.workspaceId ?? latest.workspaceId,
|
|
299
|
+
workspace: active?.workspace ?? latest.workspace,
|
|
300
|
+
source: 'gateway',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
for (const native of await this.listNativeSessions()) {
|
|
304
|
+
const existing = sessions.get(native.sessionId);
|
|
305
|
+
if (!existing) {
|
|
306
|
+
sessions.set(native.sessionId, native);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
sessions.set(native.sessionId, {
|
|
310
|
+
...existing,
|
|
311
|
+
createdAt: Math.min(existing.createdAt, native.createdAt),
|
|
312
|
+
updatedAt: Math.max(existing.updatedAt, native.updatedAt),
|
|
313
|
+
title: existing.title ?? native.title,
|
|
314
|
+
turnCount: existing.turnCount ?? native.turnCount,
|
|
315
|
+
messageCount: existing.messageCount ?? native.messageCount,
|
|
316
|
+
model: existing.model ?? native.model,
|
|
317
|
+
cost: existing.cost ?? native.cost,
|
|
318
|
+
cwd: existing.cwd ?? native.cwd,
|
|
319
|
+
workspaceId: existing.workspaceId ?? native.workspaceId,
|
|
320
|
+
workspace: existing.workspace ?? native.workspace,
|
|
321
|
+
source: 'merged',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const enriched = await Promise.all(Array.from(sessions.values()).map(async (session) => {
|
|
325
|
+
const runs = grouped.get(session.sessionId) ?? [];
|
|
326
|
+
session.runtime = await this.buildRuntimeSurface(session, runs);
|
|
327
|
+
return session;
|
|
328
|
+
}));
|
|
329
|
+
return enriched
|
|
330
|
+
.sort((left, right) => right.updatedAt - left.updatedAt)
|
|
331
|
+
.map(cloneSession);
|
|
332
|
+
}
|
|
333
|
+
async listWorkspaces() {
|
|
334
|
+
return await this.workspaceService.listWorkspaces({
|
|
335
|
+
liveSessions: (await this.listSessions()).map((session) => this.toWorkspaceSessionBinding(session)),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
async subscribe(conn, runId, sinceSeq = 0) {
|
|
339
|
+
const state = this.eventLog.getSeqState(runId);
|
|
340
|
+
if (state.tailSeq > 0 && sinceSeq < state.headSeq - 1) {
|
|
341
|
+
conn.send({
|
|
342
|
+
type: 'error',
|
|
343
|
+
code: 'seq_gone',
|
|
344
|
+
message: 'Requested sequence is no longer available',
|
|
345
|
+
runId,
|
|
346
|
+
tailSeq: state.tailSeq,
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
let subscriberMap = this.subscribers.get(runId);
|
|
351
|
+
if (!subscriberMap) {
|
|
352
|
+
subscriberMap = new Map();
|
|
353
|
+
this.subscribers.set(runId, subscriberMap);
|
|
354
|
+
}
|
|
355
|
+
const existing = subscriberMap.get(conn.id);
|
|
356
|
+
if (existing) {
|
|
357
|
+
subscriberMap.delete(conn.id);
|
|
358
|
+
}
|
|
359
|
+
const subscriber = new RunSubscriber(conn, runId, state.tailSeq);
|
|
360
|
+
subscriberMap.set(conn.id, subscriber);
|
|
361
|
+
conn.subscriptions.add(runId);
|
|
362
|
+
const replay = await this.readReplay(runId, sinceSeq, state.tailSeq);
|
|
363
|
+
subscriber.replay(replay);
|
|
364
|
+
subscriber.finishCatchUp();
|
|
365
|
+
}
|
|
366
|
+
async subscribeSession(conn, sessionId) {
|
|
367
|
+
let subscriberMap = this.sessionSubscribers.get(sessionId);
|
|
368
|
+
if (!subscriberMap) {
|
|
369
|
+
subscriberMap = new Map();
|
|
370
|
+
this.sessionSubscribers.set(sessionId, subscriberMap);
|
|
371
|
+
}
|
|
372
|
+
subscriberMap.set(conn.id, conn);
|
|
373
|
+
conn.sessionSubscriptions.add(sessionId);
|
|
374
|
+
const session = await this.getSession(sessionId);
|
|
375
|
+
if (session?.activeRunId) {
|
|
376
|
+
await this.attachSessionSubscribersToRun(sessionId, session.activeRunId);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
unsubscribeSession(conn, sessionId) {
|
|
380
|
+
conn.sessionSubscriptions.delete(sessionId);
|
|
381
|
+
const subscriberMap = this.sessionSubscribers.get(sessionId);
|
|
382
|
+
if (!subscriberMap) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
subscriberMap.delete(conn.id);
|
|
386
|
+
if (subscriberMap.size === 0) {
|
|
387
|
+
this.sessionSubscribers.delete(sessionId);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
unsubscribe(conn, runId) {
|
|
391
|
+
conn.subscriptions.delete(runId);
|
|
392
|
+
const subscriberMap = this.subscribers.get(runId);
|
|
393
|
+
if (!subscriberMap)
|
|
394
|
+
return;
|
|
395
|
+
subscriberMap.delete(conn.id);
|
|
396
|
+
if (subscriberMap.size === 0) {
|
|
397
|
+
this.subscribers.delete(runId);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
removeConnection(conn) {
|
|
401
|
+
for (const runId of Array.from(conn.subscriptions)) {
|
|
402
|
+
this.unsubscribe(conn, runId);
|
|
403
|
+
}
|
|
404
|
+
for (const sessionId of Array.from(conn.sessionSubscriptions)) {
|
|
405
|
+
this.unsubscribeSession(conn, sessionId);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
submitHookDecision(conn, frame) {
|
|
409
|
+
return this.hookBroker.submitDecision(conn, frame);
|
|
410
|
+
}
|
|
411
|
+
async shutdown() {
|
|
412
|
+
const pending = Array.from(this.activeRuns.values());
|
|
413
|
+
for (const active of pending) {
|
|
414
|
+
await active.handle.abort();
|
|
415
|
+
}
|
|
416
|
+
await Promise.race([
|
|
417
|
+
Promise.allSettled(pending.map((active) => active.handle.result())),
|
|
418
|
+
new Promise((resolve) => {
|
|
419
|
+
setTimeout(resolve, this.config.shutdownGraceMs);
|
|
420
|
+
}),
|
|
421
|
+
]);
|
|
422
|
+
await Promise.allSettled(Array.from(this.observations));
|
|
423
|
+
await this.eventLog.close();
|
|
424
|
+
}
|
|
425
|
+
async observeRun(active) {
|
|
426
|
+
const { handle } = active;
|
|
427
|
+
try {
|
|
428
|
+
for await (const event of handle) {
|
|
429
|
+
let eventToRecord = event;
|
|
430
|
+
if (event.type === 'session_start' && 'sessionId' in event && typeof event.sessionId === 'string') {
|
|
431
|
+
const resumedSessionId = event.resumed === true && typeof active.entry.sessionId === 'string' && active.entry.sessionId.length > 0
|
|
432
|
+
? active.entry.sessionId
|
|
433
|
+
: event.sessionId;
|
|
434
|
+
this.invalidateNativeSessionCaches(resumedSessionId);
|
|
435
|
+
if (resumedSessionId !== event.sessionId) {
|
|
436
|
+
eventToRecord = {
|
|
437
|
+
...event,
|
|
438
|
+
sessionId: resumedSessionId,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
active.entry.sessionId = resumedSessionId;
|
|
442
|
+
this.eventLog.index.upsertRun(active.entry);
|
|
443
|
+
if (active.entry.workspaceId) {
|
|
444
|
+
await this.workspaceService.bindSession({
|
|
445
|
+
workspaceId: active.entry.workspaceId,
|
|
446
|
+
session: {
|
|
447
|
+
agent: active.entry.agent,
|
|
448
|
+
sessionId: resumedSessionId,
|
|
449
|
+
status: 'running',
|
|
450
|
+
cwd: active.entry.cwd,
|
|
451
|
+
updatedAt: new Date().toISOString(),
|
|
452
|
+
activeRunId: handle.runId,
|
|
453
|
+
latestRunId: handle.runId,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
await this.attachSessionSubscribersToRun(resumedSessionId, handle.runId);
|
|
458
|
+
}
|
|
459
|
+
await this.recordAgentEvent(handle.runId, eventToRecord);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
const result = await handle.result();
|
|
464
|
+
active.entry.status = classifyStatus(result);
|
|
465
|
+
active.entry.endedAt = Date.now();
|
|
466
|
+
active.entry.exitReason = result.exitReason;
|
|
467
|
+
active.entry.error = result.error
|
|
468
|
+
? {
|
|
469
|
+
code: result.error.code,
|
|
470
|
+
message: result.error.message,
|
|
471
|
+
}
|
|
472
|
+
: null;
|
|
473
|
+
this.eventLog.index.upsertRun(active.entry);
|
|
474
|
+
if (active.entry.sessionId) {
|
|
475
|
+
this.invalidateNativeSessionCaches(active.entry.sessionId);
|
|
476
|
+
}
|
|
477
|
+
if (active.entry.workspaceId && active.entry.sessionId) {
|
|
478
|
+
await this.workspaceService.bindSession({
|
|
479
|
+
workspaceId: active.entry.workspaceId,
|
|
480
|
+
session: {
|
|
481
|
+
agent: active.entry.agent,
|
|
482
|
+
sessionId: active.entry.sessionId,
|
|
483
|
+
status: 'stopped',
|
|
484
|
+
cwd: active.entry.cwd,
|
|
485
|
+
updatedAt: new Date().toISOString(),
|
|
486
|
+
activeRunId: null,
|
|
487
|
+
latestRunId: active.entry.runId,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
await this.recordGatewayEvent(handle.runId, {
|
|
492
|
+
type: 'run.finalized',
|
|
493
|
+
exitReason: result.exitReason,
|
|
494
|
+
exitCode: result.exitCode,
|
|
495
|
+
signal: result.signal,
|
|
496
|
+
error: result.error,
|
|
497
|
+
});
|
|
498
|
+
this.activeRuns.delete(handle.runId);
|
|
499
|
+
this.logger.info('Gateway run finished', {
|
|
500
|
+
runId: handle.runId,
|
|
501
|
+
exitReason: result.exitReason,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async recordAgentEvent(runId, event) {
|
|
506
|
+
await this.recordLoggedEvents(runId, 'agent', event);
|
|
507
|
+
}
|
|
508
|
+
async recordGatewayEvent(runId, event) {
|
|
509
|
+
await this.recordLoggedEvents(runId, 'gateway', event);
|
|
510
|
+
}
|
|
511
|
+
async recordLoggedEvents(runId, source, event) {
|
|
512
|
+
const loggedEvents = await this.eventLog.append(runId, source, event);
|
|
513
|
+
for (const logged of loggedEvents) {
|
|
514
|
+
const active = this.activeRuns.get(runId);
|
|
515
|
+
if (active) {
|
|
516
|
+
active.recentEvents.push(logged);
|
|
517
|
+
if (active.recentEvents.length > this.config.replayBufferSize) {
|
|
518
|
+
active.recentEvents.splice(0, active.recentEvents.length - this.config.replayBufferSize);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const subscriberMap = this.subscribers.get(runId);
|
|
522
|
+
if (!subscriberMap)
|
|
523
|
+
continue;
|
|
524
|
+
for (const subscriber of subscriberMap.values()) {
|
|
525
|
+
subscriber.sendLive(logged);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async readReplay(runId, sinceSeq, tailSeq) {
|
|
530
|
+
const active = this.activeRuns.get(runId);
|
|
531
|
+
const recent = active?.recentEvents;
|
|
532
|
+
if (recent && recent.length > 0) {
|
|
533
|
+
const firstSeq = recent[0].seq;
|
|
534
|
+
const lastSeq = recent[recent.length - 1].seq;
|
|
535
|
+
if (sinceSeq >= firstSeq - 1 && tailSeq <= lastSeq) {
|
|
536
|
+
return recent.filter((event) => event.seq > sinceSeq && event.seq <= tailSeq);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const replay = await this.eventLog.readSince(runId, sinceSeq, tailSeq);
|
|
540
|
+
return replay.events;
|
|
541
|
+
}
|
|
542
|
+
async attachSessionSubscribersToRun(sessionId, runId) {
|
|
543
|
+
const subscribers = this.sessionSubscribers.get(sessionId);
|
|
544
|
+
if (!subscribers || subscribers.size === 0) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
await Promise.all(Array.from(subscribers.values()).map(async (conn) => {
|
|
548
|
+
if (conn.subscriptions.has(runId)) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
await this.subscribe(conn, runId, 0);
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
invalidateNativeSessionCaches(sessionId) {
|
|
555
|
+
this.nativeSessionsCache = null;
|
|
556
|
+
if (sessionId) {
|
|
557
|
+
this.nativeSessionContentCache.delete(sessionId);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
this.nativeSessionContentCache.clear();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async listNativeSessions() {
|
|
564
|
+
const cached = this.nativeSessionsCache;
|
|
565
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
566
|
+
return cached.value.map(cloneSession);
|
|
567
|
+
}
|
|
568
|
+
if (this.nativeSessionsPromise) {
|
|
569
|
+
return (await this.nativeSessionsPromise).map(cloneSession);
|
|
570
|
+
}
|
|
571
|
+
const load = this.loadNativeSessions()
|
|
572
|
+
.then((sessions) => {
|
|
573
|
+
this.nativeSessionsCache = {
|
|
574
|
+
value: sessions.map(cloneSession),
|
|
575
|
+
expiresAt: Date.now() + RunManager.NATIVE_SESSIONS_CACHE_TTL_MS,
|
|
576
|
+
};
|
|
577
|
+
return sessions;
|
|
578
|
+
})
|
|
579
|
+
.finally(() => {
|
|
580
|
+
this.nativeSessionsPromise = null;
|
|
581
|
+
});
|
|
582
|
+
this.nativeSessionsPromise = load;
|
|
583
|
+
return (await load).map(cloneSession);
|
|
584
|
+
}
|
|
585
|
+
async loadNativeSessions() {
|
|
586
|
+
const client = this.client;
|
|
587
|
+
if (!client.sessions || !client.adapters) {
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
const ambientSession = this.getAmbientNativeSessionBinding();
|
|
591
|
+
let agents = client.adapters.list().map((entry) => entry.agent);
|
|
592
|
+
if (typeof client.adapters.installed === 'function') {
|
|
593
|
+
try {
|
|
594
|
+
const installed = await client.adapters.installed();
|
|
595
|
+
const runnableAgents = installed
|
|
596
|
+
.filter((entry) => entry.installed && entry.meetsMinVersion)
|
|
597
|
+
.map((entry) => entry.agent);
|
|
598
|
+
if (runnableAgents.length > 0) {
|
|
599
|
+
agents = runnableAgents;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
// Fall back to registered adapters when installation discovery fails.
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const discovered = await Promise.all(agents.map(async (agent) => {
|
|
607
|
+
try {
|
|
608
|
+
return await client.sessions.list(agent, { limit: 100 });
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return [];
|
|
612
|
+
}
|
|
613
|
+
}));
|
|
614
|
+
const nativeSessions = discovered.flatMap((sessions) => sessions);
|
|
615
|
+
return await Promise.all(nativeSessions.map(async (session) => {
|
|
616
|
+
const workspace = await this.workspaceService.resolveSessionContext({ cwd: session.cwd });
|
|
617
|
+
return {
|
|
618
|
+
sessionId: session.sessionId,
|
|
619
|
+
agent: session.agent,
|
|
620
|
+
status: ambientSession?.agent === session.agent && ambientSession?.sessionId === session.sessionId
|
|
621
|
+
? 'active'
|
|
622
|
+
: 'inactive',
|
|
623
|
+
activeRunId: null,
|
|
624
|
+
latestRunId: null,
|
|
625
|
+
createdAt: session.createdAt.getTime(),
|
|
626
|
+
updatedAt: session.updatedAt.getTime(),
|
|
627
|
+
latestRunStartedAt: null,
|
|
628
|
+
latestRunEndedAt: null,
|
|
629
|
+
title: session.title,
|
|
630
|
+
turnCount: session.turnCount,
|
|
631
|
+
messageCount: session.messageCount,
|
|
632
|
+
model: session.model,
|
|
633
|
+
cost: session.cost,
|
|
634
|
+
cwd: session.cwd,
|
|
635
|
+
workspace: workspace ?? undefined,
|
|
636
|
+
workspaceId: workspace?.workspaceId,
|
|
637
|
+
source: 'native',
|
|
638
|
+
};
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
getAmbientNativeSessionBinding() {
|
|
642
|
+
const detected = detectHostHarness();
|
|
643
|
+
const sessionId = detected?.metadata && typeof detected.metadata['session_id'] === 'string'
|
|
644
|
+
? detected.metadata['session_id'].trim()
|
|
645
|
+
: '';
|
|
646
|
+
if (!detected || sessionId.length === 0) {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
agent: detected.agent,
|
|
651
|
+
sessionId,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async loadSessionContent(agent, sessionId) {
|
|
655
|
+
const client = this.client;
|
|
656
|
+
const direct = await this.tryLoadSessionContentDirect(agent, sessionId);
|
|
657
|
+
if (direct) {
|
|
658
|
+
return direct;
|
|
659
|
+
}
|
|
660
|
+
if (!client.sessions?.get) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
const session = await client.sessions.get(agent, sessionId);
|
|
665
|
+
const workspace = session.workspace ?? await this.workspaceService.resolveSessionContext({ cwd: session.cwd });
|
|
666
|
+
return {
|
|
667
|
+
...session,
|
|
668
|
+
workspace: workspace ?? undefined,
|
|
669
|
+
workspaceId: session.workspaceId ?? workspace?.workspaceId,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async tryLoadSessionContentDirect(agent, sessionId) {
|
|
677
|
+
const client = this.client;
|
|
678
|
+
const adapter = client.adapters?.get?.(agent);
|
|
679
|
+
if (!adapter?.listSessionFiles || !adapter.parseSessionFile) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
let filePaths;
|
|
683
|
+
try {
|
|
684
|
+
filePaths = await adapter.listSessionFiles();
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
const candidate = filePaths.find((filePath) => path.basename(filePath, path.extname(filePath)) === sessionId);
|
|
690
|
+
if (!candidate) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
const parsed = await adapter.parseSessionFile(candidate);
|
|
695
|
+
const workspace = parsed.workspace ?? await this.workspaceService.resolveSessionContext({ cwd: parsed.cwd });
|
|
696
|
+
return {
|
|
697
|
+
agent: parsed.agent,
|
|
698
|
+
sessionId: parsed.sessionId,
|
|
699
|
+
unifiedId: `${parsed.agent}:${parsed.sessionId}`,
|
|
700
|
+
title: parsed.title ?? '',
|
|
701
|
+
createdAt: new Date(parsed.createdAt),
|
|
702
|
+
updatedAt: new Date(parsed.updatedAt),
|
|
703
|
+
turnCount: parsed.turnCount,
|
|
704
|
+
model: parsed.model,
|
|
705
|
+
cost: parsed.cost,
|
|
706
|
+
tags: parsed.tags ?? [],
|
|
707
|
+
cwd: parsed.cwd,
|
|
708
|
+
workspace: workspace ?? undefined,
|
|
709
|
+
workspaceId: parsed.workspaceId ?? workspace?.workspaceId,
|
|
710
|
+
forkedFrom: parsed.forkedFrom,
|
|
711
|
+
runtime: undefined,
|
|
712
|
+
messages: parsed.messages ?? [],
|
|
713
|
+
raw: parsed.raw,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
catch {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async buildRuntimeSurface(session, runs) {
|
|
721
|
+
const workspacePath = session.cwd ?? session.workspace?.currentPath ?? session.workspace?.workspaceDefaultCwd;
|
|
722
|
+
if ((!workspacePath || workspacePath.length === 0) && runs.length === 0) {
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
const eventsByRunId = new Map();
|
|
726
|
+
for (const run of runs) {
|
|
727
|
+
eventsByRunId.set(run.runId, await this.readRecentEvents(run.runId));
|
|
728
|
+
}
|
|
729
|
+
return buildWorkspaceRuntimeSurface({
|
|
730
|
+
cwd: workspacePath,
|
|
731
|
+
runs,
|
|
732
|
+
eventsByRunId,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
async readRecentEvents(runId, limit = 120) {
|
|
736
|
+
const state = this.eventLog.getSeqState(runId);
|
|
737
|
+
if (state.tailSeq <= 0) {
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
const sinceSeq = Math.max(0, state.tailSeq - limit);
|
|
741
|
+
const replay = await this.eventLog.readSince(runId, sinceSeq, state.tailSeq);
|
|
742
|
+
return replay.events;
|
|
743
|
+
}
|
|
744
|
+
toWorkspaceSessionBinding(session) {
|
|
745
|
+
return {
|
|
746
|
+
agent: session.agent,
|
|
747
|
+
sessionId: session.sessionId,
|
|
748
|
+
status: session.status === 'active' ? 'running' : 'stopped',
|
|
749
|
+
cwd: session.cwd ?? session.workspace?.currentPath ?? session.workspace?.workspaceDefaultCwd,
|
|
750
|
+
title: session.title,
|
|
751
|
+
updatedAt: new Date(session.updatedAt).toISOString(),
|
|
752
|
+
activeRunId: session.activeRunId,
|
|
753
|
+
latestRunId: session.latestRunId,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
//# sourceMappingURL=manager.js.map
|