tabminal 3.0.8 → 3.0.10
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/package.json +1 -1
- package/public/app.js +574 -43
- package/src/acp-manager.mjs +422 -80
- package/src/server.mjs +87 -0
package/src/acp-manager.mjs
CHANGED
|
@@ -102,6 +102,66 @@ function buildAgentConfigSummary(agentId, config = {}) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function normalizeAgentSessionCapabilities(
|
|
106
|
+
agentCapabilities = null,
|
|
107
|
+
connection = null
|
|
108
|
+
) {
|
|
109
|
+
const sessionCapabilities = (
|
|
110
|
+
agentCapabilities?.sessionCapabilities
|
|
111
|
+
&& typeof agentCapabilities.sessionCapabilities === 'object'
|
|
112
|
+
)
|
|
113
|
+
? agentCapabilities.sessionCapabilities
|
|
114
|
+
: {};
|
|
115
|
+
return {
|
|
116
|
+
load: !!(
|
|
117
|
+
agentCapabilities?.loadSession
|
|
118
|
+
&& typeof connection?.loadSession === 'function'
|
|
119
|
+
),
|
|
120
|
+
list: !!(
|
|
121
|
+
sessionCapabilities?.list
|
|
122
|
+
&& typeof connection?.listSessions === 'function'
|
|
123
|
+
),
|
|
124
|
+
resume: !!(
|
|
125
|
+
sessionCapabilities?.resume
|
|
126
|
+
&& typeof connection?.unstable_resumeSession === 'function'
|
|
127
|
+
),
|
|
128
|
+
fork: !!(
|
|
129
|
+
sessionCapabilities?.fork
|
|
130
|
+
&& typeof connection?.unstable_forkSession === 'function'
|
|
131
|
+
)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeListedSessionInfo(session) {
|
|
136
|
+
const normalized = session && typeof session === 'object' ? session : {};
|
|
137
|
+
return {
|
|
138
|
+
sessionId: String(normalized.sessionId || '').trim(),
|
|
139
|
+
cwd: String(normalized.cwd || '').trim(),
|
|
140
|
+
title: typeof normalized.title === 'string' ? normalized.title : '',
|
|
141
|
+
updatedAt: typeof normalized.updatedAt === 'string'
|
|
142
|
+
? normalized.updatedAt
|
|
143
|
+
: ''
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseGeminiListedSessions(output) {
|
|
148
|
+
const text = String(output || '');
|
|
149
|
+
const lines = text.split(/\r?\n/);
|
|
150
|
+
const sessions = [];
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
const match = line.match(
|
|
153
|
+
/^\s*\d+\.\s+(.*?)\s+\((.*?)\)\s+\[([0-9a-f-]{8,})\]\s*$/i
|
|
154
|
+
);
|
|
155
|
+
if (!match) continue;
|
|
156
|
+
sessions.push({
|
|
157
|
+
title: match[1]?.trim() || '',
|
|
158
|
+
relativeUpdatedAt: match[2]?.trim() || '',
|
|
159
|
+
sessionId: match[3]?.trim() || ''
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return sessions.filter((session) => session.sessionId);
|
|
163
|
+
}
|
|
164
|
+
|
|
105
165
|
let ghCopilotCliInstalledCache = null;
|
|
106
166
|
let ghAuthTokenCache = null;
|
|
107
167
|
const availabilityProbeCache = new Map();
|
|
@@ -1451,6 +1511,72 @@ class AcpRuntime extends EventEmitter {
|
|
|
1451
1511
|
this.cachedModelState = null;
|
|
1452
1512
|
}
|
|
1453
1513
|
|
|
1514
|
+
#getSessionCapabilities() {
|
|
1515
|
+
const capabilities = normalizeAgentSessionCapabilities(
|
|
1516
|
+
this.agentCapabilities,
|
|
1517
|
+
this.connection
|
|
1518
|
+
);
|
|
1519
|
+
if (
|
|
1520
|
+
this.definition?.id === 'gemini'
|
|
1521
|
+
&& this.#supportsGeminiCliSessionListing()
|
|
1522
|
+
) {
|
|
1523
|
+
capabilities.list = true;
|
|
1524
|
+
}
|
|
1525
|
+
return capabilities;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
#supportsGeminiCliSessionListing() {
|
|
1529
|
+
if (this.definition?.id !== 'gemini') return false;
|
|
1530
|
+
const command = this.definition.command;
|
|
1531
|
+
return Boolean(
|
|
1532
|
+
command === 'gemini'
|
|
1533
|
+
|| (
|
|
1534
|
+
command === NPX_COMMAND
|
|
1535
|
+
&& Array.isArray(this.definition.args)
|
|
1536
|
+
&& this.definition.args[0] === '@google/gemini-cli@latest'
|
|
1537
|
+
)
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
#buildGeminiSessionListArgs() {
|
|
1542
|
+
const baseArgs = Array.isArray(this.definition.args)
|
|
1543
|
+
? this.definition.args.filter((arg) => arg !== '--acp')
|
|
1544
|
+
: [];
|
|
1545
|
+
return [
|
|
1546
|
+
...baseArgs,
|
|
1547
|
+
'--list-sessions'
|
|
1548
|
+
];
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
async #listSessionsViaGeminiCli() {
|
|
1552
|
+
const args = this.#buildGeminiSessionListArgs();
|
|
1553
|
+
const result = spawnSync(this.definition.command, args, {
|
|
1554
|
+
encoding: 'utf8',
|
|
1555
|
+
timeout: 5000,
|
|
1556
|
+
cwd: this.cwd,
|
|
1557
|
+
env: withAgentPath(this.env)
|
|
1558
|
+
});
|
|
1559
|
+
if (result.error) {
|
|
1560
|
+
throw result.error;
|
|
1561
|
+
}
|
|
1562
|
+
if (result.status !== 0) {
|
|
1563
|
+
const detail = [
|
|
1564
|
+
result.stdout,
|
|
1565
|
+
result.stderr
|
|
1566
|
+
].filter(Boolean).join('\n').trim();
|
|
1567
|
+
throw new Error(
|
|
1568
|
+
detail || 'Gemini session listing failed'
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
return parseGeminiListedSessions(result.stdout).map((session) => ({
|
|
1572
|
+
sessionId: session.sessionId,
|
|
1573
|
+
cwd: this.cwd,
|
|
1574
|
+
title: session.title,
|
|
1575
|
+
updatedAt: '',
|
|
1576
|
+
relativeUpdatedAt: session.relativeUpdatedAt
|
|
1577
|
+
}));
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1454
1580
|
#resolveAvailableModes(availableModes, existingModes = []) {
|
|
1455
1581
|
if (Array.isArray(availableModes) && availableModes.length > 0) {
|
|
1456
1582
|
this.cachedAvailableModes = availableModes;
|
|
@@ -1892,6 +2018,125 @@ class AcpRuntime extends EventEmitter {
|
|
|
1892
2018
|
}
|
|
1893
2019
|
}
|
|
1894
2020
|
|
|
2021
|
+
async listSessions(options = {}) {
|
|
2022
|
+
await this.start();
|
|
2023
|
+
this.clearIdleShutdown();
|
|
2024
|
+
|
|
2025
|
+
const sessionCapabilities = this.#getSessionCapabilities();
|
|
2026
|
+
if (
|
|
2027
|
+
sessionCapabilities.list
|
|
2028
|
+
&& typeof this.connection?.listSessions === 'function'
|
|
2029
|
+
) {
|
|
2030
|
+
try {
|
|
2031
|
+
const response = await this.connection.listSessions({
|
|
2032
|
+
cwd: options.cwd || this.cwd,
|
|
2033
|
+
cursor: options.cursor || null
|
|
2034
|
+
});
|
|
2035
|
+
return {
|
|
2036
|
+
sessions: Array.isArray(response?.sessions)
|
|
2037
|
+
? response.sessions
|
|
2038
|
+
.map(normalizeListedSessionInfo)
|
|
2039
|
+
.filter(
|
|
2040
|
+
(session) => session.sessionId && session.cwd
|
|
2041
|
+
)
|
|
2042
|
+
: [],
|
|
2043
|
+
nextCursor: typeof response?.nextCursor === 'string'
|
|
2044
|
+
? response.nextCursor
|
|
2045
|
+
: ''
|
|
2046
|
+
};
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
const message = String(error?.message || '');
|
|
2049
|
+
const canFallbackToCli = this.#supportsGeminiCliSessionListing();
|
|
2050
|
+
const unsupportedListMethod =
|
|
2051
|
+
/method not found/i.test(message)
|
|
2052
|
+
|| /session\/list/i.test(message);
|
|
2053
|
+
if (!canFallbackToCli || !unsupportedListMethod) {
|
|
2054
|
+
throw error;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
if (this.#supportsGeminiCliSessionListing()) {
|
|
2059
|
+
return {
|
|
2060
|
+
sessions: await this.#listSessionsViaGeminiCli(),
|
|
2061
|
+
nextCursor: ''
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
if (!sessionCapabilities.list) {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`${this.definition.label} does not support session history`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
throw new Error(`${this.definition.label} session history unavailable`);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
async resumeTab(meta) {
|
|
2073
|
+
await this.start();
|
|
2074
|
+
this.clearIdleShutdown();
|
|
2075
|
+
|
|
2076
|
+
const sessionCapabilities = this.#getSessionCapabilities();
|
|
2077
|
+
if (!sessionCapabilities.load) {
|
|
2078
|
+
throw new Error(
|
|
2079
|
+
`${this.definition.label} does not support session restore`
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
if (this.sessionToTabId.has(meta.acpSessionId)) {
|
|
2083
|
+
throw new Error('Session is already open');
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
const tab = this.#buildTab({
|
|
2087
|
+
id: meta.id,
|
|
2088
|
+
acpSessionId: meta.acpSessionId,
|
|
2089
|
+
terminalSessionId: meta.terminalSessionId,
|
|
2090
|
+
cwd: meta.cwd,
|
|
2091
|
+
createdAt: new Date().toISOString(),
|
|
2092
|
+
title: meta.title || ''
|
|
2093
|
+
});
|
|
2094
|
+
tab.status = 'restoring';
|
|
2095
|
+
tab.busy = true;
|
|
2096
|
+
|
|
2097
|
+
this.tabs.set(tab.id, tab);
|
|
2098
|
+
this.sessionToTabId.set(tab.acpSessionId, tab.id);
|
|
2099
|
+
|
|
2100
|
+
try {
|
|
2101
|
+
const response = await this.connection.loadSession({
|
|
2102
|
+
cwd: meta.cwd,
|
|
2103
|
+
sessionId: meta.acpSessionId,
|
|
2104
|
+
mcpServers: []
|
|
2105
|
+
});
|
|
2106
|
+
const restoredSessionId = response?.sessionId || meta.acpSessionId;
|
|
2107
|
+
if (restoredSessionId !== tab.acpSessionId) {
|
|
2108
|
+
this.sessionToTabId.delete(tab.acpSessionId);
|
|
2109
|
+
tab.acpSessionId = restoredSessionId;
|
|
2110
|
+
this.sessionToTabId.set(tab.acpSessionId, tab.id);
|
|
2111
|
+
}
|
|
2112
|
+
if (typeof response?.title === 'string') {
|
|
2113
|
+
tab.title = response.title;
|
|
2114
|
+
}
|
|
2115
|
+
tab.currentModeId = response?.modes?.currentModeId || '';
|
|
2116
|
+
tab.availableModes = this.#resolveAvailableModes(
|
|
2117
|
+
response?.modes?.availableModes,
|
|
2118
|
+
tab.availableModes
|
|
2119
|
+
);
|
|
2120
|
+
tab.availableCommands = this.#resolveAvailableCommands(
|
|
2121
|
+
response?.availableCommands,
|
|
2122
|
+
tab.availableCommands
|
|
2123
|
+
);
|
|
2124
|
+
tab.configOptions = this.#resolveConfigOptions(
|
|
2125
|
+
response?.configOptions,
|
|
2126
|
+
tab.configOptions,
|
|
2127
|
+
response?.models
|
|
2128
|
+
);
|
|
2129
|
+
tab.status = 'ready';
|
|
2130
|
+
tab.busy = false;
|
|
2131
|
+
tab.errorMessage = '';
|
|
2132
|
+
return this.serializeTab(tab);
|
|
2133
|
+
} catch (error) {
|
|
2134
|
+
this.tabs.delete(tab.id);
|
|
2135
|
+
this.sessionToTabId.delete(tab.acpSessionId);
|
|
2136
|
+
throw error;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
|
|
1895
2140
|
#markTabDirty(tab) {
|
|
1896
2141
|
if (!tab) return;
|
|
1897
2142
|
this.emit('tab_dirty', {
|
|
@@ -1919,6 +2164,7 @@ class AcpRuntime extends EventEmitter {
|
|
|
1919
2164
|
currentModeId: tab.currentModeId,
|
|
1920
2165
|
availableModes: tab.availableModes,
|
|
1921
2166
|
availableCommands: tab.availableCommands,
|
|
2167
|
+
sessionCapabilities: this.#getSessionCapabilities(),
|
|
1922
2168
|
configOptions: tab.configOptions,
|
|
1923
2169
|
messages: tab.messages,
|
|
1924
2170
|
toolCalls: Array.from(tab.toolCalls.values()),
|
|
@@ -3082,11 +3328,74 @@ export class AcpManager {
|
|
|
3082
3328
|
return this.getSerializedAgentConfig(agentId);
|
|
3083
3329
|
}
|
|
3084
3330
|
|
|
3331
|
+
#ensureRuntimeEntry(definition, cwd) {
|
|
3332
|
+
const runtimeKey = makeRuntimeKey(definition.id, cwd);
|
|
3333
|
+
const runtimeStoreKey = makeRuntimeStoreKey(
|
|
3334
|
+
definition.id,
|
|
3335
|
+
cwd,
|
|
3336
|
+
this.getAgentConfigVersion(definition.id)
|
|
3337
|
+
);
|
|
3338
|
+
let runtimeEntry = this.runtimes.get(runtimeStoreKey);
|
|
3339
|
+
let createdRuntime = false;
|
|
3340
|
+
|
|
3341
|
+
if (!runtimeEntry) {
|
|
3342
|
+
const runtime = this.runtimeFactory(definition, {
|
|
3343
|
+
cwd,
|
|
3344
|
+
idleTimeoutMs: this.idleTimeoutMs,
|
|
3345
|
+
runtimeStoreKey,
|
|
3346
|
+
terminalManager: this.terminalManager,
|
|
3347
|
+
env: mergeDefinitionEnv(
|
|
3348
|
+
definition,
|
|
3349
|
+
this.getAgentConfig(definition.id)
|
|
3350
|
+
)
|
|
3351
|
+
});
|
|
3352
|
+
runtimeEntry = {
|
|
3353
|
+
runtime,
|
|
3354
|
+
definition,
|
|
3355
|
+
runtimeKey,
|
|
3356
|
+
runtimeStoreKey
|
|
3357
|
+
};
|
|
3358
|
+
this.runtimes.set(runtimeStoreKey, runtimeEntry);
|
|
3359
|
+
createdRuntime = true;
|
|
3360
|
+
runtime.on('tab_dirty', () => {
|
|
3361
|
+
this.schedulePersistTabs();
|
|
3362
|
+
});
|
|
3363
|
+
runtime.on('runtime_exit', () => {
|
|
3364
|
+
if (this.disposing) return;
|
|
3365
|
+
for (const [tabId, tabEntry] of this.tabs.entries()) {
|
|
3366
|
+
if (tabEntry.runtime !== runtime) continue;
|
|
3367
|
+
this.tabs.delete(tabId);
|
|
3368
|
+
}
|
|
3369
|
+
this.runtimes.delete(runtimeStoreKey);
|
|
3370
|
+
void this.persistTabs();
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
return {
|
|
3375
|
+
runtimeEntry,
|
|
3376
|
+
createdRuntime,
|
|
3377
|
+
runtimeStoreKey
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
async #disposeRuntimeEntry(runtimeStoreKey, runtimeEntry) {
|
|
3382
|
+
if (!runtimeEntry) return;
|
|
3383
|
+
this.runtimes.delete(runtimeStoreKey);
|
|
3384
|
+
await runtimeEntry.runtime.dispose().catch(() => {});
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3085
3387
|
#applyRuntimeMetadataFallback(runtime, serialized) {
|
|
3086
3388
|
if (!serialized || typeof serialized !== 'object') {
|
|
3087
3389
|
return serialized;
|
|
3088
3390
|
}
|
|
3089
3391
|
|
|
3392
|
+
const sessionCapabilities = normalizeAgentSessionCapabilities(
|
|
3393
|
+
runtime?.agentCapabilities,
|
|
3394
|
+
runtime?.connection
|
|
3395
|
+
);
|
|
3396
|
+
const hasSessionCapabilities = serialized.sessionCapabilities
|
|
3397
|
+
&& typeof serialized.sessionCapabilities === 'object';
|
|
3398
|
+
|
|
3090
3399
|
let availableModes = Array.isArray(serialized.availableModes)
|
|
3091
3400
|
? serialized.availableModes
|
|
3092
3401
|
: [];
|
|
@@ -3105,7 +3414,13 @@ export class AcpManager {
|
|
|
3105
3414
|
)
|
|
3106
3415
|
|| !(runtime?.tabs instanceof Map)
|
|
3107
3416
|
) {
|
|
3108
|
-
|
|
3417
|
+
if (hasSessionCapabilities) {
|
|
3418
|
+
return serialized;
|
|
3419
|
+
}
|
|
3420
|
+
return {
|
|
3421
|
+
...serialized,
|
|
3422
|
+
sessionCapabilities
|
|
3423
|
+
};
|
|
3109
3424
|
}
|
|
3110
3425
|
|
|
3111
3426
|
for (const runtimeTab of runtime.tabs.values()) {
|
|
@@ -3144,6 +3459,7 @@ export class AcpManager {
|
|
|
3144
3459
|
availableModes === serialized.availableModes
|
|
3145
3460
|
&& availableCommands === serialized.availableCommands
|
|
3146
3461
|
&& configOptions === serialized.configOptions
|
|
3462
|
+
&& hasSessionCapabilities
|
|
3147
3463
|
) {
|
|
3148
3464
|
return serialized;
|
|
3149
3465
|
}
|
|
@@ -3152,7 +3468,8 @@ export class AcpManager {
|
|
|
3152
3468
|
...serialized,
|
|
3153
3469
|
availableModes,
|
|
3154
3470
|
availableCommands,
|
|
3155
|
-
configOptions
|
|
3471
|
+
configOptions,
|
|
3472
|
+
sessionCapabilities
|
|
3156
3473
|
};
|
|
3157
3474
|
}
|
|
3158
3475
|
|
|
@@ -3274,7 +3591,8 @@ export class AcpManager {
|
|
|
3274
3591
|
status: serialized.status,
|
|
3275
3592
|
busy: serialized.busy,
|
|
3276
3593
|
errorMessage: serialized.errorMessage,
|
|
3277
|
-
currentModeId: serialized.currentModeId
|
|
3594
|
+
currentModeId: serialized.currentModeId,
|
|
3595
|
+
sessionCapabilities: serialized.sessionCapabilities
|
|
3278
3596
|
};
|
|
3279
3597
|
})
|
|
3280
3598
|
};
|
|
@@ -3294,54 +3612,115 @@ export class AcpManager {
|
|
|
3294
3612
|
}
|
|
3295
3613
|
|
|
3296
3614
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
3297
|
-
const
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
let createdRuntime = false;
|
|
3305
|
-
if (!runtimeEntry) {
|
|
3306
|
-
const runtime = this.runtimeFactory(definition, {
|
|
3615
|
+
const { runtimeEntry, createdRuntime, runtimeStoreKey } =
|
|
3616
|
+
this.#ensureRuntimeEntry(definition, cwd);
|
|
3617
|
+
|
|
3618
|
+
const tabId = crypto.randomUUID();
|
|
3619
|
+
try {
|
|
3620
|
+
const rawSerialized = await runtimeEntry.runtime.createTab({
|
|
3621
|
+
id: tabId,
|
|
3307
3622
|
cwd,
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
terminalManager: this.terminalManager,
|
|
3311
|
-
env: mergeDefinitionEnv(
|
|
3312
|
-
definition,
|
|
3313
|
-
this.getAgentConfig(definition.id)
|
|
3314
|
-
)
|
|
3315
|
-
});
|
|
3316
|
-
runtimeEntry = {
|
|
3317
|
-
runtime,
|
|
3318
|
-
definition,
|
|
3319
|
-
runtimeKey,
|
|
3320
|
-
runtimeStoreKey
|
|
3321
|
-
};
|
|
3322
|
-
this.runtimes.set(runtimeStoreKey, runtimeEntry);
|
|
3323
|
-
createdRuntime = true;
|
|
3324
|
-
runtime.on('tab_dirty', () => {
|
|
3325
|
-
this.schedulePersistTabs();
|
|
3623
|
+
terminalSessionId: options.terminalSessionId || '',
|
|
3624
|
+
modeId: options.modeId || ''
|
|
3326
3625
|
});
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3626
|
+
const serialized = this.#applyRuntimeMetadataFallback(
|
|
3627
|
+
runtimeEntry.runtime,
|
|
3628
|
+
rawSerialized
|
|
3629
|
+
);
|
|
3630
|
+
const tabEntry = {
|
|
3631
|
+
runtime: runtimeEntry.runtime,
|
|
3632
|
+
serialize: () => {
|
|
3633
|
+
const tab = runtimeEntry.runtime.tabs.get(tabId);
|
|
3634
|
+
const nextSerialized = tab
|
|
3635
|
+
? runtimeEntry.runtime.serializeTab(tab)
|
|
3636
|
+
: serialized;
|
|
3637
|
+
return this.#applyRuntimeMetadataFallback(
|
|
3638
|
+
runtimeEntry.runtime,
|
|
3639
|
+
nextSerialized
|
|
3640
|
+
);
|
|
3332
3641
|
}
|
|
3333
|
-
|
|
3334
|
-
|
|
3642
|
+
};
|
|
3643
|
+
this.tabs.set(tabId, tabEntry);
|
|
3644
|
+
this.#clearDefinitionAvailabilityOverride(definition.id);
|
|
3645
|
+
await this.persistTabs();
|
|
3646
|
+
return tabEntry.serialize();
|
|
3647
|
+
} catch (error) {
|
|
3648
|
+
const shouldDisposeRuntime = createdRuntime
|
|
3649
|
+
|| runtimeEntry.runtime.tabs.size === 0;
|
|
3650
|
+
if (shouldDisposeRuntime) {
|
|
3651
|
+
await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
|
|
3652
|
+
}
|
|
3653
|
+
this.#recordDefinitionStartupFailure(definition, error);
|
|
3654
|
+
throw new Error(formatAgentStartupError(definition, error));
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
async listSessions(options) {
|
|
3659
|
+
await this.ensureConfigsLoaded();
|
|
3660
|
+
const definition = this.definitions.find(
|
|
3661
|
+
(entry) => entry.id === options.agentId
|
|
3662
|
+
);
|
|
3663
|
+
if (!definition) {
|
|
3664
|
+
throw new Error('Unknown agent');
|
|
3665
|
+
}
|
|
3666
|
+
const availability = this.getDefinitionAvailability(definition);
|
|
3667
|
+
if (!availability.available) {
|
|
3668
|
+
throw new Error(availability.reason || 'Agent unavailable');
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
3672
|
+
const { runtimeEntry, createdRuntime, runtimeStoreKey } =
|
|
3673
|
+
this.#ensureRuntimeEntry(definition, cwd);
|
|
3674
|
+
|
|
3675
|
+
try {
|
|
3676
|
+
const result = await runtimeEntry.runtime.listSessions({
|
|
3677
|
+
cwd,
|
|
3678
|
+
cursor: typeof options.cursor === 'string' ? options.cursor : ''
|
|
3335
3679
|
});
|
|
3680
|
+
this.#clearDefinitionAvailabilityOverride(definition.id);
|
|
3681
|
+
if (runtimeEntry.runtime.tabs.size === 0) {
|
|
3682
|
+
runtimeEntry.runtime.scheduleIdleShutdown(async () => {
|
|
3683
|
+
if (runtimeEntry.runtime.tabs.size > 0) return;
|
|
3684
|
+
await this.#disposeRuntimeEntry(
|
|
3685
|
+
runtimeStoreKey,
|
|
3686
|
+
runtimeEntry
|
|
3687
|
+
);
|
|
3688
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
return result;
|
|
3691
|
+
} catch (error) {
|
|
3692
|
+
if (createdRuntime && runtimeEntry.runtime.tabs.size === 0) {
|
|
3693
|
+
await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
|
|
3694
|
+
}
|
|
3695
|
+
throw error;
|
|
3336
3696
|
}
|
|
3697
|
+
}
|
|
3337
3698
|
|
|
3699
|
+
async resumeTab(options) {
|
|
3700
|
+
await this.ensureConfigsLoaded();
|
|
3701
|
+
const definition = this.definitions.find(
|
|
3702
|
+
(entry) => entry.id === options.agentId
|
|
3703
|
+
);
|
|
3704
|
+
if (!definition) {
|
|
3705
|
+
throw new Error('Unknown agent');
|
|
3706
|
+
}
|
|
3707
|
+
const availability = this.getDefinitionAvailability(definition);
|
|
3708
|
+
if (!availability.available) {
|
|
3709
|
+
throw new Error(availability.reason || 'Agent unavailable');
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
3713
|
+
const { runtimeEntry, createdRuntime, runtimeStoreKey } =
|
|
3714
|
+
this.#ensureRuntimeEntry(definition, cwd);
|
|
3338
3715
|
const tabId = crypto.randomUUID();
|
|
3716
|
+
|
|
3339
3717
|
try {
|
|
3340
|
-
const rawSerialized = await runtimeEntry.runtime.
|
|
3718
|
+
const rawSerialized = await runtimeEntry.runtime.resumeTab({
|
|
3341
3719
|
id: tabId,
|
|
3720
|
+
acpSessionId: options.sessionId,
|
|
3342
3721
|
cwd,
|
|
3343
3722
|
terminalSessionId: options.terminalSessionId || '',
|
|
3344
|
-
|
|
3723
|
+
title: options.title || ''
|
|
3345
3724
|
});
|
|
3346
3725
|
const serialized = this.#applyRuntimeMetadataFallback(
|
|
3347
3726
|
runtimeEntry.runtime,
|
|
@@ -3368,11 +3747,9 @@ export class AcpManager {
|
|
|
3368
3747
|
const shouldDisposeRuntime = createdRuntime
|
|
3369
3748
|
|| runtimeEntry.runtime.tabs.size === 0;
|
|
3370
3749
|
if (shouldDisposeRuntime) {
|
|
3371
|
-
this
|
|
3372
|
-
await runtimeEntry.runtime.dispose().catch(() => {});
|
|
3750
|
+
await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
|
|
3373
3751
|
}
|
|
3374
|
-
|
|
3375
|
-
throw new Error(formatAgentStartupError(definition, error));
|
|
3752
|
+
throw error;
|
|
3376
3753
|
}
|
|
3377
3754
|
}
|
|
3378
3755
|
|
|
@@ -3398,7 +3775,6 @@ export class AcpManager {
|
|
|
3398
3775
|
continue;
|
|
3399
3776
|
}
|
|
3400
3777
|
|
|
3401
|
-
const agentConfig = this.getAgentConfig(definition.id);
|
|
3402
3778
|
const availability = this.getDefinitionAvailability(definition);
|
|
3403
3779
|
if (!availability.available) {
|
|
3404
3780
|
changed = true;
|
|
@@ -3406,41 +3782,7 @@ export class AcpManager {
|
|
|
3406
3782
|
}
|
|
3407
3783
|
|
|
3408
3784
|
const cwd = path.resolve(meta.cwd || process.cwd());
|
|
3409
|
-
const
|
|
3410
|
-
const runtimeStoreKey = makeRuntimeStoreKey(
|
|
3411
|
-
definition.id,
|
|
3412
|
-
cwd,
|
|
3413
|
-
this.getAgentConfigVersion(definition.id)
|
|
3414
|
-
);
|
|
3415
|
-
let runtimeEntry = this.runtimes.get(runtimeStoreKey);
|
|
3416
|
-
if (!runtimeEntry) {
|
|
3417
|
-
const runtime = this.runtimeFactory(definition, {
|
|
3418
|
-
cwd,
|
|
3419
|
-
idleTimeoutMs: this.idleTimeoutMs,
|
|
3420
|
-
runtimeStoreKey,
|
|
3421
|
-
terminalManager: this.terminalManager,
|
|
3422
|
-
env: mergeDefinitionEnv(definition, agentConfig)
|
|
3423
|
-
});
|
|
3424
|
-
runtimeEntry = {
|
|
3425
|
-
runtime,
|
|
3426
|
-
definition,
|
|
3427
|
-
runtimeKey,
|
|
3428
|
-
runtimeStoreKey
|
|
3429
|
-
};
|
|
3430
|
-
this.runtimes.set(runtimeStoreKey, runtimeEntry);
|
|
3431
|
-
runtime.on('tab_dirty', () => {
|
|
3432
|
-
this.schedulePersistTabs();
|
|
3433
|
-
});
|
|
3434
|
-
runtime.on('runtime_exit', () => {
|
|
3435
|
-
if (this.disposing) return;
|
|
3436
|
-
for (const [tabId, tabEntry] of this.tabs.entries()) {
|
|
3437
|
-
if (tabEntry.runtime !== runtime) continue;
|
|
3438
|
-
this.tabs.delete(tabId);
|
|
3439
|
-
}
|
|
3440
|
-
this.runtimes.delete(runtimeStoreKey);
|
|
3441
|
-
void this.persistTabs();
|
|
3442
|
-
});
|
|
3443
|
-
}
|
|
3785
|
+
const { runtimeEntry } = this.#ensureRuntimeEntry(definition, cwd);
|
|
3444
3786
|
|
|
3445
3787
|
try {
|
|
3446
3788
|
const rawSerialized = await runtimeEntry.runtime.restoreTab({
|
package/src/server.mjs
CHANGED
|
@@ -343,6 +343,52 @@ router.get('/api/agents', async (ctx) => {
|
|
|
343
343
|
ctx.body = await acpManager.listState();
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
+
router.get('/api/agents/sessions', async (ctx) => {
|
|
347
|
+
const { agentId = '', cwd = '', cursor = '' } = ctx.query || {};
|
|
348
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
349
|
+
ctx.status = 400;
|
|
350
|
+
ctx.body = { error: 'agentId is required' };
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (!cwd || typeof cwd !== 'string') {
|
|
354
|
+
ctx.status = 400;
|
|
355
|
+
ctx.body = { error: 'cwd is required' };
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
let nextCursor = typeof cursor === 'string' ? cursor : '';
|
|
361
|
+
const sessions = [];
|
|
362
|
+
const paginate = !!nextCursor;
|
|
363
|
+
for (let page = 0; page < 5; page += 1) {
|
|
364
|
+
const result = await acpManager.listSessions({
|
|
365
|
+
agentId,
|
|
366
|
+
cwd,
|
|
367
|
+
cursor: nextCursor
|
|
368
|
+
});
|
|
369
|
+
sessions.push(...(Array.isArray(result?.sessions)
|
|
370
|
+
? result.sessions
|
|
371
|
+
: []));
|
|
372
|
+
nextCursor = typeof result?.nextCursor === 'string'
|
|
373
|
+
? result.nextCursor
|
|
374
|
+
: '';
|
|
375
|
+
if (paginate || !nextCursor || sessions.length >= 50) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
ctx.body = {
|
|
380
|
+
sessions: sessions.slice(0, 50),
|
|
381
|
+
nextCursor
|
|
382
|
+
};
|
|
383
|
+
} catch (error) {
|
|
384
|
+
const message = error?.message || 'Failed to list agent sessions';
|
|
385
|
+
ctx.status = /does not support session history/i.test(message)
|
|
386
|
+
? 501
|
|
387
|
+
: 500;
|
|
388
|
+
ctx.body = { error: message };
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
346
392
|
router.get('/api/agents/config', async (ctx) => {
|
|
347
393
|
ctx.body = {
|
|
348
394
|
configs: await acpManager.listAgentConfigs()
|
|
@@ -414,6 +460,47 @@ router.post('/api/agents/tabs', async (ctx) => {
|
|
|
414
460
|
}
|
|
415
461
|
});
|
|
416
462
|
|
|
463
|
+
router.post('/api/agents/tabs/resume', async (ctx) => {
|
|
464
|
+
const { agentId, cwd, terminalSessionId, sessionId, title } =
|
|
465
|
+
ctx.request.body || {};
|
|
466
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
467
|
+
ctx.status = 400;
|
|
468
|
+
ctx.body = { error: 'agentId is required' };
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (!cwd || typeof cwd !== 'string') {
|
|
472
|
+
ctx.status = 400;
|
|
473
|
+
ctx.body = { error: 'cwd is required' };
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
477
|
+
ctx.status = 400;
|
|
478
|
+
ctx.body = { error: 'sessionId is required' };
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
ctx.status = 201;
|
|
484
|
+
ctx.body = await acpManager.resumeTab({
|
|
485
|
+
agentId,
|
|
486
|
+
cwd,
|
|
487
|
+
sessionId,
|
|
488
|
+
title: typeof title === 'string' ? title : '',
|
|
489
|
+
terminalSessionId: typeof terminalSessionId === 'string'
|
|
490
|
+
? terminalSessionId
|
|
491
|
+
: ''
|
|
492
|
+
});
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const message = error?.message || 'Failed to resume agent tab';
|
|
495
|
+
ctx.status = /already open/i.test(message)
|
|
496
|
+
? 409
|
|
497
|
+
: /does not support session restore/i.test(message)
|
|
498
|
+
? 501
|
|
499
|
+
: 500;
|
|
500
|
+
ctx.body = { error: message };
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
417
504
|
router.post('/api/agents/tabs/:tabId/prompt', async (ctx) => {
|
|
418
505
|
const { tabId } = ctx.params;
|
|
419
506
|
let text = '';
|