@ganglion/xacpx 0.20.3-beta.0 → 0.20.3
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/bridge/bridge-main.js +22 -0
- package/dist/cli.js +162 -97
- package/dist/config/agent-launch.d.ts +9 -3
- package/dist/i18n/types.d.ts +1 -0
- package/dist/plugin-api.js +2 -0
- package/dist/sessions/session-service.d.ts +24 -0
- package/dist/transport/types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -4059,6 +4059,7 @@ var init_session = __esm(() => {
|
|
|
4059
4059
|
`Switch to it with /use ${alias}, or remove it first with /session rm ${alias}.`
|
|
4060
4060
|
].join(`
|
|
4061
4061
|
`),
|
|
4062
|
+
sessionAliasCollided: (desiredAlias, finalAlias) => `The requested name "${desiredAlias}" is already in use (by an archived session). Creating the new session as "${finalAlias}" instead.`,
|
|
4062
4063
|
sessionAttachNotFound: (alias, agent, workspace) => [
|
|
4063
4064
|
"No existing session found to attach.",
|
|
4064
4065
|
`Check the session name and retry: /session attach ${alias} --agent ${agent} --ws ${workspace} --name <session-name>`
|
|
@@ -5184,6 +5185,7 @@ var init_session2 = __esm(() => {
|
|
|
5184
5185
|
`发送 /use ${alias} 切换到它,或先执行 /session rm ${alias} 删除后再创建。`
|
|
5185
5186
|
].join(`
|
|
5186
5187
|
`),
|
|
5188
|
+
sessionAliasCollided: (desiredAlias, finalAlias) => `你指定的会话名「${desiredAlias}」已被其他会话(含已睡眠/归档)占用,已自动改为「${finalAlias}」创建。`,
|
|
5187
5189
|
sessionAttachNotFound: (alias, agent2, workspace2) => [
|
|
5188
5190
|
"没有找到可绑定的已有会话。",
|
|
5189
5191
|
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent2} --ws ${workspace2} --name <会话名>`
|
|
@@ -7225,8 +7227,28 @@ function deriveAgentAlias(driver, argv) {
|
|
|
7225
7227
|
const hash = createHash5("sha256").update(identity).digest("hex").slice(0, 12);
|
|
7226
7228
|
return `xacpx-managed-${driver}-${hash}`;
|
|
7227
7229
|
}
|
|
7230
|
+
function isDerivedAgentArgv(driver, argv, runtimeRoot, platform) {
|
|
7231
|
+
if (!argv || argv.length === 0) {
|
|
7232
|
+
return false;
|
|
7233
|
+
}
|
|
7234
|
+
if (isManagedAdapterId(driver)) {
|
|
7235
|
+
const spec = MANAGED_ADAPTERS[driver];
|
|
7236
|
+
return argv[0] === "npx" && argv[1] === "-y" && argv.some((entry) => entry.startsWith(`${spec.packageName}@`)) || classifyRecordedPreinstalledAdapterCommand(renderAgentArgvIdentity(argv), {
|
|
7237
|
+
runtimeRoot,
|
|
7238
|
+
platform
|
|
7239
|
+
}) === driver;
|
|
7240
|
+
}
|
|
7241
|
+
if (driver === "hermes") {
|
|
7242
|
+
return argv[1]?.includes("hermes-acp-shim.") === true;
|
|
7243
|
+
}
|
|
7244
|
+
if (driver === "opencode" || driver === "kilocode") {
|
|
7245
|
+
return argv.length === 2 && argv[0] === driver && argv[1] === "acp";
|
|
7246
|
+
}
|
|
7247
|
+
return false;
|
|
7248
|
+
}
|
|
7228
7249
|
var IDENTITY_SAFE_ARG_RE;
|
|
7229
7250
|
var init_agent_launch = __esm(() => {
|
|
7251
|
+
init_adapter_catalog();
|
|
7230
7252
|
IDENTITY_SAFE_ARG_RE = /^[A-Za-z0-9_@%+=:,./^~-]+$/u;
|
|
7231
7253
|
});
|
|
7232
7254
|
|
package/dist/cli.js
CHANGED
|
@@ -97,6 +97,7 @@ var init_session = __esm(() => {
|
|
|
97
97
|
`Switch to it with /use ${alias}, or remove it first with /session rm ${alias}.`
|
|
98
98
|
].join(`
|
|
99
99
|
`),
|
|
100
|
+
sessionAliasCollided: (desiredAlias, finalAlias) => `The requested name "${desiredAlias}" is already in use (by an archived session). Creating the new session as "${finalAlias}" instead.`,
|
|
100
101
|
sessionAttachNotFound: (alias, agent, workspace) => [
|
|
101
102
|
"No existing session found to attach.",
|
|
102
103
|
`Check the session name and retry: /session attach ${alias} --agent ${agent} --ws ${workspace} --name <session-name>`
|
|
@@ -1222,6 +1223,7 @@ var init_session2 = __esm(() => {
|
|
|
1222
1223
|
`发送 /use ${alias} 切换到它,或先执行 /session rm ${alias} 删除后再创建。`
|
|
1223
1224
|
].join(`
|
|
1224
1225
|
`),
|
|
1226
|
+
sessionAliasCollided: (desiredAlias, finalAlias) => `你指定的会话名「${desiredAlias}」已被其他会话(含已睡眠/归档)占用,已自动改为「${finalAlias}」创建。`,
|
|
1225
1227
|
sessionAttachNotFound: (alias, agent2, workspace2) => [
|
|
1226
1228
|
"没有找到可绑定的已有会话。",
|
|
1227
1229
|
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent2} --ws ${workspace2} --name <会话名>`
|
|
@@ -4620,8 +4622,28 @@ function deriveAgentAlias(driver, argv) {
|
|
|
4620
4622
|
const hash = createHash3("sha256").update(identity).digest("hex").slice(0, 12);
|
|
4621
4623
|
return `xacpx-managed-${driver}-${hash}`;
|
|
4622
4624
|
}
|
|
4625
|
+
function isDerivedAgentArgv(driver, argv, runtimeRoot, platform) {
|
|
4626
|
+
if (!argv || argv.length === 0) {
|
|
4627
|
+
return false;
|
|
4628
|
+
}
|
|
4629
|
+
if (isManagedAdapterId(driver)) {
|
|
4630
|
+
const spec = MANAGED_ADAPTERS[driver];
|
|
4631
|
+
return argv[0] === "npx" && argv[1] === "-y" && argv.some((entry) => entry.startsWith(`${spec.packageName}@`)) || classifyRecordedPreinstalledAdapterCommand(renderAgentArgvIdentity(argv), {
|
|
4632
|
+
runtimeRoot,
|
|
4633
|
+
platform
|
|
4634
|
+
}) === driver;
|
|
4635
|
+
}
|
|
4636
|
+
if (driver === "hermes") {
|
|
4637
|
+
return argv[1]?.includes("hermes-acp-shim.") === true;
|
|
4638
|
+
}
|
|
4639
|
+
if (driver === "opencode" || driver === "kilocode") {
|
|
4640
|
+
return argv.length === 2 && argv[0] === driver && argv[1] === "acp";
|
|
4641
|
+
}
|
|
4642
|
+
return false;
|
|
4643
|
+
}
|
|
4623
4644
|
var IDENTITY_SAFE_ARG_RE;
|
|
4624
4645
|
var init_agent_launch = __esm(() => {
|
|
4646
|
+
init_adapter_catalog();
|
|
4625
4647
|
IDENTITY_SAFE_ARG_RE = /^[A-Za-z0-9_@%+=:,./^~-]+$/u;
|
|
4626
4648
|
});
|
|
4627
4649
|
|
|
@@ -6302,6 +6324,7 @@ class ConfigStore {
|
|
|
6302
6324
|
agents[name] = {
|
|
6303
6325
|
driver: agent3.driver,
|
|
6304
6326
|
...agent3.command ? { command: agent3.command } : {},
|
|
6327
|
+
...agent3.argv ? { argv: [...agent3.argv] } : {},
|
|
6305
6328
|
...agent3.model ? { model: agent3.model } : {},
|
|
6306
6329
|
...agent3.settingsPolicy ? { settingsPolicy: agent3.settingsPolicy } : {}
|
|
6307
6330
|
};
|
|
@@ -41205,20 +41228,19 @@ async function handleSessions(context, chatKey) {
|
|
|
41205
41228
|
async function handleSessionNew(context, chatKey, alias, agent3, workspace3, model) {
|
|
41206
41229
|
const channelId = getChannelIdFromChatKey(chatKey);
|
|
41207
41230
|
const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
|
|
41208
|
-
const
|
|
41209
|
-
if (
|
|
41210
|
-
return { text: t().session.sessionAlreadyExists(alias, existing.agent, existing.workspace) };
|
|
41211
|
-
}
|
|
41212
|
-
const releaseAliasReservation = context.sessions.tryReserveNewSessionAlias(internalAlias);
|
|
41213
|
-
if (!releaseAliasReservation) {
|
|
41231
|
+
const reserved = context.sessions.tryReserveFreeSessionAlias(internalAlias);
|
|
41232
|
+
if (!reserved) {
|
|
41214
41233
|
const claimed = context.sessions.getResolvedSessionByInternalAlias(internalAlias);
|
|
41215
41234
|
return {
|
|
41216
41235
|
text: claimed ? t().session.sessionAlreadyExists(alias, claimed.agent, claimed.workspace) : t().session.sessionAlreadyExists(alias, agent3, workspace3)
|
|
41217
41236
|
};
|
|
41218
41237
|
}
|
|
41238
|
+
const { alias: finalInternalAlias, release: releaseAliasReservation } = reserved;
|
|
41239
|
+
const finalDisplayAlias = toDisplaySessionAlias(finalInternalAlias);
|
|
41240
|
+
const aliasWasAdjusted = finalInternalAlias !== internalAlias;
|
|
41219
41241
|
try {
|
|
41220
|
-
const stableTransportSession = `${workspace3}:${
|
|
41221
|
-
const session3 = context.lifecycle.resolveSession(
|
|
41242
|
+
const stableTransportSession = `${workspace3}:${finalInternalAlias}`;
|
|
41243
|
+
const session3 = context.lifecycle.resolveSession(finalInternalAlias, agent3, workspace3, context.sessions.buildFreshTransportSession(stableTransportSession));
|
|
41222
41244
|
const normalizedModel = model?.trim();
|
|
41223
41245
|
if (normalizedModel) {
|
|
41224
41246
|
session3.model = normalizedModel;
|
|
@@ -41234,18 +41256,27 @@ async function handleSessionNew(context, chatKey, alias, agent3, workspace3, mod
|
|
|
41234
41256
|
} catch (error2) {
|
|
41235
41257
|
return context.recovery.renderSessionCreationError(session3, error2);
|
|
41236
41258
|
}
|
|
41237
|
-
await context.sessions.attachSession(
|
|
41259
|
+
await context.sessions.attachSession(finalInternalAlias, agent3, workspace3, session3.transportSession, session3.agentCommand, session3.acpxAgent, session3.agentArgv);
|
|
41238
41260
|
if (normalizedModel) {
|
|
41239
|
-
await context.sessions.setSessionModel(
|
|
41261
|
+
await context.sessions.setSessionModel(finalInternalAlias, normalizedModel);
|
|
41240
41262
|
}
|
|
41241
|
-
await context.sessions.useSession(chatKey,
|
|
41242
|
-
await refreshSessionTransportAgentCommandBestEffort(context,
|
|
41263
|
+
await context.sessions.useSession(chatKey, finalInternalAlias);
|
|
41264
|
+
await refreshSessionTransportAgentCommandBestEffort(context, finalInternalAlias, "session.agent_command_refresh_failed");
|
|
41243
41265
|
await context.logger.info("session.created", "created and selected logical session", {
|
|
41244
|
-
alias:
|
|
41266
|
+
alias: finalInternalAlias,
|
|
41245
41267
|
agent: agent3,
|
|
41246
41268
|
workspace: workspace3
|
|
41247
41269
|
});
|
|
41248
|
-
|
|
41270
|
+
if (aliasWasAdjusted) {
|
|
41271
|
+
return {
|
|
41272
|
+
text: [
|
|
41273
|
+
t().session.sessionAliasCollided(alias, finalDisplayAlias),
|
|
41274
|
+
t().session.sessionCreated(finalDisplayAlias)
|
|
41275
|
+
].join(`
|
|
41276
|
+
`)
|
|
41277
|
+
};
|
|
41278
|
+
}
|
|
41279
|
+
return { text: t().session.sessionCreated(finalDisplayAlias) };
|
|
41249
41280
|
} finally {
|
|
41250
41281
|
await releaseTransportReservation();
|
|
41251
41282
|
}
|
|
@@ -42845,65 +42876,67 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent3, targe
|
|
|
42845
42876
|
const baseAlias = `${workspace3.name}:${agent3}`;
|
|
42846
42877
|
const channelId = getChannelIdFromChatKey(chatKey);
|
|
42847
42878
|
const scopedBase = scopeDisplayAliasToInternal(channelId, baseAlias);
|
|
42848
|
-
const
|
|
42849
|
-
const
|
|
42850
|
-
if (!createNew && await hasLogicalSession(context,
|
|
42851
|
-
await context.sessions.useSession(chatKey,
|
|
42879
|
+
const desiredAlias = scopedBase;
|
|
42880
|
+
const desiredDisplay = toDisplaySessionAlias(desiredAlias);
|
|
42881
|
+
if (!createNew && await hasLogicalSession(context, desiredAlias, chatKey)) {
|
|
42882
|
+
await context.sessions.useSession(chatKey, desiredAlias);
|
|
42852
42883
|
await context.logger.info("session.shortcut.reused", "reused existing logical session", {
|
|
42853
|
-
alias,
|
|
42884
|
+
alias: desiredAlias,
|
|
42854
42885
|
workspace: workspace3.name,
|
|
42855
42886
|
agent: agent3
|
|
42856
42887
|
});
|
|
42857
42888
|
return {
|
|
42858
42889
|
text: [
|
|
42859
|
-
t().shortcut.reuseHeader(
|
|
42890
|
+
t().shortcut.reuseHeader(desiredDisplay),
|
|
42860
42891
|
t().shortcut.reuseWorkspace(workspace3.name),
|
|
42861
|
-
t().shortcut.reuseSession(
|
|
42892
|
+
t().shortcut.reuseSession(desiredDisplay)
|
|
42862
42893
|
].join(`
|
|
42863
42894
|
`)
|
|
42864
42895
|
};
|
|
42865
42896
|
}
|
|
42866
|
-
const
|
|
42867
|
-
if (!
|
|
42868
|
-
return renderShortcutSessionCreationError(workspace3,
|
|
42897
|
+
const reserved = context.sessions.tryReserveFreeSessionAlias(desiredAlias);
|
|
42898
|
+
if (!reserved) {
|
|
42899
|
+
return renderShortcutSessionCreationError(workspace3, desiredDisplay);
|
|
42869
42900
|
}
|
|
42901
|
+
const { alias: finalAlias, release: releaseAliasReservation } = reserved;
|
|
42902
|
+
const finalDisplay = toDisplaySessionAlias(finalAlias);
|
|
42870
42903
|
try {
|
|
42871
|
-
const stableTransportSession = channelId === "weixin" ?
|
|
42872
|
-
const session3 = ops.resolveSession(
|
|
42904
|
+
const stableTransportSession = channelId === "weixin" ? finalAlias : context.sessions.buildDefaultTransportSessionForChat(chatKey, finalDisplay);
|
|
42905
|
+
const session3 = ops.resolveSession(finalAlias, agent3, workspace3.name, context.sessions.buildFreshTransportSession(stableTransportSession));
|
|
42873
42906
|
const releaseTransportReservation = await ops.reserveTransportSession(stableTransportSession);
|
|
42874
42907
|
try {
|
|
42875
42908
|
try {
|
|
42876
42909
|
await ops.ensureTransportSession(session3);
|
|
42877
42910
|
const exists = await ops.checkTransportSession(session3);
|
|
42878
42911
|
if (!exists) {
|
|
42879
|
-
return renderShortcutSessionCreationError(workspace3,
|
|
42912
|
+
return renderShortcutSessionCreationError(workspace3, finalDisplay);
|
|
42880
42913
|
}
|
|
42881
42914
|
} catch (err) {
|
|
42882
42915
|
if (err instanceof AutoInstallFailedError)
|
|
42883
42916
|
throw err;
|
|
42884
|
-
return renderShortcutSessionCreationError(workspace3,
|
|
42917
|
+
return renderShortcutSessionCreationError(workspace3, finalDisplay);
|
|
42885
42918
|
}
|
|
42886
|
-
await context.sessions.attachSession(
|
|
42887
|
-
await context.sessions.useSession(chatKey,
|
|
42919
|
+
await context.sessions.attachSession(finalAlias, agent3, workspace3.name, session3.transportSession, session3.agentCommand, session3.acpxAgent, session3.agentArgv);
|
|
42920
|
+
await context.sessions.useSession(chatKey, finalAlias);
|
|
42888
42921
|
try {
|
|
42889
|
-
await ops.refreshSessionTransportAgentCommand(
|
|
42922
|
+
await ops.refreshSessionTransportAgentCommand(finalAlias);
|
|
42890
42923
|
} catch (error2) {
|
|
42891
42924
|
await context.logger.error("session.shortcut.agent_command_refresh_failed", "failed to refresh session agent command", {
|
|
42892
|
-
alias,
|
|
42925
|
+
alias: finalAlias,
|
|
42893
42926
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
42894
42927
|
});
|
|
42895
42928
|
}
|
|
42896
42929
|
await context.logger.info("session.shortcut.created", "created new logical session from shortcut", {
|
|
42897
|
-
alias,
|
|
42930
|
+
alias: finalAlias,
|
|
42898
42931
|
workspace: workspace3.name,
|
|
42899
42932
|
agent: agent3,
|
|
42900
42933
|
workspaceReused: workspace3.reused
|
|
42901
42934
|
});
|
|
42902
42935
|
return {
|
|
42903
42936
|
text: [
|
|
42904
|
-
t().shortcut.createdHeader(
|
|
42937
|
+
t().shortcut.createdHeader(finalDisplay),
|
|
42905
42938
|
workspace3.reused ? t().shortcut.createdReusedWorkspace(workspace3.name) : t().shortcut.createdNewWorkspace(workspace3.name, workspace3.cwd),
|
|
42906
|
-
t().shortcut.createdNewSession(
|
|
42939
|
+
t().shortcut.createdNewSession(finalDisplay)
|
|
42907
42940
|
].join(`
|
|
42908
42941
|
`)
|
|
42909
42942
|
};
|
|
@@ -42950,16 +42983,6 @@ async function resolveShortcutWorkspace(context, target) {
|
|
|
42950
42983
|
reused: false
|
|
42951
42984
|
};
|
|
42952
42985
|
}
|
|
42953
|
-
async function allocateUniqueSessionAlias(context, baseAlias, chatKey) {
|
|
42954
|
-
if (!await hasLogicalSession(context, baseAlias, chatKey)) {
|
|
42955
|
-
return baseAlias;
|
|
42956
|
-
}
|
|
42957
|
-
let suffix = 2;
|
|
42958
|
-
while (await hasLogicalSession(context, `${baseAlias}-${suffix}`, chatKey)) {
|
|
42959
|
-
suffix += 1;
|
|
42960
|
-
}
|
|
42961
|
-
return `${baseAlias}-${suffix}`;
|
|
42962
|
-
}
|
|
42963
42986
|
async function hasLogicalSession(context, alias, chatKey) {
|
|
42964
42987
|
const sessions = await context.sessions.listSessions(chatKey);
|
|
42965
42988
|
return sessions.some((session3) => session3.internalAlias === alias);
|
|
@@ -43082,17 +43105,43 @@ async function attachNativeSession(context, chatKey, target, session3, alias) {
|
|
|
43082
43105
|
const existing = await context.sessions.findAttachedNativeSession(chatKey, nativeTarget.agent, session3.sessionId);
|
|
43083
43106
|
if (existing) {
|
|
43084
43107
|
await context.sessions.useSession(chatKey, existing.alias);
|
|
43085
|
-
const
|
|
43108
|
+
const displayAlias = toDisplaySessionAlias(existing.alias);
|
|
43086
43109
|
return {
|
|
43087
|
-
text: t().nativeSession.alreadySwitched(nativeTarget.agentDisplayName,
|
|
43110
|
+
text: t().nativeSession.alreadySwitched(nativeTarget.agentDisplayName, displayAlias)
|
|
43088
43111
|
};
|
|
43089
43112
|
}
|
|
43113
|
+
const channelId = getChannelIdFromChatKey(chatKey);
|
|
43090
43114
|
const requestedAlias = alias?.trim() || buildDefaultNativeAlias(nativeTarget.agent, session3.sessionId);
|
|
43091
|
-
|
|
43092
|
-
|
|
43093
|
-
|
|
43094
|
-
|
|
43095
|
-
|
|
43115
|
+
let reserved = null;
|
|
43116
|
+
let finalDisplayAlias = requestedAlias;
|
|
43117
|
+
for (let attempt = 0;attempt < 5; attempt += 1) {
|
|
43118
|
+
const candidateDisplay = await allocateUniqueNativeAlias(context, chatKey, requestedAlias);
|
|
43119
|
+
const candidateInternal = scopeDisplayAliasToInternal(channelId, candidateDisplay);
|
|
43120
|
+
const release = context.sessions.tryReserveNewSessionAlias(candidateInternal);
|
|
43121
|
+
if (release) {
|
|
43122
|
+
reserved = { alias: candidateInternal, release };
|
|
43123
|
+
finalDisplayAlias = candidateDisplay;
|
|
43124
|
+
break;
|
|
43125
|
+
}
|
|
43126
|
+
}
|
|
43127
|
+
let finalInternalAlias;
|
|
43128
|
+
let releaseAliasReservation;
|
|
43129
|
+
if (reserved) {
|
|
43130
|
+
finalInternalAlias = reserved.alias;
|
|
43131
|
+
releaseAliasReservation = reserved.release;
|
|
43132
|
+
} else {
|
|
43133
|
+
const desiredInternal = scopeDisplayAliasToInternal(channelId, finalDisplayAlias);
|
|
43134
|
+
const free = context.sessions.tryReserveFreeSessionAlias(desiredInternal);
|
|
43135
|
+
if (!free) {
|
|
43136
|
+
return { text: t().session.sessionAlreadyExists(finalDisplayAlias, nativeTarget.agent, nativeTarget.workspace) };
|
|
43137
|
+
}
|
|
43138
|
+
finalInternalAlias = free.alias;
|
|
43139
|
+
releaseAliasReservation = free.release;
|
|
43140
|
+
finalDisplayAlias = toDisplaySessionAlias(finalInternalAlias);
|
|
43141
|
+
}
|
|
43142
|
+
const transportSession = context.sessions.buildDefaultTransportSessionForChat(chatKey, finalDisplayAlias);
|
|
43143
|
+
const resolvedSession = context.lifecycle.resolveSession(finalInternalAlias, nativeTarget.agent, nativeTarget.workspace, transportSession);
|
|
43144
|
+
const releaseTransportReservation = await context.lifecycle.reserveTransportSession(resolvedSession.transportSession);
|
|
43096
43145
|
try {
|
|
43097
43146
|
try {
|
|
43098
43147
|
await context.transport.resumeAgentSession(resolvedSession, session3.sessionId);
|
|
@@ -43104,7 +43153,7 @@ async function attachNativeSession(context, chatKey, target, session3, alias) {
|
|
|
43104
43153
|
return { text: t().nativeSession.attachVerificationFailed(target.agentDisplayName) };
|
|
43105
43154
|
}
|
|
43106
43155
|
await context.sessions.attachNativeSession({
|
|
43107
|
-
alias:
|
|
43156
|
+
alias: finalInternalAlias,
|
|
43108
43157
|
agent: nativeTarget.agent,
|
|
43109
43158
|
workspace: nativeTarget.workspace,
|
|
43110
43159
|
transportSession,
|
|
@@ -43115,13 +43164,17 @@ async function attachNativeSession(context, chatKey, target, session3, alias) {
|
|
|
43115
43164
|
title: session3.title,
|
|
43116
43165
|
updatedAt: session3.updatedAt
|
|
43117
43166
|
});
|
|
43118
|
-
await context.sessions.useSession(chatKey,
|
|
43119
|
-
await refreshAgentCommandBestEffort(context,
|
|
43167
|
+
await context.sessions.useSession(chatKey, finalInternalAlias);
|
|
43168
|
+
await refreshAgentCommandBestEffort(context, finalInternalAlias);
|
|
43120
43169
|
return {
|
|
43121
|
-
text: t().nativeSession.attachedAndSwitched(target.agentDisplayName, toDisplaySessionAlias(
|
|
43170
|
+
text: t().nativeSession.attachedAndSwitched(target.agentDisplayName, toDisplaySessionAlias(finalInternalAlias))
|
|
43122
43171
|
};
|
|
43123
43172
|
} finally {
|
|
43124
|
-
|
|
43173
|
+
try {
|
|
43174
|
+
await releaseTransportReservation();
|
|
43175
|
+
} finally {
|
|
43176
|
+
releaseAliasReservation();
|
|
43177
|
+
}
|
|
43125
43178
|
}
|
|
43126
43179
|
}
|
|
43127
43180
|
async function resolveNativeTarget(context, chatKey, input) {
|
|
@@ -44384,13 +44437,14 @@ class SessionControlService {
|
|
|
44384
44437
|
this.reserveLogicalTransportSession = deps.reserveLogicalTransportSession;
|
|
44385
44438
|
}
|
|
44386
44439
|
async createSessionWithTransport(internalAlias, agent3, workspace3, model) {
|
|
44387
|
-
const
|
|
44388
|
-
if (!
|
|
44389
|
-
throw new Error(`session "${internalAlias}" already exists`);
|
|
44440
|
+
const reserved = this.sessions.tryReserveFreeSessionAlias(internalAlias);
|
|
44441
|
+
if (!reserved) {
|
|
44442
|
+
throw new Error(`session "${internalAlias}" already exists and could not derive a free alias`);
|
|
44390
44443
|
}
|
|
44444
|
+
const { alias: finalInternalAlias, release: releaseAliasReservation } = reserved;
|
|
44391
44445
|
try {
|
|
44392
|
-
const stableTransportSession = `${workspace3}:${
|
|
44393
|
-
const session3 = this.sessions.resolveSession(
|
|
44446
|
+
const stableTransportSession = `${workspace3}:${finalInternalAlias}`;
|
|
44447
|
+
const session3 = this.sessions.resolveSession(finalInternalAlias, agent3, workspace3, this.sessions.buildFreshTransportSession(stableTransportSession));
|
|
44394
44448
|
const normalizedModel = model?.trim();
|
|
44395
44449
|
if (normalizedModel) {
|
|
44396
44450
|
session3.model = normalizedModel;
|
|
@@ -44402,15 +44456,15 @@ class SessionControlService {
|
|
|
44402
44456
|
if (!exists) {
|
|
44403
44457
|
throw new Error(`transport session "${session3.transportSession}" could not be verified`);
|
|
44404
44458
|
}
|
|
44405
|
-
await this.sessions.attachSession(
|
|
44459
|
+
await this.sessions.attachSession(finalInternalAlias, agent3, workspace3, session3.transportSession, session3.agentCommand, session3.acpxAgent, session3.agentArgv);
|
|
44406
44460
|
if (normalizedModel) {
|
|
44407
|
-
await this.sessions.setSessionModel(
|
|
44461
|
+
await this.sessions.setSessionModel(finalInternalAlias, normalizedModel);
|
|
44408
44462
|
}
|
|
44409
44463
|
try {
|
|
44410
|
-
await this.invoker.refreshSessionTransportAgentCommand(
|
|
44464
|
+
await this.invoker.refreshSessionTransportAgentCommand(finalInternalAlias);
|
|
44411
44465
|
} catch (error2) {
|
|
44412
44466
|
await this.logger.error("session.agent_command_refresh_failed", "failed to refresh session agent command", {
|
|
44413
|
-
alias:
|
|
44467
|
+
alias: finalInternalAlias,
|
|
44414
44468
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
44415
44469
|
});
|
|
44416
44470
|
}
|
|
@@ -44533,12 +44587,13 @@ class SessionControlService {
|
|
|
44533
44587
|
if (!this.transport.resumeAgentSession) {
|
|
44534
44588
|
throw new Error("the active transport does not support native sessions");
|
|
44535
44589
|
}
|
|
44536
|
-
const
|
|
44537
|
-
if (!
|
|
44538
|
-
throw new Error(`session "${internalAlias}" already exists`);
|
|
44590
|
+
const reserved = this.sessions.tryReserveFreeSessionAlias(internalAlias);
|
|
44591
|
+
if (!reserved) {
|
|
44592
|
+
throw new Error(`session "${internalAlias}" already exists and could not derive a free alias`);
|
|
44539
44593
|
}
|
|
44594
|
+
const { alias: finalInternalAlias, release: releaseAliasReservation } = reserved;
|
|
44540
44595
|
try {
|
|
44541
|
-
const session3 = this.sessions.resolveSession(
|
|
44596
|
+
const session3 = this.sessions.resolveSession(finalInternalAlias, agent3, workspace3, `${workspace3}:${finalInternalAlias}`);
|
|
44542
44597
|
const release = await this.reserveLogicalTransportSession(session3.transportSession);
|
|
44543
44598
|
try {
|
|
44544
44599
|
await this.transport.resumeAgentSession(session3, agentSessionId);
|
|
@@ -44547,7 +44602,7 @@ class SessionControlService {
|
|
|
44547
44602
|
throw new Error(`transport session "${session3.transportSession}" could not be verified`);
|
|
44548
44603
|
}
|
|
44549
44604
|
await this.sessions.attachNativeSession({
|
|
44550
|
-
alias:
|
|
44605
|
+
alias: finalInternalAlias,
|
|
44551
44606
|
agent: agent3,
|
|
44552
44607
|
workspace: workspace3,
|
|
44553
44608
|
transportSession: session3.transportSession,
|
|
@@ -44559,10 +44614,10 @@ class SessionControlService {
|
|
|
44559
44614
|
...nativeMeta?.updatedAt !== undefined ? { updatedAt: nativeMeta.updatedAt } : {}
|
|
44560
44615
|
});
|
|
44561
44616
|
try {
|
|
44562
|
-
await this.invoker.refreshSessionTransportAgentCommand(
|
|
44617
|
+
await this.invoker.refreshSessionTransportAgentCommand(finalInternalAlias);
|
|
44563
44618
|
} catch (error2) {
|
|
44564
44619
|
await this.logger.error("session.native.agent_command_refresh_failed", "failed to refresh native session agent command", {
|
|
44565
|
-
alias:
|
|
44620
|
+
alias: finalInternalAlias,
|
|
44566
44621
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
44567
44622
|
});
|
|
44568
44623
|
}
|
|
@@ -49832,6 +49887,37 @@ class SessionService {
|
|
|
49832
49887
|
this.pendingSessionAliasOperations.delete(alias);
|
|
49833
49888
|
};
|
|
49834
49889
|
}
|
|
49890
|
+
deriveFreeAlias(desiredAlias) {
|
|
49891
|
+
const taken = Object.keys(this.state.sessions);
|
|
49892
|
+
const takenSet = new Set(taken);
|
|
49893
|
+
if (!takenSet.has(desiredAlias))
|
|
49894
|
+
return desiredAlias;
|
|
49895
|
+
const sep4 = desiredAlias.lastIndexOf(":");
|
|
49896
|
+
const prefix = sep4 >= 0 ? desiredAlias.slice(0, sep4 + 1) : "";
|
|
49897
|
+
const base = sep4 >= 0 ? desiredAlias.slice(sep4 + 1) : desiredAlias;
|
|
49898
|
+
const suffixMatch = /-(\d+)$/.exec(base);
|
|
49899
|
+
let startN = 2;
|
|
49900
|
+
let stem = base;
|
|
49901
|
+
if (suffixMatch) {
|
|
49902
|
+
startN = Number(suffixMatch[1]) + 1;
|
|
49903
|
+
stem = base.slice(0, -suffixMatch[0].length);
|
|
49904
|
+
}
|
|
49905
|
+
for (let n = startN;n <= 9999; n += 1) {
|
|
49906
|
+
const candidate = `${prefix}${stem}-${n}`;
|
|
49907
|
+
if (!takenSet.has(candidate))
|
|
49908
|
+
return candidate;
|
|
49909
|
+
}
|
|
49910
|
+
throw new Error(`could not derive a free alias for "${desiredAlias}" after 9999 attempts`);
|
|
49911
|
+
}
|
|
49912
|
+
tryReserveFreeSessionAlias(desiredAlias) {
|
|
49913
|
+
for (let attempt = 0;attempt < 5; attempt += 1) {
|
|
49914
|
+
const alias = this.deriveFreeAlias(desiredAlias);
|
|
49915
|
+
const release = this.tryReserveNewSessionAlias(alias);
|
|
49916
|
+
if (release)
|
|
49917
|
+
return { alias, release };
|
|
49918
|
+
}
|
|
49919
|
+
return null;
|
|
49920
|
+
}
|
|
49835
49921
|
buildFreshTransportSession(stableTransportSession) {
|
|
49836
49922
|
const timestamp = Math.max(0, Math.trunc(this.now()));
|
|
49837
49923
|
const nonce = randomBytes2(8).readBigUInt64BE().toString().padStart(20, "0");
|
|
@@ -50066,7 +50152,7 @@ class SessionService {
|
|
|
50066
50152
|
platform,
|
|
50067
50153
|
runtimeRoot: this.runtimeRoot
|
|
50068
50154
|
});
|
|
50069
|
-
if (current.rawCommand) {
|
|
50155
|
+
if (current.rawCommand || resolveAgentCommand(agentConfig.driver, agentConfig.command)) {
|
|
50070
50156
|
return current;
|
|
50071
50157
|
}
|
|
50072
50158
|
const recordedArgv = isDerivedAgentArgv(agentConfig.driver, session3.transport_agent_argv, this.runtimeRoot, platform) ? undefined : session3.transport_agent_argv;
|
|
@@ -50086,9 +50172,6 @@ class SessionService {
|
|
|
50086
50172
|
}) === agentConfig.driver);
|
|
50087
50173
|
const recordedCommand = recordedIsDerived ? undefined : session3.transport_agent_command;
|
|
50088
50174
|
if (recordedCommand) {
|
|
50089
|
-
if (platform === "win32") {
|
|
50090
|
-
throw new Error(`session "${session3.alias}" was created with a raw command that cannot be launched ` + "on Windows. Migrate the agent to an argv array in config: " + 'agents.<name>.argv = ["agent.exe", "--acp", ...]');
|
|
50091
|
-
}
|
|
50092
50175
|
return { acpxAgent: agentConfig.driver, rawCommand: recordedCommand, agentCommand: recordedCommand };
|
|
50093
50176
|
}
|
|
50094
50177
|
return current;
|
|
@@ -50265,25 +50348,6 @@ class SessionService {
|
|
|
50265
50348
|
}
|
|
50266
50349
|
}
|
|
50267
50350
|
}
|
|
50268
|
-
function isDerivedAgentArgv(driver, argv, runtimeRoot, platform) {
|
|
50269
|
-
if (!argv || argv.length === 0) {
|
|
50270
|
-
return false;
|
|
50271
|
-
}
|
|
50272
|
-
if (isManagedAdapterId(driver)) {
|
|
50273
|
-
const spec = MANAGED_ADAPTERS[driver];
|
|
50274
|
-
return argv[0] === "npx" && argv[1] === "-y" && argv.some((entry) => entry.startsWith(`${spec.packageName}@`)) || classifyRecordedPreinstalledAdapterCommand(renderAgentArgvIdentity(argv), {
|
|
50275
|
-
runtimeRoot,
|
|
50276
|
-
platform
|
|
50277
|
-
}) === driver;
|
|
50278
|
-
}
|
|
50279
|
-
if (driver === "hermes") {
|
|
50280
|
-
return argv[1]?.includes("hermes-acp-shim.") === true;
|
|
50281
|
-
}
|
|
50282
|
-
if (driver === "opencode" || driver === "kilocode") {
|
|
50283
|
-
return argv.length === 2 && argv[0] === driver && argv[1] === "acp";
|
|
50284
|
-
}
|
|
50285
|
-
return false;
|
|
50286
|
-
}
|
|
50287
50351
|
var init_session_service = __esm(() => {
|
|
50288
50352
|
init_resolve_agent_command();
|
|
50289
50353
|
init_adapter_catalog();
|
|
@@ -57056,7 +57120,8 @@ class ControlService {
|
|
|
57056
57120
|
const session3 = agentSessionId ? await this.deps.attachNativeSessionWithTransport(internalAlias, agent3, workspace3, agentSessionId) : await this.deps.createSessionWithTransport(internalAlias, agent3, workspace3, model);
|
|
57057
57121
|
this.deps.events.emit({ type: "sessions-changed" });
|
|
57058
57122
|
if (nativeHistory.length > 0) {
|
|
57059
|
-
|
|
57123
|
+
const historyAlias = toDisplaySessionAlias(session3.alias);
|
|
57124
|
+
this.deps.events.emit({ type: "session-history", chatKey, sessionAlias: historyAlias, messages: nativeHistory });
|
|
57060
57125
|
}
|
|
57061
57126
|
return {
|
|
57062
57127
|
alias: toDisplaySessionAlias(session3.alias),
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
* - Structured launches carry `agentArgv` (exact boundaries, Windows-safe) and
|
|
9
9
|
* `agentCommand` (the stable canonical identity acpx persists and keys its
|
|
10
10
|
* session records by).
|
|
11
|
-
* - `rawCommand` is the
|
|
11
|
+
* - `rawCommand` is the legacy escape hatch / historical selector for acpx `--agent`.
|
|
12
12
|
*/
|
|
13
13
|
export interface AgentLaunchSpec {
|
|
14
14
|
/** acpx positional agent: bare driver or xacpx-managed alias. */
|
|
15
15
|
acpxAgent: string;
|
|
16
16
|
/** Stable acpx session identity for explicit launches. */
|
|
17
17
|
agentCommand?: string;
|
|
18
|
-
/** Exact executable and argument boundaries for overlay
|
|
18
|
+
/** Exact executable and argument boundaries for structured / overlay launches. */
|
|
19
19
|
agentArgv?: string[];
|
|
20
|
-
/**
|
|
20
|
+
/** Legacy raw override / historical session selector. */
|
|
21
21
|
rawCommand?: string;
|
|
22
22
|
}
|
|
23
23
|
export declare function renderAgentArgvIdentity(argv: readonly string[]): string;
|
|
@@ -26,3 +26,9 @@ export declare function isValidAgentArgv(value: unknown): value is string[];
|
|
|
26
26
|
/** Content-addressed overlay alias: stable per canonical argv across processes
|
|
27
27
|
* and platforms; only acpx-safe name characters. */
|
|
28
28
|
export declare function deriveAgentAlias(driver: string, argv: readonly string[]): string;
|
|
29
|
+
/** Detects an argv shape that `resolveLaunchSpec` treats as derived state
|
|
30
|
+
* (managed adapter pin, hermes shim, or local fallback for opencode /
|
|
31
|
+
* kilocode). A session with such argv has `recordedArgv` reset to undefined
|
|
32
|
+
* before step 2 in `resolveLaunchSpec`, so the sticky bypass does NOT
|
|
33
|
+
* apply — the session follows the current config / derived launch instead. */
|
|
34
|
+
export declare function isDerivedAgentArgv(driver: string, argv: string[] | undefined, runtimeRoot: string, platform: NodeJS.Platform): boolean;
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface SessionMessages {
|
|
|
12
12
|
sessionListItem: (alias: string, agent: string, workspace: string) => string;
|
|
13
13
|
sessionCreated: (alias: string) => string;
|
|
14
14
|
sessionAlreadyExists: (alias: string, agent: string, workspace: string) => string;
|
|
15
|
+
sessionAliasCollided: (desiredAlias: string, finalAlias: string) => string;
|
|
15
16
|
sessionAttachNotFound: (alias: string, agent: string, workspace: string) => string;
|
|
16
17
|
sessionAttached: (alias: string) => string;
|
|
17
18
|
sessionLifecycleBusy: (alias: string) => string;
|
package/dist/plugin-api.js
CHANGED
|
@@ -97,6 +97,7 @@ var init_session = __esm(() => {
|
|
|
97
97
|
`Switch to it with /use ${alias}, or remove it first with /session rm ${alias}.`
|
|
98
98
|
].join(`
|
|
99
99
|
`),
|
|
100
|
+
sessionAliasCollided: (desiredAlias, finalAlias) => `The requested name "${desiredAlias}" is already in use (by an archived session). Creating the new session as "${finalAlias}" instead.`,
|
|
100
101
|
sessionAttachNotFound: (alias, agent, workspace) => [
|
|
101
102
|
"No existing session found to attach.",
|
|
102
103
|
`Check the session name and retry: /session attach ${alias} --agent ${agent} --ws ${workspace} --name <session-name>`
|
|
@@ -1222,6 +1223,7 @@ var init_session2 = __esm(() => {
|
|
|
1222
1223
|
`发送 /use ${alias} 切换到它,或先执行 /session rm ${alias} 删除后再创建。`
|
|
1223
1224
|
].join(`
|
|
1224
1225
|
`),
|
|
1226
|
+
sessionAliasCollided: (desiredAlias, finalAlias) => `你指定的会话名「${desiredAlias}」已被其他会话(含已睡眠/归档)占用,已自动改为「${finalAlias}」创建。`,
|
|
1225
1227
|
sessionAttachNotFound: (alias, agent2, workspace2) => [
|
|
1226
1228
|
"没有找到可绑定的已有会话。",
|
|
1227
1229
|
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent2} --ws ${workspace2} --name <会话名>`
|
|
@@ -124,6 +124,30 @@ export declare class SessionService {
|
|
|
124
124
|
tryReserveNewSessionAlias(alias: string): (() => void) | null;
|
|
125
125
|
/** Exclusively claim any lifecycle operation that can replace an alias binding. */
|
|
126
126
|
tryReserveSessionAliasOperation(alias: string): (() => void) | null;
|
|
127
|
+
/**
|
|
128
|
+
* Derive a free internal alias for a new session. When `desiredAlias` is already
|
|
129
|
+
* taken (by an active OR archived session), append `-2`, `-3`, … until a free
|
|
130
|
+
* slot is found.
|
|
131
|
+
*
|
|
132
|
+
* The alias may carry a channel prefix (`relay:demo`, `weixin:chat123/demo`); the
|
|
133
|
+
* numeric suffix is always appended to the last path segment after the rightmost
|
|
134
|
+
* `:` so prefix semantics are preserved (e.g. `relay:demo-2`, never `relay-2:demo`).
|
|
135
|
+
*/
|
|
136
|
+
deriveFreeAlias(desiredAlias: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Convenience: atomically derive a free alias (see `deriveFreeAlias`) AND
|
|
139
|
+
* reserve it via `tryReserveNewSessionAlias`. If the derived alias loses the
|
|
140
|
+
* race between the derive step and the reserve step (extremely rare because
|
|
141
|
+
* callers typically run this while holding the state mutex), retry up to a
|
|
142
|
+
* small, bounded number of times.
|
|
143
|
+
*
|
|
144
|
+
* Returns the chosen alias together with the reservation release callback, or
|
|
145
|
+
* `null` if no reservation could be obtained.
|
|
146
|
+
*/
|
|
147
|
+
tryReserveFreeSessionAlias(desiredAlias: string): {
|
|
148
|
+
alias: string;
|
|
149
|
+
release: () => void;
|
|
150
|
+
} | null;
|
|
127
151
|
/**
|
|
128
152
|
* Allocate a fresh acpx transport incarnation while preserving a stable logical
|
|
129
153
|
* coordinator identity. Explicit "new session" paths use this; archive/restore
|
|
@@ -64,7 +64,7 @@ export interface ResolvedSession {
|
|
|
64
64
|
* alias provisioned in ~/.acpx/config.json; bare drivers pass through.
|
|
65
65
|
*/
|
|
66
66
|
acpxAgent?: string;
|
|
67
|
-
/**
|
|
67
|
+
/** Legacy raw override / historical session selector passed as acpx `--agent`. */
|
|
68
68
|
rawCommand?: string;
|
|
69
69
|
/** Exact executable + argument boundaries for overlay/migration. */
|
|
70
70
|
agentArgv?: string[];
|
|
@@ -124,7 +124,7 @@ export interface AgentSessionListQuery {
|
|
|
124
124
|
agentCommand?: string;
|
|
125
125
|
/** Positional acpx agent for list/show queries (overlay alias or bare driver). */
|
|
126
126
|
acpxAgent?: string;
|
|
127
|
-
/**
|
|
127
|
+
/** Legacy raw override / historical selector; the shared builder prefers it over acpxAgent. */
|
|
128
128
|
rawCommand?: string;
|
|
129
129
|
/** Resolved acpx driver for `agent` (e.g. a custom `my-codex` agent has driver `codex`). Used to gate driver-specific list filtering. */
|
|
130
130
|
driver?: string;
|