@ganglion/xacpx 0.9.2 → 0.10.0
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 +170 -80
- package/dist/channels/types.d.ts +11 -0
- package/dist/channels/weixin-channel.d.ts +8 -0
- package/dist/cli.js +1456 -833
- package/dist/commands/handlers/later-handler.d.ts +3 -3
- package/dist/commands/parse-command.d.ts +1 -0
- package/dist/commands/router-types.d.ts +1 -1
- package/dist/config/config-store.d.ts +44 -5
- package/dist/config/types.d.ts +8 -0
- package/dist/i18n/types.d.ts +7 -3
- package/dist/orchestration/coordinator-identity.d.ts +27 -0
- package/dist/orchestration/orchestration-service.d.ts +0 -23
- package/dist/perf/perf-log-writer.d.ts +1 -0
- package/dist/plugin-api.js +24 -8
- package/dist/scheduled/scheduled-service.d.ts +5 -2
- package/dist/sessions/session-service.d.ts +7 -3
- package/dist/state/state-store.d.ts +69 -2
- package/dist/util/private-file.d.ts +12 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -93,6 +93,11 @@ var init_session = __esm(() => {
|
|
|
93
93
|
currentLabel: "[current]",
|
|
94
94
|
sessionListItem: (alias, agent, workspace) => `- ${alias} (${agent} @ ${workspace})`,
|
|
95
95
|
sessionCreated: (alias) => `Session "${alias}" created and switched.`,
|
|
96
|
+
sessionAlreadyExists: (alias, agent, workspace) => [
|
|
97
|
+
`Session "${alias}" already exists (${agent} @ ${workspace}).`,
|
|
98
|
+
`Switch to it with /use ${alias}, or remove it first with /session rm ${alias}.`
|
|
99
|
+
].join(`
|
|
100
|
+
`),
|
|
96
101
|
sessionAttachNotFound: (alias, agent, workspace) => [
|
|
97
102
|
"No existing session found to attach.",
|
|
98
103
|
`Check the session name and retry: /session attach ${alias} --agent ${agent} --ws ${workspace} --name <session-name>`
|
|
@@ -129,6 +134,7 @@ var init_session = __esm(() => {
|
|
|
129
134
|
sessionBlockedByTasksHint: "Use /tasks to list tasks, or /task cancel <id> to cancel one.",
|
|
130
135
|
sessionRemoved: (alias) => `Session "${alias}" removed.`,
|
|
131
136
|
sessionRemovedWasActive: "This was the active session. Its chat context has been cleared.",
|
|
137
|
+
sessionRemovedWasActivePromoted: (alias) => `This was the active session. Switched back to the previous session "${alias}".`,
|
|
132
138
|
sessionTransportShared: (transportSession, count) => `Note: backend session "${transportSession}" is still referenced by ${count} other session(s) and was not closed.`,
|
|
133
139
|
sessionOrchestrationPurgeFailed: (warning) => `Note: failed to purge orchestration references (${warning}). Run /tasks clean manually to clean up.`,
|
|
134
140
|
sessionTransportTeardownFailed: (warning) => `Note: backend session could not be closed automatically (${warning}). Run acpx sessions close manually if needed.`,
|
|
@@ -422,7 +428,7 @@ var init_later = __esm(() => {
|
|
|
422
428
|
helpNote2: "Time must be at least 10 seconds and at most 7 days away",
|
|
423
429
|
helpNote3: "By default runs in a new temporary session that is destroyed after completion",
|
|
424
430
|
helpNote4: "Use --bind to send to the session that was current when the task was created (configurable via later.defaultMode)",
|
|
425
|
-
helpNote5: "/lt list shows
|
|
431
|
+
helpNote5: "/lt list shows only this chat's pending tasks; in group chats only the owner can cancel",
|
|
426
432
|
helpNote6: "Scheduling slash-prefixed xacpx commands is not supported",
|
|
427
433
|
helpNote7: "Full time format reference: docs/later-command.md"
|
|
428
434
|
};
|
|
@@ -851,7 +857,6 @@ var init_cli_update = __esm(() => {
|
|
|
851
857
|
updateFailed: (name, error) => `${name} update failed: ${error}`,
|
|
852
858
|
targetNotFound: (name) => `Update target not found: ${name}`,
|
|
853
859
|
targetVersionUnknown: (name) => `${name}: cannot check latest version; skipped.`,
|
|
854
|
-
targetNotPinned: (name) => `${name} has no recorded version; use \`xacpx plugin update ${name}\` or specify a version explicitly.`,
|
|
855
860
|
multiTargetNonInteractive: "Installed plugins detected; in non-interactive mode use `xacpx update --all` or `xacpx update <name>`.",
|
|
856
861
|
selectionPrompt: "Select items to update (numbers, comma-separated; a=all; Enter to cancel): ",
|
|
857
862
|
selectionInvalid: (part) => `Invalid selection: ${part}`,
|
|
@@ -916,6 +921,8 @@ var init_plugin_cli = __esm(() => {
|
|
|
916
921
|
noPlugins: "No plugins installed yet.",
|
|
917
922
|
pluginListHeader: "Plugins:",
|
|
918
923
|
unrecognizedArgs: (args) => `Unrecognized arguments: ${args}`,
|
|
924
|
+
pluginSpecHasDoubleQuote: (spec) => `Invalid plugin spec ${spec}: double quotes (") are never valid in an npm package spec.`,
|
|
925
|
+
pluginSpecHasPercentOnWindows: (spec) => `Invalid plugin spec ${spec}: "%" would be mangled by cmd.exe on Windows. Install the package with npm directly instead.`,
|
|
919
926
|
pluginInstallFailed: (packageSpec, error) => `Plugin ${packageSpec} install failed: ${error}`,
|
|
920
927
|
pluginValidateFailed: (recordedName, error) => `Plugin ${recordedName} validation failed: ${error}`,
|
|
921
928
|
pluginInstalled: (recordedName) => `Plugin ${recordedName} installed`,
|
|
@@ -1027,8 +1034,6 @@ var init_weixin = __esm(() => {
|
|
|
1027
1034
|
debugEnabled: "Debug mode enabled",
|
|
1028
1035
|
debugDisabled: "Debug mode disabled",
|
|
1029
1036
|
sessionCleared: "✅ Session cleared. Starting a fresh conversation.",
|
|
1030
|
-
noAccountsLoggedIn: "No accounts are currently logged in.",
|
|
1031
|
-
logoutSuccess: "✅ Logged out. All account credentials cleared.",
|
|
1032
1037
|
commandFailed: (detail) => `❌ Command failed: ${detail}`
|
|
1033
1038
|
};
|
|
1034
1039
|
});
|
|
@@ -1066,6 +1071,7 @@ var init_misc = __esm(() => {
|
|
|
1066
1071
|
quotaOverflowSummary: (count) => `(${count} progress updates omitted due to message limit; see final result below)`,
|
|
1067
1072
|
finalHeadsUp: (total, sentSoFar, remaining) => `—
|
|
1068
1073
|
\uD83D\uDCC4 Result: ${total} parts total, ${sentSoFar} sent. Reply /jx to see the next ${remaining} parts.`,
|
|
1074
|
+
finalAllParked: (count) => `\uD83D\uDCC4 Message limit reached: the result (${count} parts) is parked. Reply /jx to receive it.`,
|
|
1069
1075
|
quotedMessagePrefix: (parts) => `[Quote: ${parts}]`,
|
|
1070
1076
|
scheduledTaskFailed: (message) => `Scheduled task failed: ${message}`,
|
|
1071
1077
|
orchestrationTaskCompleted: (taskId, workerSession, result) => `Delegation task "${taskId}" completed
|
|
@@ -1094,6 +1100,8 @@ var init_misc = __esm(() => {
|
|
|
1094
1100
|
delegateQPackageInstr3: "Do not forward the human's exact words to the worker",
|
|
1095
1101
|
commandAccessDeniedSuffix: " is restricted to group owner only.",
|
|
1096
1102
|
commandAccessDeniedHint: "To perform control operations, have the owner send them in the group, or use a private chat.",
|
|
1103
|
+
commandAccessDeniedChatTypeMissingSuffix: " was blocked: this channel did not report the chat type (direct or group), so control commands are disabled here.",
|
|
1104
|
+
commandAccessDeniedChatTypeMissingHint: "Read-only commands and prompts still work. This is a channel metadata issue — update or report the channel plugin.",
|
|
1097
1105
|
commandLabelThisMessage: "This message",
|
|
1098
1106
|
sessionResetNoCurrentSession: "No session is currently selected. Run /session new ... or /use <alias> first.",
|
|
1099
1107
|
sessionResetFailed: (alias) => `Session "${alias}" reset failed. The new backend session was not created, please try again later.`,
|
|
@@ -1164,6 +1172,11 @@ var init_session2 = __esm(() => {
|
|
|
1164
1172
|
currentLabel: "[当前]",
|
|
1165
1173
|
sessionListItem: (alias, agent2, workspace2) => `- ${alias} (${agent2} @ ${workspace2})`,
|
|
1166
1174
|
sessionCreated: (alias) => `会话「${alias}」已创建并切换`,
|
|
1175
|
+
sessionAlreadyExists: (alias, agent2, workspace2) => [
|
|
1176
|
+
`会话「${alias}」已存在(${agent2} @ ${workspace2})。`,
|
|
1177
|
+
`发送 /use ${alias} 切换到它,或先执行 /session rm ${alias} 删除后再创建。`
|
|
1178
|
+
].join(`
|
|
1179
|
+
`),
|
|
1167
1180
|
sessionAttachNotFound: (alias, agent2, workspace2) => [
|
|
1168
1181
|
"没有找到可绑定的已有会话。",
|
|
1169
1182
|
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent2} --ws ${workspace2} --name <会话名>`
|
|
@@ -1200,6 +1213,7 @@ var init_session2 = __esm(() => {
|
|
|
1200
1213
|
sessionBlockedByTasksHint: "使用 /tasks 查看任务列表,或 /task cancel <id> 取消任务。",
|
|
1201
1214
|
sessionRemoved: (alias) => `已删除会话「${alias}」。`,
|
|
1202
1215
|
sessionRemovedWasActive: "该会话是当前活跃会话,已自动清除相关聊天上下文。",
|
|
1216
|
+
sessionRemovedWasActivePromoted: (alias) => `该会话是当前活跃会话,已切换回上一个会话「${alias}」。`,
|
|
1203
1217
|
sessionTransportShared: (transportSession, count) => `提示:后端会话「${transportSession}」仍被其他 ${count} 个会话引用,未关闭。`,
|
|
1204
1218
|
sessionOrchestrationPurgeFailed: (warning) => `提示:清理任务编排引用失败(${warning}),请稍后执行 /tasks clean 手动清理。`,
|
|
1205
1219
|
sessionTransportTeardownFailed: (warning) => `提示:后端会话未能自动关闭(${warning}),如有残留请手动执行 acpx sessions close。`,
|
|
@@ -1493,7 +1507,7 @@ var init_later2 = __esm(() => {
|
|
|
1493
1507
|
helpNote2: "时间必须在 10 秒之后、7 天之内",
|
|
1494
1508
|
helpNote3: "默认在为本次任务新建的临时会话里执行,跑完即销毁",
|
|
1495
1509
|
helpNote4: "加 --bind 改为发送到创建时绑定的当前会话(默认模式可用 later.defaultMode 配置)",
|
|
1496
|
-
helpNote5: "/lt list
|
|
1510
|
+
helpNote5: "/lt list 只显示本聊天创建的待执行任务;群聊中只有群主可取消",
|
|
1497
1511
|
helpNote6: "不支持延迟执行 / 开头的 xacpx 命令",
|
|
1498
1512
|
helpNote7: "完整时间格式与说明见 docs/later-command.md"
|
|
1499
1513
|
};
|
|
@@ -1922,7 +1936,6 @@ var init_cli_update2 = __esm(() => {
|
|
|
1922
1936
|
updateFailed: (name, error) => `${name} 更新失败:${error}`,
|
|
1923
1937
|
targetNotFound: (name) => `没有找到更新项:${name}`,
|
|
1924
1938
|
targetVersionUnknown: (name) => `${name} 无法检查最新版本,已跳过。`,
|
|
1925
|
-
targetNotPinned: (name) => `${name} 未记录当前版本;请先使用 \`xacpx plugin update ${name}\` 或显式选择版本。`,
|
|
1926
1939
|
multiTargetNonInteractive: "检测到已安装插件;非交互模式请使用 `xacpx update --all` 或 `xacpx update <name>`。",
|
|
1927
1940
|
selectionPrompt: "请选择要更新的项目(数字,逗号分隔,a=全部,回车取消):",
|
|
1928
1941
|
selectionInvalid: (part) => `无效选择:${part}`,
|
|
@@ -1987,6 +2000,8 @@ var init_plugin_cli2 = __esm(() => {
|
|
|
1987
2000
|
noPlugins: "还没有安装插件。",
|
|
1988
2001
|
pluginListHeader: "插件:",
|
|
1989
2002
|
unrecognizedArgs: (args) => `未识别的参数:${args}`,
|
|
2003
|
+
pluginSpecHasDoubleQuote: (spec) => `非法插件 spec ${spec}:npm 包 spec 不允许包含双引号 (")。`,
|
|
2004
|
+
pluginSpecHasPercentOnWindows: (spec) => `非法插件 spec ${spec}:Windows 上 cmd.exe 会展开 %,无法安全传递。请改用 npm 直接安装该包。`,
|
|
1990
2005
|
pluginInstallFailed: (packageSpec, error) => `插件 ${packageSpec} 安装失败:${error}`,
|
|
1991
2006
|
pluginValidateFailed: (recordedName, error) => `插件 ${recordedName} 校验失败:${error}`,
|
|
1992
2007
|
pluginInstalled: (recordedName) => `插件 ${recordedName} 已安装`,
|
|
@@ -2098,8 +2113,6 @@ var init_weixin2 = __esm(() => {
|
|
|
2098
2113
|
debugEnabled: "Debug 模式已开启",
|
|
2099
2114
|
debugDisabled: "Debug 模式已关闭",
|
|
2100
2115
|
sessionCleared: "✅ 会话已清除,重新开始对话",
|
|
2101
|
-
noAccountsLoggedIn: "当前没有已登录的账号",
|
|
2102
|
-
logoutSuccess: "✅ 已退出登录,清除所有账号凭证",
|
|
2103
2116
|
commandFailed: (detail) => `❌ 指令执行失败: ${detail}`
|
|
2104
2117
|
};
|
|
2105
2118
|
});
|
|
@@ -2137,6 +2150,7 @@ var init_misc2 = __esm(() => {
|
|
|
2137
2150
|
quotaOverflowSummary: (count) => `(因消息次数限制省略 ${count} 条进度,请继续查看下方最终结果)`,
|
|
2138
2151
|
finalHeadsUp: (total, sentSoFar, remaining) => `—
|
|
2139
2152
|
\uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`,
|
|
2153
|
+
finalAllParked: (count) => `\uD83D\uDCC4 已达消息上限:结果共 ${count} 段已暂存。回复 /jx 接收。`,
|
|
2140
2154
|
quotedMessagePrefix: (parts) => `[引用: ${parts}]`,
|
|
2141
2155
|
scheduledTaskFailed: (message) => `定时任务执行失败:${message}`,
|
|
2142
2156
|
orchestrationTaskCompleted: (taskId, workerSession, result) => `委派任务「${taskId}」已完成
|
|
@@ -2165,6 +2179,8 @@ var init_misc2 = __esm(() => {
|
|
|
2165
2179
|
delegateQPackageInstr3: "不要直接把 human 原话转发给 worker",
|
|
2166
2180
|
commandAccessDeniedSuffix: " 仅限群创建者/频道 owner 使用。",
|
|
2167
2181
|
commandAccessDeniedHint: "如果需要执行控制类操作,请由 owner 在群内发送,或改用私聊。",
|
|
2182
|
+
commandAccessDeniedChatTypeMissingSuffix: " 已被拦截:该频道未上报会话类型(直聊/群聊),控制类命令在此暂不可用。",
|
|
2183
|
+
commandAccessDeniedChatTypeMissingHint: "只读命令与普通对话不受影响。这是频道元数据问题,请升级或反馈该频道插件。",
|
|
2168
2184
|
commandLabelThisMessage: "该消息",
|
|
2169
2185
|
sessionResetNoCurrentSession: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。",
|
|
2170
2186
|
sessionResetFailed: (alias) => `会话「${alias}」重置失败。
|
|
@@ -4214,7 +4230,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
4214
4230
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
4215
4231
|
import { chmodSync, mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
|
|
4216
4232
|
import { dirname } from "node:path";
|
|
4217
|
-
async function
|
|
4233
|
+
async function withPrivateFileLock(path, fn) {
|
|
4218
4234
|
await mkdir(dirname(path), { recursive: true });
|
|
4219
4235
|
const release = await lockfile.lock(path, {
|
|
4220
4236
|
realpath: false,
|
|
@@ -4228,23 +4244,31 @@ async function writePrivateFileAtomic(path, content) {
|
|
|
4228
4244
|
}
|
|
4229
4245
|
});
|
|
4230
4246
|
try {
|
|
4231
|
-
|
|
4232
|
-
await retryTransientWriteErrors(async () => import_write_file_atomic.default(path, content, {
|
|
4233
|
-
mode: PRIVATE_FILE_MODE,
|
|
4234
|
-
encoding: "utf8",
|
|
4235
|
-
fsync: true
|
|
4236
|
-
}));
|
|
4237
|
-
} catch (error) {
|
|
4238
|
-
if (!isTransientWriteError(error, process.platform)) {
|
|
4239
|
-
throw error;
|
|
4240
|
-
}
|
|
4241
|
-
await writeFile(path, content, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
4242
|
-
await chmod(path, PRIVATE_FILE_MODE).catch(() => {});
|
|
4243
|
-
}
|
|
4247
|
+
return await fn((content) => writePrivateFileAtomicUnlocked(path, content));
|
|
4244
4248
|
} finally {
|
|
4245
4249
|
await release();
|
|
4246
4250
|
}
|
|
4247
4251
|
}
|
|
4252
|
+
async function writePrivateFileAtomic(path, content) {
|
|
4253
|
+
await withPrivateFileLock(path, async (writeLocked) => {
|
|
4254
|
+
await writeLocked(content);
|
|
4255
|
+
});
|
|
4256
|
+
}
|
|
4257
|
+
async function writePrivateFileAtomicUnlocked(path, content) {
|
|
4258
|
+
try {
|
|
4259
|
+
await retryTransientWriteErrors(async () => import_write_file_atomic.default(path, content, {
|
|
4260
|
+
mode: PRIVATE_FILE_MODE,
|
|
4261
|
+
encoding: "utf8",
|
|
4262
|
+
fsync: true
|
|
4263
|
+
}));
|
|
4264
|
+
} catch (error) {
|
|
4265
|
+
if (!isTransientWriteError(error, process.platform)) {
|
|
4266
|
+
throw error;
|
|
4267
|
+
}
|
|
4268
|
+
await writeFile(path, content, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
4269
|
+
await chmod(path, PRIVATE_FILE_MODE).catch(() => {});
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4248
4272
|
function writePrivateFileSync(path, content, deps = {}) {
|
|
4249
4273
|
mkdirSync2(dirname(path), { recursive: true });
|
|
4250
4274
|
const platform = deps.platform ?? process.platform;
|
|
@@ -4401,6 +4425,14 @@ function isRecord(value) {
|
|
|
4401
4425
|
function isReplyMode(value) {
|
|
4402
4426
|
return value === "stream" || value === "final" || value === "verbose";
|
|
4403
4427
|
}
|
|
4428
|
+
function parseOwnerIds(value, path2) {
|
|
4429
|
+
if (value === undefined)
|
|
4430
|
+
return;
|
|
4431
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim().length === 0)) {
|
|
4432
|
+
throw new Error(`${path2} must be an array of non-empty strings`);
|
|
4433
|
+
}
|
|
4434
|
+
return value.map((entry) => entry.trim());
|
|
4435
|
+
}
|
|
4404
4436
|
function parseChannelConfig(channel, legacyWechat) {
|
|
4405
4437
|
if (channel !== undefined) {
|
|
4406
4438
|
if (!isRecord(channel)) {
|
|
@@ -4413,6 +4445,7 @@ function parseChannelConfig(channel, legacyWechat) {
|
|
|
4413
4445
|
throw new Error("channel.replyMode must be stream, final, or verbose");
|
|
4414
4446
|
}
|
|
4415
4447
|
const type = typeof channel.type === "string" ? channel.type : "weixin";
|
|
4448
|
+
const ownerIds = parseOwnerIds(channel.ownerIds, "channel.ownerIds");
|
|
4416
4449
|
let options = undefined;
|
|
4417
4450
|
if ("feishu" in channel && isRecord(channel.feishu)) {
|
|
4418
4451
|
options = channel.feishu;
|
|
@@ -4422,6 +4455,7 @@ function parseChannelConfig(channel, legacyWechat) {
|
|
|
4422
4455
|
return {
|
|
4423
4456
|
type,
|
|
4424
4457
|
replyMode: isReplyMode(channel.replyMode) ? channel.replyMode : DEFAULT_CHANNEL_CONFIG.replyMode,
|
|
4458
|
+
...ownerIds ? { ownerIds } : {},
|
|
4425
4459
|
...options ? { options } : {}
|
|
4426
4460
|
};
|
|
4427
4461
|
}
|
|
@@ -4660,6 +4694,7 @@ function parseRuntimeChannelConfig(raw, index) {
|
|
|
4660
4694
|
if ("replyMode" in raw && !isReplyMode(raw.replyMode)) {
|
|
4661
4695
|
throw new Error(`channels[${index}].replyMode must be stream, final, or verbose`);
|
|
4662
4696
|
}
|
|
4697
|
+
const ownerIds = parseOwnerIds(raw.ownerIds, `channels[${index}].ownerIds`);
|
|
4663
4698
|
let options = undefined;
|
|
4664
4699
|
if ("feishu" in raw && isRecord(raw.feishu)) {
|
|
4665
4700
|
options = raw.feishu;
|
|
@@ -4671,6 +4706,7 @@ function parseRuntimeChannelConfig(raw, index) {
|
|
|
4671
4706
|
type: raw.type,
|
|
4672
4707
|
enabled,
|
|
4673
4708
|
...isReplyMode(raw.replyMode) ? { replyMode: raw.replyMode } : {},
|
|
4709
|
+
...ownerIds ? { ownerIds } : {},
|
|
4674
4710
|
...options ? { options } : {}
|
|
4675
4711
|
};
|
|
4676
4712
|
}
|
|
@@ -4755,6 +4791,8 @@ var init_load_config = __esm(() => {
|
|
|
4755
4791
|
});
|
|
4756
4792
|
|
|
4757
4793
|
// src/config/config-store.ts
|
|
4794
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
4795
|
+
|
|
4758
4796
|
class ConfigStore {
|
|
4759
4797
|
path;
|
|
4760
4798
|
constructor(path2) {
|
|
@@ -4763,60 +4801,250 @@ class ConfigStore {
|
|
|
4763
4801
|
async load() {
|
|
4764
4802
|
return await loadConfig(this.path);
|
|
4765
4803
|
}
|
|
4766
|
-
async
|
|
4767
|
-
await
|
|
4768
|
-
|
|
4804
|
+
async getRawValue(path2) {
|
|
4805
|
+
return readRawConfigValue((await this.readRaw()).raw, path2);
|
|
4806
|
+
}
|
|
4807
|
+
async setRawValue(path2, value) {
|
|
4808
|
+
return await this.patchRaw((raw) => {
|
|
4809
|
+
setRawConfigValue(raw, path2, value);
|
|
4810
|
+
});
|
|
4811
|
+
}
|
|
4812
|
+
async unsetRawValue(path2) {
|
|
4813
|
+
return await this.patchRaw((raw) => {
|
|
4814
|
+
unsetRawConfigValue(raw, path2);
|
|
4815
|
+
});
|
|
4769
4816
|
}
|
|
4770
4817
|
async upsertWorkspace(name, cwd, description) {
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4818
|
+
assertSafeConfigKey(name);
|
|
4819
|
+
return await this.patchRaw((raw) => {
|
|
4820
|
+
const workspaces = ensureRecordAt(raw, "workspaces");
|
|
4821
|
+
workspaces[name] = {
|
|
4822
|
+
cwd,
|
|
4823
|
+
...description ? { description } : {}
|
|
4824
|
+
};
|
|
4825
|
+
});
|
|
4779
4826
|
}
|
|
4780
4827
|
async removeWorkspace(name) {
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4828
|
+
assertSafeConfigKey(name);
|
|
4829
|
+
return await this.patchRaw((raw) => {
|
|
4830
|
+
deleteRecordEntry(raw, "workspaces", name);
|
|
4831
|
+
});
|
|
4785
4832
|
}
|
|
4786
4833
|
async upsertAgent(name, agent3) {
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4834
|
+
assertSafeConfigKey(name);
|
|
4835
|
+
return await this.patchRaw((raw) => {
|
|
4836
|
+
const agents = ensureRecordAt(raw, "agents");
|
|
4837
|
+
agents[name] = {
|
|
4838
|
+
driver: agent3.driver,
|
|
4839
|
+
...agent3.command ? { command: agent3.command } : {}
|
|
4840
|
+
};
|
|
4841
|
+
});
|
|
4791
4842
|
}
|
|
4792
4843
|
async removeAgent(name) {
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
}
|
|
4798
|
-
async updateTransport(
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
await this.
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
return
|
|
4844
|
+
assertSafeConfigKey(name);
|
|
4845
|
+
return await this.patchRaw((raw) => {
|
|
4846
|
+
deleteRecordEntry(raw, "agents", name);
|
|
4847
|
+
});
|
|
4848
|
+
}
|
|
4849
|
+
async updateTransport(patch) {
|
|
4850
|
+
return await this.patchRaw((raw) => {
|
|
4851
|
+
applyRecordPatch(ensureRecordAt(raw, "transport"), patch);
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4854
|
+
async updateChannel(patch) {
|
|
4855
|
+
return await this.patchRaw((raw) => {
|
|
4856
|
+
applyRecordPatch(ensureRecordAt(raw, "channel"), patch);
|
|
4857
|
+
});
|
|
4858
|
+
}
|
|
4859
|
+
async replacePlugins(plugins) {
|
|
4860
|
+
return await this.patchRaw((raw) => {
|
|
4861
|
+
raw.plugins = clonePlain(plugins);
|
|
4862
|
+
});
|
|
4863
|
+
}
|
|
4864
|
+
async replaceChannels(channels) {
|
|
4865
|
+
return await this.patchRaw((raw) => {
|
|
4866
|
+
raw.channels = clonePlain(channels);
|
|
4867
|
+
});
|
|
4868
|
+
}
|
|
4869
|
+
async patchRaw(mutate) {
|
|
4870
|
+
return await withPrivateFileLock(this.path, async (writeLocked) => {
|
|
4871
|
+
const { raw, existed } = await this.readRaw();
|
|
4872
|
+
const doc = existed ? raw : { transport: {}, agents: {}, workspaces: {} };
|
|
4873
|
+
mutate(doc);
|
|
4874
|
+
const parsed = parseConfig({ transport: {}, agents: {}, workspaces: {}, ...doc });
|
|
4875
|
+
await writeLocked(serializeRawConfig(doc));
|
|
4876
|
+
return parsed;
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4879
|
+
async readRaw() {
|
|
4880
|
+
let text;
|
|
4881
|
+
try {
|
|
4882
|
+
text = await readFile2(this.path, "utf8");
|
|
4883
|
+
} catch (error) {
|
|
4884
|
+
if (isMissingFileError(error)) {
|
|
4885
|
+
return { raw: {}, existed: false };
|
|
4886
|
+
}
|
|
4887
|
+
throw error;
|
|
4888
|
+
}
|
|
4889
|
+
let parsed;
|
|
4890
|
+
try {
|
|
4891
|
+
parsed = JSON.parse(text);
|
|
4892
|
+
} catch (error) {
|
|
4893
|
+
throw new Error(`refusing to modify ${this.path}: it is not valid JSON (${error instanceof Error ? error.message : String(error)}); fix the file and retry`);
|
|
4894
|
+
}
|
|
4895
|
+
if (!isPlainRecord(parsed)) {
|
|
4896
|
+
throw new Error(`refusing to modify ${this.path}: the top level must be a JSON object`);
|
|
4897
|
+
}
|
|
4898
|
+
return { raw: parsed, existed: true };
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
function serializeRawConfig(raw) {
|
|
4902
|
+
return `${JSON.stringify(raw, null, 2)}
|
|
4903
|
+
`;
|
|
4904
|
+
}
|
|
4905
|
+
function readRawConfigValue(root, path2) {
|
|
4906
|
+
const lastKey = requireObjectKeyTail(path2);
|
|
4907
|
+
let container = root;
|
|
4908
|
+
for (const segment of path2.slice(0, -1)) {
|
|
4909
|
+
const next = descendForRead(container, segment);
|
|
4910
|
+
if (!next.ok) {
|
|
4911
|
+
return { present: false };
|
|
4912
|
+
}
|
|
4913
|
+
container = next.value;
|
|
4914
|
+
}
|
|
4915
|
+
if (!isPlainRecord(container) || !Object.hasOwn(container, lastKey)) {
|
|
4916
|
+
return { present: false };
|
|
4917
|
+
}
|
|
4918
|
+
return { present: true, value: container[lastKey] };
|
|
4919
|
+
}
|
|
4920
|
+
function setRawConfigValue(root, path2, value) {
|
|
4921
|
+
const lastKey = requireObjectKeyTail(path2);
|
|
4922
|
+
let container = root;
|
|
4923
|
+
for (let index = 0;index < path2.length - 1; index += 1) {
|
|
4924
|
+
const segment = path2[index];
|
|
4925
|
+
const nextSegment = path2[index + 1];
|
|
4926
|
+
container = descendForWrite(container, segment, typeof nextSegment !== "string", path2);
|
|
4927
|
+
}
|
|
4928
|
+
if (!isPlainRecord(container)) {
|
|
4929
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" does not address an object`);
|
|
4930
|
+
}
|
|
4931
|
+
container[lastKey] = value;
|
|
4932
|
+
}
|
|
4933
|
+
function unsetRawConfigValue(root, path2) {
|
|
4934
|
+
const lastKey = requireObjectKeyTail(path2);
|
|
4935
|
+
let container = root;
|
|
4936
|
+
for (const segment of path2.slice(0, -1)) {
|
|
4937
|
+
const next = descendForRead(container, segment);
|
|
4938
|
+
if (!next.ok) {
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
container = next.value;
|
|
4942
|
+
}
|
|
4943
|
+
if (isPlainRecord(container)) {
|
|
4944
|
+
delete container[lastKey];
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
function descendForRead(container, segment) {
|
|
4948
|
+
if (typeof segment === "string") {
|
|
4949
|
+
assertSafeConfigKey(segment);
|
|
4950
|
+
if (!isPlainRecord(container) || !Object.hasOwn(container, segment)) {
|
|
4951
|
+
return { ok: false };
|
|
4952
|
+
}
|
|
4953
|
+
return { ok: true, value: container[segment] };
|
|
4954
|
+
}
|
|
4955
|
+
if (!Array.isArray(container)) {
|
|
4956
|
+
return { ok: false };
|
|
4957
|
+
}
|
|
4958
|
+
const entry = container.find((item) => isPlainRecord(item) && item.id === segment.id);
|
|
4959
|
+
return entry === undefined ? { ok: false } : { ok: true, value: entry };
|
|
4960
|
+
}
|
|
4961
|
+
function descendForWrite(container, segment, nextIsArrayEntry, path2) {
|
|
4962
|
+
if (typeof segment === "string") {
|
|
4963
|
+
assertSafeConfigKey(segment);
|
|
4964
|
+
if (!isPlainRecord(container)) {
|
|
4965
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" does not address an object at "${segment}"`);
|
|
4966
|
+
}
|
|
4967
|
+
let existing = container[segment];
|
|
4968
|
+
if (existing === undefined) {
|
|
4969
|
+
existing = nextIsArrayEntry ? [] : {};
|
|
4970
|
+
container[segment] = existing;
|
|
4971
|
+
}
|
|
4972
|
+
if (!isPlainRecord(existing) && !Array.isArray(existing)) {
|
|
4973
|
+
throw new Error(`refusing to overwrite config key "${segment}" (path "${describeRawConfigPath(path2)}"): it is not an object`);
|
|
4974
|
+
}
|
|
4975
|
+
return existing;
|
|
4976
|
+
}
|
|
4977
|
+
if (!Array.isArray(container)) {
|
|
4978
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" expects an array before [id=${segment.id}]`);
|
|
4979
|
+
}
|
|
4980
|
+
const entry = container.find((item) => isPlainRecord(item) && item.id === segment.id);
|
|
4981
|
+
if (entry !== undefined) {
|
|
4982
|
+
return entry;
|
|
4983
|
+
}
|
|
4984
|
+
if (!segment.createWith) {
|
|
4985
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" has no entry with id "${segment.id}"`);
|
|
4986
|
+
}
|
|
4987
|
+
const created = clonePlain(segment.createWith);
|
|
4988
|
+
container.push(created);
|
|
4989
|
+
return created;
|
|
4990
|
+
}
|
|
4991
|
+
function requireObjectKeyTail(path2) {
|
|
4992
|
+
const last = path2[path2.length - 1];
|
|
4993
|
+
if (typeof last !== "string") {
|
|
4994
|
+
throw new Error("raw config path must end with an object key");
|
|
4995
|
+
}
|
|
4996
|
+
assertSafeConfigKey(last);
|
|
4997
|
+
return last;
|
|
4998
|
+
}
|
|
4999
|
+
function assertSafeConfigKey(key) {
|
|
5000
|
+
if (PROTOTYPE_POLLUTING_KEYS.has(key)) {
|
|
5001
|
+
throw new Error(`refusing to use unsafe config key "${key}"`);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
function describeRawConfigPath(path2) {
|
|
5005
|
+
return path2.map((segment) => typeof segment === "string" ? segment : `[id=${segment.id}]`).join(".");
|
|
5006
|
+
}
|
|
5007
|
+
function ensureRecordAt(raw, key) {
|
|
5008
|
+
const existing = raw[key];
|
|
5009
|
+
if (existing === undefined) {
|
|
5010
|
+
const created = {};
|
|
5011
|
+
raw[key] = created;
|
|
5012
|
+
return created;
|
|
5013
|
+
}
|
|
5014
|
+
if (!isPlainRecord(existing)) {
|
|
5015
|
+
throw new Error(`refusing to overwrite config key "${key}": it is not a JSON object`);
|
|
5016
|
+
}
|
|
5017
|
+
return existing;
|
|
5018
|
+
}
|
|
5019
|
+
function deleteRecordEntry(raw, section, name) {
|
|
5020
|
+
const record = raw[section];
|
|
5021
|
+
if (isPlainRecord(record)) {
|
|
5022
|
+
delete record[name];
|
|
4815
5023
|
}
|
|
4816
5024
|
}
|
|
5025
|
+
function applyRecordPatch(target, patch) {
|
|
5026
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
5027
|
+
if (value === undefined) {
|
|
5028
|
+
delete target[key];
|
|
5029
|
+
} else {
|
|
5030
|
+
target[key] = value;
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
function clonePlain(value) {
|
|
5035
|
+
return JSON.parse(JSON.stringify(value));
|
|
5036
|
+
}
|
|
5037
|
+
function isPlainRecord(value) {
|
|
5038
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5039
|
+
}
|
|
5040
|
+
function isMissingFileError(error) {
|
|
5041
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5042
|
+
}
|
|
5043
|
+
var PROTOTYPE_POLLUTING_KEYS;
|
|
4817
5044
|
var init_config_store = __esm(() => {
|
|
4818
5045
|
init_private_file();
|
|
4819
5046
|
init_load_config();
|
|
5047
|
+
PROTOTYPE_POLLUTING_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
4820
5048
|
});
|
|
4821
5049
|
|
|
4822
5050
|
// src/config/default-workspace.ts
|
|
@@ -4829,16 +5057,16 @@ var init_default_workspace = __esm(() => {
|
|
|
4829
5057
|
});
|
|
4830
5058
|
|
|
4831
5059
|
// src/config/ensure-config.ts
|
|
4832
|
-
import { readFile as
|
|
5060
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
4833
5061
|
async function ensureConfigExists(path2, options = {}) {
|
|
4834
5062
|
try {
|
|
4835
5063
|
await loadConfig(path2);
|
|
4836
5064
|
} catch (error) {
|
|
4837
|
-
if (!
|
|
5065
|
+
if (!isMissingFileError2(error)) {
|
|
4838
5066
|
throw error;
|
|
4839
5067
|
}
|
|
4840
|
-
const
|
|
4841
|
-
await
|
|
5068
|
+
const seed = await loadDefaultConfigTemplate(options);
|
|
5069
|
+
await writePrivateFileAtomic(path2, serializeRawConfig(seed));
|
|
4842
5070
|
}
|
|
4843
5071
|
}
|
|
4844
5072
|
async function loadDefaultConfigTemplate(options = {}) {
|
|
@@ -4846,7 +5074,7 @@ async function loadDefaultConfigTemplate(options = {}) {
|
|
|
4846
5074
|
try {
|
|
4847
5075
|
return normalizeDefaultConfigTemplate(await options.readDefaultConfigTemplate());
|
|
4848
5076
|
} catch (error) {
|
|
4849
|
-
if (!
|
|
5077
|
+
if (!isMissingFileError2(error)) {
|
|
4850
5078
|
throw error;
|
|
4851
5079
|
}
|
|
4852
5080
|
}
|
|
@@ -4858,10 +5086,10 @@ async function loadDefaultConfigTemplate(options = {}) {
|
|
|
4858
5086
|
let raw;
|
|
4859
5087
|
for (const candidate of candidates) {
|
|
4860
5088
|
try {
|
|
4861
|
-
raw = await
|
|
5089
|
+
raw = await readFile3(candidate, "utf8");
|
|
4862
5090
|
break;
|
|
4863
5091
|
} catch (error) {
|
|
4864
|
-
if (!
|
|
5092
|
+
if (!isMissingFileError2(error)) {
|
|
4865
5093
|
throw error;
|
|
4866
5094
|
}
|
|
4867
5095
|
}
|
|
@@ -4874,7 +5102,14 @@ async function loadDefaultConfigTemplate(options = {}) {
|
|
|
4874
5102
|
function normalizeDefaultConfigTemplate(raw) {
|
|
4875
5103
|
const template = parseConfig(raw);
|
|
4876
5104
|
return {
|
|
4877
|
-
|
|
5105
|
+
transport: {
|
|
5106
|
+
type: template.transport.type,
|
|
5107
|
+
...template.transport.command ? { command: template.transport.command } : {}
|
|
5108
|
+
},
|
|
5109
|
+
channel: {
|
|
5110
|
+
type: template.channel.type,
|
|
5111
|
+
replyMode: template.channel.replyMode
|
|
5112
|
+
},
|
|
4878
5113
|
agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent3]) => [
|
|
4879
5114
|
name,
|
|
4880
5115
|
{
|
|
@@ -4887,32 +5122,18 @@ function normalizeDefaultConfigTemplate(raw) {
|
|
|
4887
5122
|
}
|
|
4888
5123
|
};
|
|
4889
5124
|
}
|
|
4890
|
-
function
|
|
5125
|
+
function isMissingFileError2(error) {
|
|
4891
5126
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4892
5127
|
}
|
|
4893
5128
|
var BUILTIN_DEFAULT_CONFIG_TEMPLATE;
|
|
4894
5129
|
var init_ensure_config = __esm(() => {
|
|
5130
|
+
init_private_file();
|
|
4895
5131
|
init_config_store();
|
|
4896
5132
|
init_default_workspace();
|
|
4897
5133
|
init_load_config();
|
|
4898
5134
|
BUILTIN_DEFAULT_CONFIG_TEMPLATE = {
|
|
4899
5135
|
transport: {
|
|
4900
|
-
type: "acpx-bridge"
|
|
4901
|
-
sessionInitTimeoutMs: 120000,
|
|
4902
|
-
permissionMode: "approve-all",
|
|
4903
|
-
nonInteractivePermissions: "deny"
|
|
4904
|
-
},
|
|
4905
|
-
logging: {
|
|
4906
|
-
level: "info",
|
|
4907
|
-
maxSizeBytes: 2 * 1024 * 1024,
|
|
4908
|
-
maxFiles: 5,
|
|
4909
|
-
retentionDays: 7,
|
|
4910
|
-
perf: {
|
|
4911
|
-
enabled: false,
|
|
4912
|
-
maxSizeBytes: 5242880,
|
|
4913
|
-
maxFiles: 3,
|
|
4914
|
-
retentionDays: 7
|
|
4915
|
-
}
|
|
5136
|
+
type: "acpx-bridge"
|
|
4916
5137
|
},
|
|
4917
5138
|
channel: {
|
|
4918
5139
|
type: "weixin",
|
|
@@ -4924,8 +5145,7 @@ var init_ensure_config = __esm(() => {
|
|
|
4924
5145
|
},
|
|
4925
5146
|
workspaces: {
|
|
4926
5147
|
[DEFAULT_HOME_WORKSPACE_NAME]: { ...DEFAULT_HOME_WORKSPACE }
|
|
4927
|
-
}
|
|
4928
|
-
plugins: []
|
|
5148
|
+
}
|
|
4929
5149
|
};
|
|
4930
5150
|
});
|
|
4931
5151
|
|
|
@@ -5006,7 +5226,7 @@ var init_agent_templates = __esm(() => {
|
|
|
5006
5226
|
});
|
|
5007
5227
|
|
|
5008
5228
|
// src/daemon/daemon-status.ts
|
|
5009
|
-
import { mkdir as mkdir2, readFile as
|
|
5229
|
+
import { mkdir as mkdir2, readFile as readFile4, rm } from "node:fs/promises";
|
|
5010
5230
|
import { dirname as dirname2 } from "node:path";
|
|
5011
5231
|
|
|
5012
5232
|
class DaemonStatusStore {
|
|
@@ -5016,7 +5236,7 @@ class DaemonStatusStore {
|
|
|
5016
5236
|
}
|
|
5017
5237
|
async load() {
|
|
5018
5238
|
try {
|
|
5019
|
-
const content = await
|
|
5239
|
+
const content = await readFile4(this.path, "utf8");
|
|
5020
5240
|
if (content.trim() === "") {
|
|
5021
5241
|
return null;
|
|
5022
5242
|
}
|
|
@@ -5032,18 +5252,37 @@ class DaemonStatusStore {
|
|
|
5032
5252
|
}
|
|
5033
5253
|
}
|
|
5034
5254
|
async save(status) {
|
|
5035
|
-
await mkdir2(dirname2(this.path), { recursive: true });
|
|
5036
|
-
await
|
|
5255
|
+
await mkdir2(dirname2(this.path), { recursive: true, mode: 448 });
|
|
5256
|
+
await import_write_file_atomic2.default(this.path, JSON.stringify(status, null, 2), { encoding: "utf8" });
|
|
5037
5257
|
}
|
|
5038
5258
|
async clear() {
|
|
5039
5259
|
await rm(this.path, { force: true });
|
|
5040
5260
|
}
|
|
5041
5261
|
}
|
|
5042
|
-
var
|
|
5262
|
+
var import_write_file_atomic2;
|
|
5263
|
+
var init_daemon_status = __esm(() => {
|
|
5264
|
+
import_write_file_atomic2 = __toESM(require_lib(), 1);
|
|
5265
|
+
});
|
|
5266
|
+
|
|
5267
|
+
// src/daemon/private-runtime-dir.ts
|
|
5268
|
+
import { chmod as chmod2, mkdir as mkdir3 } from "node:fs/promises";
|
|
5269
|
+
async function ensurePrivateRuntimeDir(runtimeDir, options = {}) {
|
|
5270
|
+
await mkdir3(runtimeDir, { recursive: true, mode: 448 });
|
|
5271
|
+
const platform = options.platform ?? process.platform;
|
|
5272
|
+
if (platform === "win32") {
|
|
5273
|
+
return;
|
|
5274
|
+
}
|
|
5275
|
+
const chmodImpl = options.chmodImpl ?? chmod2;
|
|
5276
|
+
try {
|
|
5277
|
+
await chmodImpl(runtimeDir, 448);
|
|
5278
|
+
} catch (error) {
|
|
5279
|
+
options.onChmodError?.(error);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
var init_private_runtime_dir = () => {};
|
|
5043
5283
|
|
|
5044
5284
|
// src/daemon/daemon-controller.ts
|
|
5045
|
-
import {
|
|
5046
|
-
import { dirname as dirname3 } from "node:path";
|
|
5285
|
+
import { open, readFile as readFile5, rm as rm2 } from "node:fs/promises";
|
|
5047
5286
|
|
|
5048
5287
|
class DaemonController {
|
|
5049
5288
|
paths;
|
|
@@ -5131,7 +5370,7 @@ class DaemonController {
|
|
|
5131
5370
|
}
|
|
5132
5371
|
async loadPid() {
|
|
5133
5372
|
try {
|
|
5134
|
-
const content = await
|
|
5373
|
+
const content = await readFile5(this.paths.pidFile, "utf8");
|
|
5135
5374
|
const pid = Number(content.trim());
|
|
5136
5375
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
5137
5376
|
} catch (error) {
|
|
@@ -5142,7 +5381,7 @@ class DaemonController {
|
|
|
5142
5381
|
}
|
|
5143
5382
|
}
|
|
5144
5383
|
async openPidFileExclusive() {
|
|
5145
|
-
await
|
|
5384
|
+
await ensurePrivateRuntimeDir(this.paths.runtimeDir);
|
|
5146
5385
|
try {
|
|
5147
5386
|
return await open(this.paths.pidFile, "wx", 384);
|
|
5148
5387
|
} catch (error) {
|
|
@@ -5196,6 +5435,7 @@ class DaemonController {
|
|
|
5196
5435
|
}
|
|
5197
5436
|
var init_daemon_controller = __esm(() => {
|
|
5198
5437
|
init_daemon_status();
|
|
5438
|
+
init_private_runtime_dir();
|
|
5199
5439
|
});
|
|
5200
5440
|
|
|
5201
5441
|
// src/process/terminate-process-tree.ts
|
|
@@ -5247,13 +5487,15 @@ async function defaultRunProcessCommand(command, args) {
|
|
|
5247
5487
|
var init_terminate_process_tree = () => {};
|
|
5248
5488
|
|
|
5249
5489
|
// src/daemon/create-daemon-controller.ts
|
|
5250
|
-
import {
|
|
5490
|
+
import { open as open2 } from "node:fs/promises";
|
|
5251
5491
|
import { spawn as spawn2 } from "node:child_process";
|
|
5252
5492
|
function createDaemonController(paths, options) {
|
|
5253
5493
|
return new DaemonController(paths, {
|
|
5254
5494
|
isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning2,
|
|
5255
5495
|
spawnDetached: async (spawnOptions) => {
|
|
5256
|
-
await
|
|
5496
|
+
await ensurePrivateRuntimeDir(paths.runtimeDir, {
|
|
5497
|
+
...options.platform ? { platform: options.platform } : {}
|
|
5498
|
+
});
|
|
5257
5499
|
const stdoutHandle = await open2(paths.stdoutLog, "a", 384);
|
|
5258
5500
|
const stderrHandle = await open2(paths.stderrLog, "a", 384);
|
|
5259
5501
|
await stdoutHandle.chmod(384).catch(() => {});
|
|
@@ -5325,8 +5567,10 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
5325
5567
|
function buildWindowsLauncherScript() {
|
|
5326
5568
|
const script = [
|
|
5327
5569
|
"$env:XACPX_DAEMON_RUN = '1'",
|
|
5570
|
+
`$arg0 = '"' + $env:XACPX_DAEMON_ARG0 + '"'`,
|
|
5571
|
+
`$arg1 = '"' + $env:XACPX_DAEMON_ARG1 + '"'`,
|
|
5328
5572
|
"$process = Start-Process -FilePath $env:XACPX_DAEMON_COMMAND `",
|
|
5329
|
-
" -ArgumentList @($
|
|
5573
|
+
" -ArgumentList @($arg0, $arg1) `",
|
|
5330
5574
|
" -WorkingDirectory $env:XACPX_DAEMON_CWD `",
|
|
5331
5575
|
" -RedirectStandardOutput $env:XACPX_DAEMON_STDOUT `",
|
|
5332
5576
|
" -RedirectStandardError $env:XACPX_DAEMON_STDERR `",
|
|
@@ -5397,6 +5641,7 @@ async function defaultTerminateProcess(pid) {
|
|
|
5397
5641
|
}
|
|
5398
5642
|
var init_create_daemon_controller = __esm(() => {
|
|
5399
5643
|
init_daemon_controller();
|
|
5644
|
+
init_private_runtime_dir();
|
|
5400
5645
|
init_terminate_process_tree();
|
|
5401
5646
|
});
|
|
5402
5647
|
|
|
@@ -5433,7 +5678,7 @@ function encodeOrchestrationRpcResponse(response) {
|
|
|
5433
5678
|
var init_orchestration_ipc = () => {};
|
|
5434
5679
|
|
|
5435
5680
|
// src/daemon/daemon-files.ts
|
|
5436
|
-
import { dirname as
|
|
5681
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
5437
5682
|
function resolveDaemonPaths(options) {
|
|
5438
5683
|
const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join4(coreHomeDir(options.home), "runtime"));
|
|
5439
5684
|
return {
|
|
@@ -5446,7 +5691,7 @@ function resolveDaemonPaths(options) {
|
|
|
5446
5691
|
};
|
|
5447
5692
|
}
|
|
5448
5693
|
function resolveRuntimeDirFromConfigPath(configPath) {
|
|
5449
|
-
return join4(
|
|
5694
|
+
return join4(dirname3(configPath), "runtime");
|
|
5450
5695
|
}
|
|
5451
5696
|
function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
|
|
5452
5697
|
return resolveOrchestrationEndpoint(runtimeDir, platform).path;
|
|
@@ -11959,6 +12204,41 @@ var init_version = __esm(() => {
|
|
|
11959
12204
|
XACPX_CORE_VERSION = readVersion();
|
|
11960
12205
|
});
|
|
11961
12206
|
|
|
12207
|
+
// src/orchestration/endpoint-probe.ts
|
|
12208
|
+
import { createConnection } from "node:net";
|
|
12209
|
+
async function canConnectToEndpoint(path3, timeoutMs) {
|
|
12210
|
+
return await new Promise((resolve) => {
|
|
12211
|
+
const socket = createConnection(path3);
|
|
12212
|
+
let settled = false;
|
|
12213
|
+
let timer;
|
|
12214
|
+
const finish = (result) => {
|
|
12215
|
+
if (settled) {
|
|
12216
|
+
return;
|
|
12217
|
+
}
|
|
12218
|
+
settled = true;
|
|
12219
|
+
if (timer) {
|
|
12220
|
+
clearTimeout(timer);
|
|
12221
|
+
}
|
|
12222
|
+
socket.destroy();
|
|
12223
|
+
resolve(result);
|
|
12224
|
+
};
|
|
12225
|
+
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
12226
|
+
timer = setTimeout(() => finish(true), timeoutMs);
|
|
12227
|
+
timer.unref?.();
|
|
12228
|
+
}
|
|
12229
|
+
socket.once("connect", () => finish(true));
|
|
12230
|
+
socket.once("error", (error2) => {
|
|
12231
|
+
const code = error2.code;
|
|
12232
|
+
if (code === "ENOENT" || code === "ECONNREFUSED") {
|
|
12233
|
+
finish(false);
|
|
12234
|
+
return;
|
|
12235
|
+
}
|
|
12236
|
+
finish(true);
|
|
12237
|
+
});
|
|
12238
|
+
});
|
|
12239
|
+
}
|
|
12240
|
+
var init_endpoint_probe = () => {};
|
|
12241
|
+
|
|
11962
12242
|
// src/orchestration/task-watch-timeouts.ts
|
|
11963
12243
|
var DEFAULT_TASK_WATCH_TIMEOUT_MS = 60000, MAX_TASK_WATCH_TIMEOUT_MS, DEFAULT_TASK_WATCH_POLL_INTERVAL_MS = 1000, MAX_TASK_WATCH_POLL_INTERVAL_MS = 1e4, TASK_WATCH_RPC_TIMEOUT_PADDING_MS = 5000;
|
|
11964
12244
|
var init_task_watch_timeouts = __esm(() => {
|
|
@@ -12014,6 +12294,14 @@ function escapeRegExp(s) {
|
|
|
12014
12294
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12015
12295
|
}
|
|
12016
12296
|
|
|
12297
|
+
// src/orchestration/coordinator-identity.ts
|
|
12298
|
+
function stableCoordinatorSession(transportSession) {
|
|
12299
|
+
return transportSession.replace(/:reset-\d+$/, "");
|
|
12300
|
+
}
|
|
12301
|
+
function sameCoordinatorSession(a, b) {
|
|
12302
|
+
return stableCoordinatorSession(a) === stableCoordinatorSession(b);
|
|
12303
|
+
}
|
|
12304
|
+
|
|
12017
12305
|
// src/util/text.ts
|
|
12018
12306
|
function truncateText(text, maxLength, ellipsis = "…") {
|
|
12019
12307
|
if (text.length <= maxLength)
|
|
@@ -12083,10 +12371,24 @@ function createEmptyState() {
|
|
|
12083
12371
|
var init_types = () => {};
|
|
12084
12372
|
|
|
12085
12373
|
// src/state/state-store.ts
|
|
12086
|
-
import { readFile as
|
|
12374
|
+
import { readFile as readFile6, rename, writeFile as writeFile3 } from "node:fs/promises";
|
|
12087
12375
|
function isRecord2(value) {
|
|
12088
12376
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12089
12377
|
}
|
|
12378
|
+
function sectionRecord(value, section, dropped) {
|
|
12379
|
+
if (value === undefined) {
|
|
12380
|
+
return {};
|
|
12381
|
+
}
|
|
12382
|
+
if (!isRecord2(value)) {
|
|
12383
|
+
dropped.push({
|
|
12384
|
+
section,
|
|
12385
|
+
key: "",
|
|
12386
|
+
reason: `field "${section}" is not an object; reset to empty`
|
|
12387
|
+
});
|
|
12388
|
+
return {};
|
|
12389
|
+
}
|
|
12390
|
+
return value;
|
|
12391
|
+
}
|
|
12090
12392
|
function isString(value) {
|
|
12091
12393
|
return typeof value === "string";
|
|
12092
12394
|
}
|
|
@@ -12191,73 +12493,82 @@ function isHumanQuestionPackageRecord(value) {
|
|
|
12191
12493
|
const messages = value.messages;
|
|
12192
12494
|
return isString(value.packageId) && isString(value.coordinatorSession) && (value.status === "active" || value.status === "closed") && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.closedAt) && Array.isArray(initialTaskIds) && initialTaskIds.every(isString) && Array.isArray(openTaskIds) && openTaskIds.every(isString) && Array.isArray(resolvedTaskIds) && resolvedTaskIds.every(isString) && Array.isArray(messages) && messages.every(isHumanQuestionPackageMessageRecord) && isOptionalString(value.awaitingReplyMessageId);
|
|
12193
12495
|
}
|
|
12194
|
-
function parseOrchestrationState(raw,
|
|
12496
|
+
function parseOrchestrationState(raw, dropped) {
|
|
12195
12497
|
if (raw === undefined) {
|
|
12196
12498
|
return createEmptyOrchestrationState();
|
|
12197
12499
|
}
|
|
12198
12500
|
if (!isRecord2(raw)) {
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
const workerBindings = raw.workerBindings;
|
|
12206
|
-
if (workerBindings !== undefined && !isRecord2(workerBindings)) {
|
|
12207
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.workerBindings"`);
|
|
12208
|
-
}
|
|
12209
|
-
const groups = raw.groups;
|
|
12210
|
-
if (groups !== undefined && !isRecord2(groups)) {
|
|
12211
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.groups"`);
|
|
12212
|
-
}
|
|
12213
|
-
const humanQuestionPackages = raw.humanQuestionPackages;
|
|
12214
|
-
if (humanQuestionPackages !== undefined && !isRecord2(humanQuestionPackages)) {
|
|
12215
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.humanQuestionPackages"`);
|
|
12216
|
-
}
|
|
12217
|
-
const coordinatorQuestionState = raw.coordinatorQuestionState;
|
|
12218
|
-
if (coordinatorQuestionState !== undefined && !isRecord2(coordinatorQuestionState)) {
|
|
12219
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.coordinatorQuestionState"`);
|
|
12220
|
-
}
|
|
12221
|
-
const coordinatorRoutes = raw.coordinatorRoutes;
|
|
12222
|
-
if (coordinatorRoutes !== undefined && !isRecord2(coordinatorRoutes)) {
|
|
12223
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.coordinatorRoutes"`);
|
|
12224
|
-
}
|
|
12225
|
-
const externalCoordinators = raw.externalCoordinators;
|
|
12226
|
-
if (externalCoordinators !== undefined && !isRecord2(externalCoordinators)) {
|
|
12227
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.externalCoordinators"`);
|
|
12501
|
+
dropped.push({
|
|
12502
|
+
section: "orchestration",
|
|
12503
|
+
key: "",
|
|
12504
|
+
reason: 'field "orchestration" is not an object; reset to empty'
|
|
12505
|
+
});
|
|
12506
|
+
return createEmptyOrchestrationState();
|
|
12228
12507
|
}
|
|
12508
|
+
const tasks = sectionRecord(raw.tasks, "orchestration.tasks", dropped);
|
|
12509
|
+
const workerBindings = sectionRecord(raw.workerBindings, "orchestration.workerBindings", dropped);
|
|
12510
|
+
const groups = sectionRecord(raw.groups, "orchestration.groups", dropped);
|
|
12511
|
+
const humanQuestionPackages = sectionRecord(raw.humanQuestionPackages, "orchestration.humanQuestionPackages", dropped);
|
|
12512
|
+
const coordinatorQuestionState = sectionRecord(raw.coordinatorQuestionState, "orchestration.coordinatorQuestionState", dropped);
|
|
12513
|
+
const coordinatorRoutes = sectionRecord(raw.coordinatorRoutes, "orchestration.coordinatorRoutes", dropped);
|
|
12514
|
+
const externalCoordinators = sectionRecord(raw.externalCoordinators, "orchestration.externalCoordinators", dropped);
|
|
12229
12515
|
const parsedTasks = {};
|
|
12230
|
-
for (const [taskId, task] of Object.entries(tasks
|
|
12516
|
+
for (const [taskId, task] of Object.entries(tasks)) {
|
|
12231
12517
|
if (!isTaskRecord(task)) {
|
|
12232
|
-
|
|
12518
|
+
dropped.push({
|
|
12519
|
+
section: "orchestration.tasks",
|
|
12520
|
+
key: taskId,
|
|
12521
|
+
reason: "malformed orchestration task record"
|
|
12522
|
+
});
|
|
12523
|
+
continue;
|
|
12233
12524
|
}
|
|
12234
12525
|
parsedTasks[taskId] = task;
|
|
12235
12526
|
}
|
|
12236
12527
|
const parsedWorkerBindings = {};
|
|
12237
|
-
for (const [workerSession, binding] of Object.entries(workerBindings
|
|
12528
|
+
for (const [workerSession, binding] of Object.entries(workerBindings)) {
|
|
12238
12529
|
if (!isWorkerBindingRecord(binding)) {
|
|
12239
|
-
|
|
12530
|
+
dropped.push({
|
|
12531
|
+
section: "orchestration.workerBindings",
|
|
12532
|
+
key: workerSession,
|
|
12533
|
+
reason: "malformed orchestration worker binding record"
|
|
12534
|
+
});
|
|
12535
|
+
continue;
|
|
12240
12536
|
}
|
|
12241
12537
|
parsedWorkerBindings[workerSession] = binding;
|
|
12242
12538
|
}
|
|
12243
12539
|
const parsedGroups = {};
|
|
12244
|
-
for (const [groupId, group] of Object.entries(groups
|
|
12540
|
+
for (const [groupId, group] of Object.entries(groups)) {
|
|
12245
12541
|
if (!isGroupRecord(group)) {
|
|
12246
|
-
|
|
12542
|
+
dropped.push({
|
|
12543
|
+
section: "orchestration.groups",
|
|
12544
|
+
key: groupId,
|
|
12545
|
+
reason: "malformed orchestration group record"
|
|
12546
|
+
});
|
|
12547
|
+
continue;
|
|
12247
12548
|
}
|
|
12248
12549
|
parsedGroups[groupId] = group;
|
|
12249
12550
|
}
|
|
12250
12551
|
const parsedHumanQuestionPackages = {};
|
|
12251
|
-
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages
|
|
12552
|
+
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages)) {
|
|
12252
12553
|
if (!isHumanQuestionPackageRecord(packageRecord)) {
|
|
12253
|
-
|
|
12554
|
+
dropped.push({
|
|
12555
|
+
section: "orchestration.humanQuestionPackages",
|
|
12556
|
+
key: packageId,
|
|
12557
|
+
reason: "malformed human question package record"
|
|
12558
|
+
});
|
|
12559
|
+
continue;
|
|
12254
12560
|
}
|
|
12255
12561
|
parsedHumanQuestionPackages[packageId] = packageRecord;
|
|
12256
12562
|
}
|
|
12257
12563
|
const parsedCoordinatorQuestionState = {};
|
|
12258
|
-
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState
|
|
12564
|
+
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState)) {
|
|
12259
12565
|
if (!isCoordinatorQuestionStateRecord(questionState)) {
|
|
12260
|
-
|
|
12566
|
+
dropped.push({
|
|
12567
|
+
section: "orchestration.coordinatorQuestionState",
|
|
12568
|
+
key: coordinatorSession,
|
|
12569
|
+
reason: "malformed coordinator question state record"
|
|
12570
|
+
});
|
|
12571
|
+
continue;
|
|
12261
12572
|
}
|
|
12262
12573
|
parsedCoordinatorQuestionState[coordinatorSession] = {
|
|
12263
12574
|
activePackageId: questionState.activePackageId,
|
|
@@ -12265,19 +12576,34 @@ function parseOrchestrationState(raw, path3) {
|
|
|
12265
12576
|
};
|
|
12266
12577
|
}
|
|
12267
12578
|
const parsedCoordinatorRoutes = {};
|
|
12268
|
-
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes
|
|
12579
|
+
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes)) {
|
|
12269
12580
|
if (!isCoordinatorRouteContextRecord(route)) {
|
|
12270
|
-
|
|
12581
|
+
dropped.push({
|
|
12582
|
+
section: "orchestration.coordinatorRoutes",
|
|
12583
|
+
key: coordinatorSession,
|
|
12584
|
+
reason: "malformed coordinator route record"
|
|
12585
|
+
});
|
|
12586
|
+
continue;
|
|
12271
12587
|
}
|
|
12272
12588
|
parsedCoordinatorRoutes[coordinatorSession] = route;
|
|
12273
12589
|
}
|
|
12274
12590
|
const parsedExternalCoordinators = {};
|
|
12275
|
-
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators
|
|
12591
|
+
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators)) {
|
|
12276
12592
|
if (!isExternalCoordinatorRecord(externalCoordinator)) {
|
|
12277
|
-
|
|
12593
|
+
dropped.push({
|
|
12594
|
+
section: "orchestration.externalCoordinators",
|
|
12595
|
+
key: coordinatorSession,
|
|
12596
|
+
reason: "malformed external coordinator record"
|
|
12597
|
+
});
|
|
12598
|
+
continue;
|
|
12278
12599
|
}
|
|
12279
12600
|
if (externalCoordinator.coordinatorSession !== coordinatorSession) {
|
|
12280
|
-
|
|
12601
|
+
dropped.push({
|
|
12602
|
+
section: "orchestration.externalCoordinators",
|
|
12603
|
+
key: coordinatorSession,
|
|
12604
|
+
reason: `coordinatorSession "${externalCoordinator.coordinatorSession}" does not match map key`
|
|
12605
|
+
});
|
|
12606
|
+
continue;
|
|
12281
12607
|
}
|
|
12282
12608
|
parsedExternalCoordinators[coordinatorSession] = externalCoordinator;
|
|
12283
12609
|
}
|
|
@@ -12303,11 +12629,12 @@ function isSessionRecord(value) {
|
|
|
12303
12629
|
}
|
|
12304
12630
|
return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isSessionSource(value.source) && isOptionalString(value.agent_session_id) && isOptionalString(value.agent_session_title) && isOptionalString(value.agent_session_updated_at) && isOptionalString(value.attached_at) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode2(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
|
|
12305
12631
|
}
|
|
12306
|
-
function parseSessions(raw,
|
|
12632
|
+
function parseSessions(raw, dropped) {
|
|
12307
12633
|
const sessions = {};
|
|
12308
12634
|
for (const [alias, value] of Object.entries(raw)) {
|
|
12309
12635
|
if (!isSessionRecord(value)) {
|
|
12310
|
-
|
|
12636
|
+
dropped.push({ section: "sessions", key: alias, reason: "malformed session record" });
|
|
12637
|
+
continue;
|
|
12311
12638
|
}
|
|
12312
12639
|
sessions[alias] = value;
|
|
12313
12640
|
}
|
|
@@ -12316,11 +12643,12 @@ function parseSessions(raw, path3) {
|
|
|
12316
12643
|
function isChatContextRecord(value) {
|
|
12317
12644
|
return isRecord2(value) && isString(value.current_session);
|
|
12318
12645
|
}
|
|
12319
|
-
function parseChatContexts(raw,
|
|
12646
|
+
function parseChatContexts(raw, dropped) {
|
|
12320
12647
|
const chatContexts = {};
|
|
12321
12648
|
for (const [chatKey, value] of Object.entries(raw)) {
|
|
12322
12649
|
if (!isChatContextRecord(value)) {
|
|
12323
|
-
|
|
12650
|
+
dropped.push({ section: "chat_contexts", key: chatKey, reason: "malformed chat context record" });
|
|
12651
|
+
continue;
|
|
12324
12652
|
}
|
|
12325
12653
|
chatContexts[chatKey] = value;
|
|
12326
12654
|
}
|
|
@@ -12361,56 +12689,58 @@ function isScheduledTaskRecord(value) {
|
|
|
12361
12689
|
return false;
|
|
12362
12690
|
return isString(value.id) && isString(value.chat_key) && isString(value.session_alias) && isString(value.execute_at) && isString(value.message) && isScheduledTaskStatus(value.status) && isString(value.created_at) && isOptionalString(value.account_id) && isOptionalString(value.reply_context_token) && isOptionalString(value.source_label) && isOptionalString(value.triggered_at) && isOptionalString(value.executed_at) && isOptionalString(value.cancelled_at) && isOptionalString(value.missed_at) && isOptionalString(value.failed_at) && isOptionalString(value.last_error) && isOptionalScheduledSessionMode(value.session_mode) && isOptionalString(value.agent) && isOptionalString(value.workspace);
|
|
12363
12691
|
}
|
|
12364
|
-
function parseScheduledTasks(raw,
|
|
12365
|
-
|
|
12366
|
-
return {};
|
|
12367
|
-
if (!isRecord2(raw)) {
|
|
12368
|
-
throw new Error(`state file "${path3}" must contain an object field "scheduled_tasks"`);
|
|
12369
|
-
}
|
|
12692
|
+
function parseScheduledTasks(raw, dropped) {
|
|
12693
|
+
const source = sectionRecord(raw, "scheduled_tasks", dropped);
|
|
12370
12694
|
const tasks = {};
|
|
12371
|
-
for (const [id, value] of Object.entries(
|
|
12695
|
+
for (const [id, value] of Object.entries(source)) {
|
|
12372
12696
|
if (!isScheduledTaskRecord(value) || value.id !== id) {
|
|
12373
|
-
|
|
12697
|
+
dropped.push({ section: "scheduled_tasks", key: id, reason: "malformed scheduled task record" });
|
|
12698
|
+
continue;
|
|
12374
12699
|
}
|
|
12375
12700
|
tasks[id] = value;
|
|
12376
12701
|
}
|
|
12377
12702
|
return tasks;
|
|
12378
12703
|
}
|
|
12379
|
-
function parseState(raw, path3) {
|
|
12704
|
+
function parseState(raw, path3, dropped = []) {
|
|
12380
12705
|
if (!isRecord2(raw)) {
|
|
12381
12706
|
throw new Error(`state file "${path3}" must contain a JSON object`);
|
|
12382
12707
|
}
|
|
12383
|
-
const
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
}
|
|
12387
|
-
const chatContexts = raw.chat_contexts;
|
|
12388
|
-
if (!isRecord2(chatContexts)) {
|
|
12389
|
-
throw new Error(`state file "${path3}" must contain an object field "chat_contexts"`);
|
|
12390
|
-
}
|
|
12391
|
-
const parsedSessions = parseSessions(sessions, path3);
|
|
12392
|
-
const orchestration3 = parseOrchestrationState(raw.orchestration, path3);
|
|
12393
|
-
validateExternalCoordinatorIdentityCollisions(parsedSessions, orchestration3, path3);
|
|
12708
|
+
const parsedSessions = parseSessions(sectionRecord(raw.sessions, "sessions", dropped), dropped);
|
|
12709
|
+
const orchestration3 = parseOrchestrationState(raw.orchestration, dropped);
|
|
12710
|
+
repairExternalCoordinatorIdentityCollisions(parsedSessions, orchestration3, dropped);
|
|
12394
12711
|
return {
|
|
12395
12712
|
sessions: parsedSessions,
|
|
12396
|
-
chat_contexts: parseChatContexts(
|
|
12713
|
+
chat_contexts: parseChatContexts(sectionRecord(raw.chat_contexts, "chat_contexts", dropped), dropped),
|
|
12397
12714
|
native_session_lists: parseNativeSessionLists(raw.native_session_lists),
|
|
12398
12715
|
orchestration: orchestration3,
|
|
12399
|
-
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks,
|
|
12716
|
+
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks, dropped)
|
|
12400
12717
|
};
|
|
12401
12718
|
}
|
|
12402
|
-
function
|
|
12719
|
+
function repairExternalCoordinatorIdentityCollisions(sessions, orchestration3, dropped) {
|
|
12403
12720
|
for (const coordinatorSession of Object.keys(orchestration3.externalCoordinators)) {
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
if (orchestration3.workerBindings[coordinatorSession]) {
|
|
12408
|
-
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with a worker binding`);
|
|
12409
|
-
}
|
|
12410
|
-
if (Object.values(orchestration3.tasks).some((task) => task.workerSession === coordinatorSession && (!isTerminalTaskStatus(task.status) || task.reviewPending !== undefined))) {
|
|
12411
|
-
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with an active task worker session`);
|
|
12721
|
+
const conflict = findExternalCoordinatorConflict(coordinatorSession, sessions, orchestration3);
|
|
12722
|
+
if (!conflict) {
|
|
12723
|
+
continue;
|
|
12412
12724
|
}
|
|
12725
|
+
delete orchestration3.externalCoordinators[coordinatorSession];
|
|
12726
|
+
dropped.push({
|
|
12727
|
+
section: "orchestration.externalCoordinators",
|
|
12728
|
+
key: coordinatorSession,
|
|
12729
|
+
reason: `conflicts with ${conflict}; dropped (re-registered on next coordinator connect)`
|
|
12730
|
+
});
|
|
12731
|
+
}
|
|
12732
|
+
}
|
|
12733
|
+
function findExternalCoordinatorConflict(coordinatorSession, sessions, orchestration3) {
|
|
12734
|
+
if (Object.values(sessions).some((session3) => session3.transport_session === coordinatorSession)) {
|
|
12735
|
+
return "a logical session";
|
|
12413
12736
|
}
|
|
12737
|
+
if (orchestration3.workerBindings[coordinatorSession]) {
|
|
12738
|
+
return "a worker binding";
|
|
12739
|
+
}
|
|
12740
|
+
if (Object.values(orchestration3.tasks).some((task) => task.workerSession === coordinatorSession && (!isTerminalTaskStatus(task.status) || task.reviewPending !== undefined))) {
|
|
12741
|
+
return "an active task worker session";
|
|
12742
|
+
}
|
|
12743
|
+
return null;
|
|
12414
12744
|
}
|
|
12415
12745
|
function isTerminalTaskStatus(status) {
|
|
12416
12746
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
@@ -12418,35 +12748,118 @@ function isTerminalTaskStatus(status) {
|
|
|
12418
12748
|
|
|
12419
12749
|
class StateStore {
|
|
12420
12750
|
path;
|
|
12421
|
-
|
|
12751
|
+
options;
|
|
12752
|
+
loadReport = null;
|
|
12753
|
+
constructor(path3, options = {}) {
|
|
12422
12754
|
this.path = path3;
|
|
12755
|
+
this.options = options;
|
|
12756
|
+
}
|
|
12757
|
+
get lastLoadReport() {
|
|
12758
|
+
return this.loadReport;
|
|
12423
12759
|
}
|
|
12424
12760
|
async load() {
|
|
12761
|
+
this.loadReport = null;
|
|
12762
|
+
const read = await this.readAndParse();
|
|
12763
|
+
if (read.kind === "absent") {
|
|
12764
|
+
return createEmptyState();
|
|
12765
|
+
}
|
|
12766
|
+
if (read.kind === "corrupt") {
|
|
12767
|
+
return await this.recoverFromCorruptFile(read.reason);
|
|
12768
|
+
}
|
|
12769
|
+
if (read.dropped.length === 0) {
|
|
12770
|
+
return read.state;
|
|
12771
|
+
}
|
|
12772
|
+
const report = { dropped: read.dropped };
|
|
12773
|
+
const quarantinePath = `${this.path}.quarantine-${this.fileTimestamp()}`;
|
|
12425
12774
|
try {
|
|
12426
|
-
const
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
}
|
|
12438
|
-
|
|
12775
|
+
const written = await (this.options.writeBackup ?? defaultWriteBackup)(quarantinePath, read.content);
|
|
12776
|
+
report.quarantinePath = typeof written === "string" ? written : quarantinePath;
|
|
12777
|
+
} catch (error2) {
|
|
12778
|
+
report.backupError = error2 instanceof Error ? error2.message : String(error2);
|
|
12779
|
+
}
|
|
12780
|
+
this.loadReport = report;
|
|
12781
|
+
return read.state;
|
|
12782
|
+
}
|
|
12783
|
+
async inspect() {
|
|
12784
|
+
const read = await this.readAndParse();
|
|
12785
|
+
if (read.kind === "absent") {
|
|
12786
|
+
return { state: createEmptyState(), report: null };
|
|
12787
|
+
}
|
|
12788
|
+
if (read.kind === "corrupt") {
|
|
12789
|
+
return {
|
|
12790
|
+
state: createEmptyState(),
|
|
12791
|
+
report: { dropped: [{ section: "file", key: this.path, reason: read.reason }] }
|
|
12792
|
+
};
|
|
12793
|
+
}
|
|
12794
|
+
return {
|
|
12795
|
+
state: read.state,
|
|
12796
|
+
report: read.dropped.length > 0 ? { dropped: read.dropped } : null
|
|
12797
|
+
};
|
|
12798
|
+
}
|
|
12799
|
+
async readAndParse() {
|
|
12800
|
+
let content;
|
|
12801
|
+
try {
|
|
12802
|
+
content = await readFile6(this.path, "utf8");
|
|
12439
12803
|
} catch (error2) {
|
|
12440
12804
|
if (error2.code === "ENOENT") {
|
|
12441
|
-
return
|
|
12805
|
+
return { kind: "absent" };
|
|
12442
12806
|
}
|
|
12443
12807
|
throw error2;
|
|
12444
12808
|
}
|
|
12809
|
+
if (content.trim() === "") {
|
|
12810
|
+
return { kind: "absent" };
|
|
12811
|
+
}
|
|
12812
|
+
let parsed;
|
|
12813
|
+
try {
|
|
12814
|
+
parsed = JSON.parse(content);
|
|
12815
|
+
} catch (error2) {
|
|
12816
|
+
return {
|
|
12817
|
+
kind: "corrupt",
|
|
12818
|
+
reason: `invalid JSON: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
12819
|
+
};
|
|
12820
|
+
}
|
|
12821
|
+
if (!isRecord2(parsed)) {
|
|
12822
|
+
return { kind: "corrupt", reason: "top-level value is not an object" };
|
|
12823
|
+
}
|
|
12824
|
+
const dropped = [];
|
|
12825
|
+
return { kind: "parsed", state: parseState(parsed, this.path, dropped), dropped, content };
|
|
12445
12826
|
}
|
|
12446
12827
|
async save(state) {
|
|
12447
|
-
await writePrivateFileAtomic(this.path, JSON.stringify(state, null, 2));
|
|
12828
|
+
await writePrivateFileAtomic(this.path, JSON.stringify({ version: STATE_FILE_VERSION, ...state }, null, 2));
|
|
12829
|
+
}
|
|
12830
|
+
fileTimestamp() {
|
|
12831
|
+
const now = this.options.now?.() ?? new Date;
|
|
12832
|
+
return now.toISOString().replace(/[:.]/g, "-");
|
|
12833
|
+
}
|
|
12834
|
+
async recoverFromCorruptFile(reason) {
|
|
12835
|
+
const corruptPath = `${this.path}.corrupt-${this.fileTimestamp()}`;
|
|
12836
|
+
const report = {
|
|
12837
|
+
dropped: [{ section: "file", key: this.path, reason }]
|
|
12838
|
+
};
|
|
12839
|
+
try {
|
|
12840
|
+
await rename(this.path, corruptPath);
|
|
12841
|
+
report.corruptPath = corruptPath;
|
|
12842
|
+
} catch (error2) {
|
|
12843
|
+
report.backupError = error2 instanceof Error ? error2.message : String(error2);
|
|
12844
|
+
}
|
|
12845
|
+
this.loadReport = report;
|
|
12846
|
+
return createEmptyState();
|
|
12847
|
+
}
|
|
12848
|
+
}
|
|
12849
|
+
async function defaultWriteBackup(targetPath, content) {
|
|
12850
|
+
for (let attempt = 0;; attempt += 1) {
|
|
12851
|
+
const candidate = attempt === 0 ? targetPath : `${targetPath}-${attempt}`;
|
|
12852
|
+
try {
|
|
12853
|
+
await writeFile3(candidate, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
12854
|
+
return candidate;
|
|
12855
|
+
} catch (error2) {
|
|
12856
|
+
if (error2.code !== "EEXIST" || attempt >= 9) {
|
|
12857
|
+
throw error2;
|
|
12858
|
+
}
|
|
12859
|
+
}
|
|
12448
12860
|
}
|
|
12449
12861
|
}
|
|
12862
|
+
var STATE_FILE_VERSION = 1;
|
|
12450
12863
|
var init_state_store = __esm(() => {
|
|
12451
12864
|
init_private_file();
|
|
12452
12865
|
init_types();
|
|
@@ -12685,14 +13098,23 @@ class ScheduledTaskService {
|
|
|
12685
13098
|
return task;
|
|
12686
13099
|
});
|
|
12687
13100
|
}
|
|
12688
|
-
listPending() {
|
|
13101
|
+
listPending(chatKey) {
|
|
13102
|
+
return this.listPendingAllChats().filter((task) => task.chat_key === chatKey);
|
|
13103
|
+
}
|
|
13104
|
+
listPendingAllChats() {
|
|
12689
13105
|
return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
|
|
12690
13106
|
}
|
|
12691
|
-
async cancelPending(inputId) {
|
|
13107
|
+
async cancelPending(inputId, chatKey) {
|
|
13108
|
+
return await this.cancelPendingWhere(inputId, (task) => task.chat_key === chatKey);
|
|
13109
|
+
}
|
|
13110
|
+
async cancelPendingAnyChat(inputId) {
|
|
13111
|
+
return await this.cancelPendingWhere(inputId, () => true);
|
|
13112
|
+
}
|
|
13113
|
+
async cancelPendingWhere(inputId, allowed) {
|
|
12692
13114
|
return await this.mutate(async () => {
|
|
12693
13115
|
const id = normalizeId(inputId);
|
|
12694
13116
|
const task = this.state.scheduled_tasks[id];
|
|
12695
|
-
if (!task || task.status !== "pending")
|
|
13117
|
+
if (!task || task.status !== "pending" || !allowed(task))
|
|
12696
13118
|
return false;
|
|
12697
13119
|
task.status = "cancelled";
|
|
12698
13120
|
task.cancelled_at = this.now().toISOString();
|
|
@@ -12724,7 +13146,7 @@ class ScheduledTaskService {
|
|
|
12724
13146
|
async claimDueTasks() {
|
|
12725
13147
|
return await this.mutate(async () => {
|
|
12726
13148
|
const nowMs = this.now().getTime();
|
|
12727
|
-
const due = this.
|
|
13149
|
+
const due = this.listPendingAllChats().filter((task) => Date.parse(task.execute_at) <= nowMs);
|
|
12728
13150
|
if (due.length === 0)
|
|
12729
13151
|
return [];
|
|
12730
13152
|
const at = this.now().toISOString();
|
|
@@ -12733,7 +13155,16 @@ class ScheduledTaskService {
|
|
|
12733
13155
|
task.triggered_at = at;
|
|
12734
13156
|
this.claimedInThisSession.add(task.id);
|
|
12735
13157
|
}
|
|
12736
|
-
|
|
13158
|
+
try {
|
|
13159
|
+
await this.save();
|
|
13160
|
+
} catch (error2) {
|
|
13161
|
+
for (const task of due) {
|
|
13162
|
+
task.status = "pending";
|
|
13163
|
+
delete task.triggered_at;
|
|
13164
|
+
this.claimedInThisSession.delete(task.id);
|
|
13165
|
+
}
|
|
13166
|
+
throw error2;
|
|
13167
|
+
}
|
|
12737
13168
|
return due.map((task) => ({ ...task }));
|
|
12738
13169
|
});
|
|
12739
13170
|
}
|
|
@@ -12791,20 +13222,20 @@ var init_scheduled_service = () => {};
|
|
|
12791
13222
|
|
|
12792
13223
|
// src/plugins/plugin-home.ts
|
|
12793
13224
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
12794
|
-
import { copyFile, mkdir as
|
|
13225
|
+
import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
12795
13226
|
import { homedir as homedir3 } from "node:os";
|
|
12796
|
-
import { dirname as
|
|
13227
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
12797
13228
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12798
13229
|
function resolveCoreRoot() {
|
|
12799
13230
|
try {
|
|
12800
|
-
let dir =
|
|
13231
|
+
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
12801
13232
|
for (let depth = 0;depth < 12; depth++) {
|
|
12802
13233
|
try {
|
|
12803
13234
|
const pkg = JSON.parse(readFileSync2(join6(dir, "package.json"), "utf-8"));
|
|
12804
13235
|
if (pkg.name && CORE_ROOT_NAMES.includes(pkg.name))
|
|
12805
13236
|
return dir;
|
|
12806
13237
|
} catch {}
|
|
12807
|
-
const parent =
|
|
13238
|
+
const parent = dirname4(dir);
|
|
12808
13239
|
if (parent === dir)
|
|
12809
13240
|
break;
|
|
12810
13241
|
dir = parent;
|
|
@@ -12822,7 +13253,7 @@ async function ensureCoreResolution(pluginHome) {
|
|
|
12822
13253
|
for (const name of SHIM_SPECIFIERS) {
|
|
12823
13254
|
const targetDir = join6(pluginHome, "node_modules", name);
|
|
12824
13255
|
const dstJs = join6(targetDir, "plugin-api.js");
|
|
12825
|
-
await
|
|
13256
|
+
await mkdir4(targetDir, { recursive: true });
|
|
12826
13257
|
try {
|
|
12827
13258
|
await copyFile(srcJs, dstJs);
|
|
12828
13259
|
} catch (error2) {
|
|
@@ -12865,7 +13296,7 @@ async function normalizePluginHomeManifest(pluginHome) {
|
|
|
12865
13296
|
const manifestPath = join6(pluginHome, "package.json");
|
|
12866
13297
|
let raw;
|
|
12867
13298
|
try {
|
|
12868
|
-
raw = await
|
|
13299
|
+
raw = await readFile7(manifestPath, "utf8");
|
|
12869
13300
|
} catch {
|
|
12870
13301
|
return false;
|
|
12871
13302
|
}
|
|
@@ -12883,7 +13314,7 @@ async function normalizePluginHomeManifest(pluginHome) {
|
|
|
12883
13314
|
return true;
|
|
12884
13315
|
}
|
|
12885
13316
|
async function ensurePluginHome(pluginHome) {
|
|
12886
|
-
await
|
|
13317
|
+
await mkdir4(pluginHome, { recursive: true, mode: 448 });
|
|
12887
13318
|
await writeFile4(join6(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
|
|
12888
13319
|
`, { flag: "wx" }).catch((error2) => {
|
|
12889
13320
|
if (error2.code !== "EEXIST")
|
|
@@ -15499,7 +15930,7 @@ function createConversationExecutor() {
|
|
|
15499
15930
|
var DEFAULT_SESSION_KEY = "__chat__";
|
|
15500
15931
|
|
|
15501
15932
|
// src/channels/media-store.ts
|
|
15502
|
-
import { access as access2, mkdir as
|
|
15933
|
+
import { access as access2, mkdir as mkdir5, readdir, rm as rm5, stat, writeFile as writeFile5 } from "node:fs/promises";
|
|
15503
15934
|
import path7 from "node:path";
|
|
15504
15935
|
|
|
15505
15936
|
class RuntimeMediaStore {
|
|
@@ -15517,7 +15948,7 @@ class RuntimeMediaStore {
|
|
|
15517
15948
|
const safeMessageId = safePathSegment(input.messageId || "message");
|
|
15518
15949
|
const baseFileName = sanitizeMediaFileName(input.fileName ?? "attachment", input.mimeType);
|
|
15519
15950
|
const dir = path7.join(this.rootDir, input.channelId, safeChatKey, safeMessageId);
|
|
15520
|
-
await
|
|
15951
|
+
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
15521
15952
|
const resolvedRoot = path7.resolve(this.rootDir);
|
|
15522
15953
|
const resolvedFile = path7.resolve(path7.join(dir, await uniqueFileName(dir, baseFileName)));
|
|
15523
15954
|
if (!isPathInside(resolvedFile, resolvedRoot)) {
|
|
@@ -16906,15 +17337,6 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
16906
17337
|
await drainPendingFinalForJx(ctx);
|
|
16907
17338
|
return { handled: true };
|
|
16908
17339
|
}
|
|
16909
|
-
case "/logout": {
|
|
16910
|
-
if (listWeixinAccountIds().length === 0) {
|
|
16911
|
-
await sendReply(ctx, t().weixin.noAccountsLoggedIn);
|
|
16912
|
-
return { handled: true };
|
|
16913
|
-
}
|
|
16914
|
-
clearAllWeixinAccounts();
|
|
16915
|
-
await sendReply(ctx, t().weixin.logoutSuccess);
|
|
16916
|
-
return { handled: true };
|
|
16917
|
-
}
|
|
16918
17340
|
default:
|
|
16919
17341
|
return { handled: false };
|
|
16920
17342
|
}
|
|
@@ -16927,7 +17349,6 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
16927
17349
|
}
|
|
16928
17350
|
}
|
|
16929
17351
|
var init_slash_commands = __esm(() => {
|
|
16930
|
-
init_accounts();
|
|
16931
17352
|
init_logger();
|
|
16932
17353
|
init_i18n();
|
|
16933
17354
|
init_final_heads_up();
|
|
@@ -16943,14 +17364,14 @@ function normalizeMediaArray(media) {
|
|
|
16943
17364
|
}
|
|
16944
17365
|
|
|
16945
17366
|
// src/logging/rotating-file-writer.ts
|
|
16946
|
-
import { readdir as readdir2, rename, rm as rm6, stat as stat2 } from "node:fs/promises";
|
|
16947
|
-
import { basename, dirname as
|
|
17367
|
+
import { readdir as readdir2, rename as rename2, rm as rm6, stat as stat2 } from "node:fs/promises";
|
|
17368
|
+
import { basename, dirname as dirname5, join as join8 } from "node:path";
|
|
16948
17369
|
async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
16949
17370
|
let currentSize = 0;
|
|
16950
17371
|
try {
|
|
16951
17372
|
currentSize = (await stat2(filePath)).size;
|
|
16952
17373
|
} catch (error2) {
|
|
16953
|
-
if (!
|
|
17374
|
+
if (!isMissingFileError3(error2)) {
|
|
16954
17375
|
throw error2;
|
|
16955
17376
|
}
|
|
16956
17377
|
}
|
|
@@ -16968,24 +17389,24 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
|
16968
17389
|
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
16969
17390
|
const source = `${filePath}.${index}`;
|
|
16970
17391
|
try {
|
|
16971
|
-
await
|
|
17392
|
+
await rename2(source, `${filePath}.${index + 1}`);
|
|
16972
17393
|
} catch (error2) {
|
|
16973
|
-
if (!
|
|
17394
|
+
if (!isMissingFileError3(error2)) {
|
|
16974
17395
|
throw error2;
|
|
16975
17396
|
}
|
|
16976
17397
|
}
|
|
16977
17398
|
}
|
|
16978
|
-
await
|
|
17399
|
+
await rename2(filePath, `${filePath}.1`);
|
|
16979
17400
|
}
|
|
16980
17401
|
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
16981
|
-
const parentDir =
|
|
17402
|
+
const parentDir = dirname5(filePath);
|
|
16982
17403
|
const prefix = `${basename(filePath)}.`;
|
|
16983
17404
|
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
16984
17405
|
let files = [];
|
|
16985
17406
|
try {
|
|
16986
17407
|
files = await readdir2(parentDir);
|
|
16987
17408
|
} catch (error2) {
|
|
16988
|
-
if (
|
|
17409
|
+
if (isMissingFileError3(error2)) {
|
|
16989
17410
|
return;
|
|
16990
17411
|
}
|
|
16991
17412
|
throw error2;
|
|
@@ -16995,23 +17416,31 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
|
16995
17416
|
continue;
|
|
16996
17417
|
}
|
|
16997
17418
|
const candidate = join8(parentDir, file);
|
|
16998
|
-
|
|
17419
|
+
let details;
|
|
17420
|
+
try {
|
|
17421
|
+
details = await stat2(candidate);
|
|
17422
|
+
} catch (error2) {
|
|
17423
|
+
if (isMissingFileError3(error2)) {
|
|
17424
|
+
continue;
|
|
17425
|
+
}
|
|
17426
|
+
throw error2;
|
|
17427
|
+
}
|
|
16999
17428
|
if (details.mtime.getTime() < cutoff) {
|
|
17000
17429
|
await rm6(candidate, { force: true });
|
|
17001
17430
|
}
|
|
17002
17431
|
}
|
|
17003
17432
|
}
|
|
17004
|
-
function
|
|
17433
|
+
function isMissingFileError3(error2) {
|
|
17005
17434
|
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
17006
17435
|
}
|
|
17007
17436
|
var init_rotating_file_writer = () => {};
|
|
17008
17437
|
|
|
17009
17438
|
// src/perf/perf-log-writer.ts
|
|
17010
17439
|
import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
|
|
17011
|
-
import { dirname as
|
|
17440
|
+
import { dirname as dirname6 } from "node:path";
|
|
17012
17441
|
function createPerfLogWriter(options) {
|
|
17013
17442
|
const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
|
|
17014
|
-
const
|
|
17443
|
+
const mkdir6 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
|
|
17015
17444
|
return;
|
|
17016
17445
|
}));
|
|
17017
17446
|
const now = options.now ?? (() => new Date);
|
|
@@ -17058,7 +17487,7 @@ function createPerfLogWriter(options) {
|
|
|
17058
17487
|
return;
|
|
17059
17488
|
const data = batch.join("");
|
|
17060
17489
|
try {
|
|
17061
|
-
await
|
|
17490
|
+
await mkdir6(dirname6(options.filePath), { recursive: true, mode: 448 });
|
|
17062
17491
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
|
|
17063
17492
|
await append(options.filePath, data);
|
|
17064
17493
|
consecutiveFailures = 0;
|
|
@@ -17589,13 +18018,39 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
17589
18018
|
deps.errLog(`weixin.final.dropped reason=backgrounded_no_store kind=text chatKey=${to}`);
|
|
17590
18019
|
} else {
|
|
17591
18020
|
const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
|
|
18021
|
+
const sendAllParkedNotice = async (count) => {
|
|
18022
|
+
try {
|
|
18023
|
+
await sendMessageWeixin({
|
|
18024
|
+
to,
|
|
18025
|
+
text: t().misc.finalAllParked(count),
|
|
18026
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
18027
|
+
});
|
|
18028
|
+
} catch (noticeErr) {
|
|
18029
|
+
deps.errLog(`weixin.final.parked_notice_failed chatKey=${to} err=${String(noticeErr)}`);
|
|
18030
|
+
}
|
|
18031
|
+
};
|
|
18032
|
+
const buildPendingChunk = (text, seq, total) => {
|
|
18033
|
+
const entry = { text, seq, total };
|
|
18034
|
+
if (contextToken !== undefined)
|
|
18035
|
+
entry.contextToken = contextToken;
|
|
18036
|
+
if (deps.accountId !== undefined)
|
|
18037
|
+
entry.accountId = deps.accountId;
|
|
18038
|
+
return entry;
|
|
18039
|
+
};
|
|
17592
18040
|
if (rawChunks.length > 0) {
|
|
17593
18041
|
const total = rawChunks.length;
|
|
17594
18042
|
if (total === 1) {
|
|
17595
18043
|
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
17596
18044
|
if (!reserved) {
|
|
17597
|
-
|
|
17598
|
-
|
|
18045
|
+
if (deps.enqueuePendingFinal) {
|
|
18046
|
+
deps.enqueuePendingFinal(to, [buildPendingChunk(rawChunks[0], 1, 1)]);
|
|
18047
|
+
finalChunksPending = 1;
|
|
18048
|
+
deps.errLog(`weixin.final.parked reason=quota_exhausted kind=text chatKey=${to}`);
|
|
18049
|
+
await sendAllParkedNotice(1);
|
|
18050
|
+
} else {
|
|
18051
|
+
finalDropped = true;
|
|
18052
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
|
|
18053
|
+
}
|
|
17599
18054
|
} else {
|
|
17600
18055
|
await sendMessageWeixin({
|
|
17601
18056
|
to,
|
|
@@ -17652,16 +18107,11 @@ ${buildFinalHeadsUp({
|
|
|
17652
18107
|
const restToPark = prefixed.slice(sent);
|
|
17653
18108
|
finalChunksPending = restToPark.length;
|
|
17654
18109
|
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
17655
|
-
const pending = restToPark.map((text, idx) =>
|
|
17656
|
-
const seq = sent + idx + 1;
|
|
17657
|
-
const entry = { text, seq, total };
|
|
17658
|
-
if (contextToken !== undefined)
|
|
17659
|
-
entry.contextToken = contextToken;
|
|
17660
|
-
if (deps.accountId !== undefined)
|
|
17661
|
-
entry.accountId = deps.accountId;
|
|
17662
|
-
return entry;
|
|
17663
|
-
});
|
|
18110
|
+
const pending = restToPark.map((text, idx) => buildPendingChunk(text, sent + idx + 1, total));
|
|
17664
18111
|
deps.enqueuePendingFinal(to, pending);
|
|
18112
|
+
if (sent === 0) {
|
|
18113
|
+
await sendAllParkedNotice(restToPark.length);
|
|
18114
|
+
}
|
|
17665
18115
|
}
|
|
17666
18116
|
}
|
|
17667
18117
|
}
|
|
@@ -17857,7 +18307,7 @@ function shouldFetchTypingConfig(textBody) {
|
|
|
17857
18307
|
const command = parseSlashCommand(textBody);
|
|
17858
18308
|
if (!command)
|
|
17859
18309
|
return true;
|
|
17860
|
-
return !["/cancel", "/stop", "/jx", "/echo", "/toggle-debug"
|
|
18310
|
+
return !["/cancel", "/stop", "/jx", "/echo", "/toggle-debug"].includes(command);
|
|
17861
18311
|
}
|
|
17862
18312
|
async function monitorWeixinProvider(opts) {
|
|
17863
18313
|
const {
|
|
@@ -18580,7 +19030,7 @@ ${buildFinalHeadsUp({
|
|
|
18580
19030
|
let sent = 0;
|
|
18581
19031
|
for (let index = 0;index < wave.length; index += 1) {
|
|
18582
19032
|
if (!deps.reserveFinal(quotaKey)) {
|
|
18583
|
-
await deps.logger.info("scheduled.final_dropped", "scheduled turn final response dropped due to quota", { chatKey: input.chatKey, reason: "quota_exhausted", chunk: index + 1, total });
|
|
19033
|
+
await deps.logger.info(deps.enqueuePendingFinal ? "scheduled.final_parked" : "scheduled.final_dropped", deps.enqueuePendingFinal ? "scheduled turn final response parked due to quota" : "scheduled turn final response dropped due to quota", { chatKey: input.chatKey, reason: "quota_exhausted", chunk: index + 1, total });
|
|
18584
19034
|
break;
|
|
18585
19035
|
}
|
|
18586
19036
|
const delivered = await sendTextViaAvailableAccount(wave[index], "scheduled.final_send_failed");
|
|
@@ -18589,7 +19039,7 @@ ${buildFinalHeadsUp({
|
|
|
18589
19039
|
sent += 1;
|
|
18590
19040
|
}
|
|
18591
19041
|
const restToPark = chunks.slice(sent);
|
|
18592
|
-
if (
|
|
19042
|
+
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
18593
19043
|
const pending = restToPark.map((text, index) => {
|
|
18594
19044
|
const entry = { text, seq: sent + index + 1, total };
|
|
18595
19045
|
if (deliveryContextToken)
|
|
@@ -18599,6 +19049,12 @@ ${buildFinalHeadsUp({
|
|
|
18599
19049
|
return entry;
|
|
18600
19050
|
});
|
|
18601
19051
|
deps.enqueuePendingFinal(quotaKey, pending);
|
|
19052
|
+
if (sent === 0) {
|
|
19053
|
+
const noticeDelivered = await sendTextViaAvailableAccount(t().misc.finalAllParked(restToPark.length), "scheduled.final_parked_notice_failed");
|
|
19054
|
+
if (!noticeDelivered) {
|
|
19055
|
+
await deps.logger.info("scheduled.final_parked_notice_failed", "scheduled parked-final notice could not be delivered", { chatKey: input.chatKey, parked: restToPark.length });
|
|
19056
|
+
}
|
|
19057
|
+
}
|
|
18602
19058
|
}
|
|
18603
19059
|
}
|
|
18604
19060
|
async function sendTextViaAvailableAccount(text, errorEvent) {
|
|
@@ -18636,8 +19092,8 @@ var init_scheduled_turn = __esm(() => {
|
|
|
18636
19092
|
});
|
|
18637
19093
|
|
|
18638
19094
|
// src/weixin/monitor/consumer-lock.ts
|
|
18639
|
-
import { mkdir as
|
|
18640
|
-
import { dirname as
|
|
19095
|
+
import { mkdir as mkdir6, open as open3, readFile as readFile8, rm as rm7 } from "node:fs/promises";
|
|
19096
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
18641
19097
|
import { homedir as homedir4 } from "node:os";
|
|
18642
19098
|
function createWeixinConsumerLock(options = {}) {
|
|
18643
19099
|
const lockFilePath = options.lockFilePath ?? join9(coreHomeDir(homedir4()), "runtime", "weixin-consumer.lock.json");
|
|
@@ -18645,7 +19101,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
18645
19101
|
const onDiagnostic = options.onDiagnostic;
|
|
18646
19102
|
return {
|
|
18647
19103
|
async acquire(meta2) {
|
|
18648
|
-
await
|
|
19104
|
+
await mkdir6(dirname7(lockFilePath), { recursive: true, mode: 448 });
|
|
18649
19105
|
while (true) {
|
|
18650
19106
|
try {
|
|
18651
19107
|
const handle = await open3(lockFilePath, "wx");
|
|
@@ -18718,7 +19174,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
18718
19174
|
}
|
|
18719
19175
|
async function loadLockMetadata(path14) {
|
|
18720
19176
|
try {
|
|
18721
|
-
const raw = await
|
|
19177
|
+
const raw = await readFile8(path14, "utf8");
|
|
18722
19178
|
const parsed = JSON.parse(raw);
|
|
18723
19179
|
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
18724
19180
|
return null;
|
|
@@ -18783,6 +19239,13 @@ class WeixinChannel {
|
|
|
18783
19239
|
logout() {
|
|
18784
19240
|
logout();
|
|
18785
19241
|
}
|
|
19242
|
+
stop() {
|
|
19243
|
+
this.agent = null;
|
|
19244
|
+
this.quota = null;
|
|
19245
|
+
this.logger = null;
|
|
19246
|
+
this.markDelivered = null;
|
|
19247
|
+
this.markFailed = null;
|
|
19248
|
+
}
|
|
18786
19249
|
createConsumerLock(options) {
|
|
18787
19250
|
return createWeixinConsumerLock({
|
|
18788
19251
|
...options?.lockFilePath ? { lockFilePath: options.lockFilePath } : {},
|
|
@@ -19363,8 +19826,8 @@ var init_bootstrap = __esm(() => {
|
|
|
19363
19826
|
});
|
|
19364
19827
|
|
|
19365
19828
|
// src/logging/app-logger.ts
|
|
19366
|
-
import { appendFile, chmod as
|
|
19367
|
-
import { dirname as
|
|
19829
|
+
import { appendFile, chmod as chmod3, mkdir as mkdir7 } from "node:fs/promises";
|
|
19830
|
+
import { dirname as dirname9 } from "node:path";
|
|
19368
19831
|
function createNoopAppLogger() {
|
|
19369
19832
|
return {
|
|
19370
19833
|
debug: async () => {},
|
|
@@ -19378,6 +19841,7 @@ function createAppLogger(options) {
|
|
|
19378
19841
|
const now = options.now ?? (() => new Date);
|
|
19379
19842
|
let writeChain = Promise.resolve();
|
|
19380
19843
|
let modeEnsured = false;
|
|
19844
|
+
let writeErrorLatched = false;
|
|
19381
19845
|
return {
|
|
19382
19846
|
debug: async (event, message, context) => {
|
|
19383
19847
|
await enqueueWrite("debug", event, message, context);
|
|
@@ -19389,14 +19853,21 @@ function createAppLogger(options) {
|
|
|
19389
19853
|
await enqueueWrite("error", event, message, context);
|
|
19390
19854
|
},
|
|
19391
19855
|
cleanup: async () => {
|
|
19392
|
-
|
|
19856
|
+
try {
|
|
19857
|
+
await cleanupExpiredRotatedLogs(options.filePath, options.retentionDays, now);
|
|
19858
|
+
} catch {}
|
|
19393
19859
|
},
|
|
19394
19860
|
flush: async () => {
|
|
19395
19861
|
await writeChain;
|
|
19396
19862
|
}
|
|
19397
19863
|
};
|
|
19398
19864
|
function enqueueWrite(level, event, message, context = {}) {
|
|
19399
|
-
const writePromise = writeChain.
|
|
19865
|
+
const writePromise = writeChain.then(() => writeLog2(level, event, message, context)).catch((error2) => {
|
|
19866
|
+
if (!writeErrorLatched) {
|
|
19867
|
+
writeErrorLatched = true;
|
|
19868
|
+
console.error("[xacpx] app-logger: log file write failed — further write errors will be suppressed.", error2 instanceof Error ? error2.message : String(error2));
|
|
19869
|
+
}
|
|
19870
|
+
});
|
|
19400
19871
|
writeChain = writePromise;
|
|
19401
19872
|
return writePromise;
|
|
19402
19873
|
}
|
|
@@ -19405,13 +19876,14 @@ function createAppLogger(options) {
|
|
|
19405
19876
|
return;
|
|
19406
19877
|
}
|
|
19407
19878
|
const line = formatLogLine(now(), level, event, message, context);
|
|
19408
|
-
await
|
|
19879
|
+
await mkdir7(dirname9(options.filePath), { recursive: true, mode: 448 });
|
|
19409
19880
|
if (!modeEnsured) {
|
|
19410
19881
|
modeEnsured = true;
|
|
19411
|
-
await
|
|
19882
|
+
await chmod3(options.filePath, 384).catch(() => {});
|
|
19412
19883
|
}
|
|
19413
19884
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
|
|
19414
19885
|
await appendFile(options.filePath, line, { encoding: "utf8", mode: 384 });
|
|
19886
|
+
writeErrorLatched = false;
|
|
19415
19887
|
}
|
|
19416
19888
|
}
|
|
19417
19889
|
function formatLogLine(time3, level, event, message, context) {
|
|
@@ -19440,7 +19912,7 @@ var init_app_logger = __esm(() => {
|
|
|
19440
19912
|
});
|
|
19441
19913
|
|
|
19442
19914
|
// src/transport/acpx-session-index.ts
|
|
19443
|
-
import { readFile as
|
|
19915
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
19444
19916
|
import { homedir as homedir5 } from "node:os";
|
|
19445
19917
|
import { resolve as resolve2 } from "node:path";
|
|
19446
19918
|
async function resolveSessionAgentCommandFromIndex(session3) {
|
|
@@ -19449,7 +19921,7 @@ async function resolveSessionAgentCommandFromIndex(session3) {
|
|
|
19449
19921
|
return;
|
|
19450
19922
|
}
|
|
19451
19923
|
try {
|
|
19452
|
-
const raw = await
|
|
19924
|
+
const raw = await readFile12(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
|
|
19453
19925
|
const parsed = JSON.parse(raw);
|
|
19454
19926
|
const targetCwd = resolve2(session3.cwd);
|
|
19455
19927
|
const match = parsed.entries?.find((entry) => entry.name === session3.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
|
|
@@ -19631,7 +20103,9 @@ function parseCommand(input) {
|
|
|
19631
20103
|
if (!trimmed.startsWith("/")) {
|
|
19632
20104
|
return { kind: "prompt", text: trimmed };
|
|
19633
20105
|
}
|
|
19634
|
-
const
|
|
20106
|
+
const tokens = tokenizeCommand(trimmed);
|
|
20107
|
+
const parts = tokens.map((token) => token.value);
|
|
20108
|
+
const rawTail = (index) => index < tokens.length ? trimmed.slice(tokens[index]?.start ?? 0) : "";
|
|
19635
20109
|
const command = normalizeCommand(parts[0] ?? "");
|
|
19636
20110
|
if (command === "/help" && parts.length === 1)
|
|
19637
20111
|
return { kind: "help" };
|
|
@@ -19740,7 +20214,7 @@ function parseCommand(input) {
|
|
|
19740
20214
|
return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
|
|
19741
20215
|
}
|
|
19742
20216
|
if (command === "/group" && parts[1] === "new" && parts.length > 2) {
|
|
19743
|
-
const title =
|
|
20217
|
+
const title = rawTail(2);
|
|
19744
20218
|
if (title.trim().length > 0) {
|
|
19745
20219
|
return { kind: "group.new", title };
|
|
19746
20220
|
}
|
|
@@ -19764,7 +20238,7 @@ function parseCommand(input) {
|
|
|
19764
20238
|
}
|
|
19765
20239
|
break;
|
|
19766
20240
|
}
|
|
19767
|
-
const task =
|
|
20241
|
+
const task = rawTail(index);
|
|
19768
20242
|
if (groupId.trim().length > 0 && targetAgent.trim().length > 0 && task.trim().length > 0) {
|
|
19769
20243
|
return {
|
|
19770
20244
|
kind: "group.delegate",
|
|
@@ -19818,7 +20292,7 @@ function parseCommand(input) {
|
|
|
19818
20292
|
return { kind: "agent.rm", name: parts[2] };
|
|
19819
20293
|
}
|
|
19820
20294
|
if ((command === "/delegate" || command === "/dg") && parts[1]) {
|
|
19821
|
-
const parsedDelegate = parseDelegateRequest(parts);
|
|
20295
|
+
const parsedDelegate = parseDelegateRequest(parts, rawTail);
|
|
19822
20296
|
if (parsedDelegate) {
|
|
19823
20297
|
return parsedDelegate;
|
|
19824
20298
|
}
|
|
@@ -19846,7 +20320,11 @@ function parseCommand(input) {
|
|
|
19846
20320
|
if (parts[1] === "cancel" && parts[2] && parts.length === 3) {
|
|
19847
20321
|
return { kind: "later.cancel", id: parts[2] };
|
|
19848
20322
|
}
|
|
19849
|
-
return {
|
|
20323
|
+
return {
|
|
20324
|
+
kind: "later.create",
|
|
20325
|
+
tokens: parts.slice(1),
|
|
20326
|
+
tails: tokens.slice(1).map((_, index) => rawTail(index + 1))
|
|
20327
|
+
};
|
|
19850
20328
|
}
|
|
19851
20329
|
if (command === "/workspace" && parts[1] === "new" && parts[2]) {
|
|
19852
20330
|
const name = parts[2];
|
|
@@ -20099,17 +20577,18 @@ function readNativeAttachCommand(parts, identifierIndex) {
|
|
|
20099
20577
|
return { kind: "session.native.attach", identifier };
|
|
20100
20578
|
}
|
|
20101
20579
|
function normalizeCommand(command) {
|
|
20102
|
-
|
|
20580
|
+
const lowered = command.toLowerCase();
|
|
20581
|
+
if (lowered === "/ss")
|
|
20103
20582
|
return "/session";
|
|
20104
|
-
if (
|
|
20583
|
+
if (lowered === "/ws")
|
|
20105
20584
|
return "/workspace";
|
|
20106
|
-
if (
|
|
20585
|
+
if (lowered === "/pm")
|
|
20107
20586
|
return "/permission";
|
|
20108
|
-
if (
|
|
20587
|
+
if (lowered === "/stop")
|
|
20109
20588
|
return "/cancel";
|
|
20110
|
-
if (
|
|
20589
|
+
if (lowered === "/lt")
|
|
20111
20590
|
return "/later";
|
|
20112
|
-
return
|
|
20591
|
+
return lowered;
|
|
20113
20592
|
}
|
|
20114
20593
|
function isRecognizedCommand(command) {
|
|
20115
20594
|
return isKnownXacpxCommandPrefix(command);
|
|
@@ -20132,31 +20611,41 @@ function toNonInteractivePermission(value) {
|
|
|
20132
20611
|
function tokenizeCommand(input) {
|
|
20133
20612
|
const tokens = [];
|
|
20134
20613
|
let current = "";
|
|
20135
|
-
let
|
|
20614
|
+
let start2 = -1;
|
|
20615
|
+
let closingQuote = null;
|
|
20616
|
+
let offset = 0;
|
|
20136
20617
|
for (const char of input) {
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20618
|
+
const charStart = offset;
|
|
20619
|
+
offset += char.length;
|
|
20620
|
+
if (closingQuote) {
|
|
20621
|
+
if (char === closingQuote) {
|
|
20622
|
+
closingQuote = null;
|
|
20140
20623
|
} else {
|
|
20141
20624
|
current += char;
|
|
20142
20625
|
}
|
|
20143
20626
|
continue;
|
|
20144
20627
|
}
|
|
20145
|
-
|
|
20146
|
-
|
|
20628
|
+
const close = QUOTE_PAIRS[char];
|
|
20629
|
+
if (close) {
|
|
20630
|
+
if (start2 === -1)
|
|
20631
|
+
start2 = charStart;
|
|
20632
|
+
closingQuote = close;
|
|
20147
20633
|
continue;
|
|
20148
20634
|
}
|
|
20149
20635
|
if (/\s/.test(char)) {
|
|
20150
20636
|
if (current.length > 0) {
|
|
20151
|
-
tokens.push(current);
|
|
20637
|
+
tokens.push({ value: current, start: start2 });
|
|
20152
20638
|
current = "";
|
|
20153
20639
|
}
|
|
20640
|
+
start2 = -1;
|
|
20154
20641
|
continue;
|
|
20155
20642
|
}
|
|
20643
|
+
if (start2 === -1)
|
|
20644
|
+
start2 = charStart;
|
|
20156
20645
|
current += char;
|
|
20157
20646
|
}
|
|
20158
20647
|
if (current.length > 0) {
|
|
20159
|
-
tokens.push(current);
|
|
20648
|
+
tokens.push({ value: current, start: start2 });
|
|
20160
20649
|
}
|
|
20161
20650
|
return tokens;
|
|
20162
20651
|
}
|
|
@@ -20199,7 +20688,7 @@ function parseListFilterFlags(parts, validStatuses) {
|
|
|
20199
20688
|
}
|
|
20200
20689
|
return { filter, ok: true };
|
|
20201
20690
|
}
|
|
20202
|
-
function parseDelegateRequest(parts) {
|
|
20691
|
+
function parseDelegateRequest(parts, rawTail) {
|
|
20203
20692
|
const targetAgent = parts[1];
|
|
20204
20693
|
if (!targetAgent) {
|
|
20205
20694
|
return null;
|
|
@@ -20227,7 +20716,7 @@ function parseDelegateRequest(parts) {
|
|
|
20227
20716
|
}
|
|
20228
20717
|
break;
|
|
20229
20718
|
}
|
|
20230
|
-
const task =
|
|
20719
|
+
const task = rawTail(index);
|
|
20231
20720
|
if (task.trim().length === 0) {
|
|
20232
20721
|
return null;
|
|
20233
20722
|
}
|
|
@@ -20239,9 +20728,16 @@ function parseDelegateRequest(parts) {
|
|
|
20239
20728
|
task
|
|
20240
20729
|
};
|
|
20241
20730
|
}
|
|
20242
|
-
var TASK_STATUS_VALUES, GROUP_STATUS_VALUES;
|
|
20731
|
+
var QUOTE_PAIRS, TASK_STATUS_VALUES, GROUP_STATUS_VALUES;
|
|
20243
20732
|
var init_parse_command = __esm(() => {
|
|
20244
20733
|
init_command_list();
|
|
20734
|
+
QUOTE_PAIRS = {
|
|
20735
|
+
'"': '"',
|
|
20736
|
+
"'": "'",
|
|
20737
|
+
"“": "”",
|
|
20738
|
+
"‘": "’",
|
|
20739
|
+
""": """
|
|
20740
|
+
};
|
|
20245
20741
|
TASK_STATUS_VALUES = [
|
|
20246
20742
|
"pending",
|
|
20247
20743
|
"needs_confirmation",
|
|
@@ -20254,7 +20750,50 @@ var init_parse_command = __esm(() => {
|
|
|
20254
20750
|
});
|
|
20255
20751
|
|
|
20256
20752
|
// src/commands/command-policy.ts
|
|
20753
|
+
function isInternalScheduledTurn(metadata) {
|
|
20754
|
+
return Boolean(metadata?.scheduledSessionAlias || metadata?.scheduledSessionDescriptor);
|
|
20755
|
+
}
|
|
20756
|
+
function resolveChannelOwnerIds(config4, channel) {
|
|
20757
|
+
if (!config4 || !channel) {
|
|
20758
|
+
return;
|
|
20759
|
+
}
|
|
20760
|
+
let configured = false;
|
|
20761
|
+
const ids = new Set;
|
|
20762
|
+
if (config4.channel?.type === channel && config4.channel.ownerIds) {
|
|
20763
|
+
configured = true;
|
|
20764
|
+
for (const id of config4.channel.ownerIds)
|
|
20765
|
+
ids.add(id);
|
|
20766
|
+
}
|
|
20767
|
+
for (const entry of config4.channels ?? []) {
|
|
20768
|
+
if ((entry.type === channel || entry.id === channel) && entry.ownerIds) {
|
|
20769
|
+
configured = true;
|
|
20770
|
+
for (const id of entry.ownerIds)
|
|
20771
|
+
ids.add(id);
|
|
20772
|
+
}
|
|
20773
|
+
}
|
|
20774
|
+
return configured ? [...ids] : undefined;
|
|
20775
|
+
}
|
|
20776
|
+
function withEffectiveOwner(metadata, config4) {
|
|
20777
|
+
if (!metadata?.channel || isInternalScheduledTurn(metadata)) {
|
|
20778
|
+
return metadata;
|
|
20779
|
+
}
|
|
20780
|
+
const ownerIds = resolveChannelOwnerIds(config4, metadata.channel);
|
|
20781
|
+
if (ownerIds === undefined) {
|
|
20782
|
+
return metadata;
|
|
20783
|
+
}
|
|
20784
|
+
const isOwner = metadata.isOwner === true || typeof metadata.senderId === "string" && ownerIds.includes(metadata.senderId);
|
|
20785
|
+
return { ...metadata, isOwner };
|
|
20786
|
+
}
|
|
20257
20787
|
function authorizeCommandForChat(command, metadata) {
|
|
20788
|
+
if (metadata?.channel && !isInternalScheduledTurn(metadata) && metadata.chatType !== "direct" && metadata.chatType !== "group") {
|
|
20789
|
+
if (GROUP_PUBLIC_COMMAND_KINDS.has(command.kind)) {
|
|
20790
|
+
return { allowed: true };
|
|
20791
|
+
}
|
|
20792
|
+
return {
|
|
20793
|
+
allowed: false,
|
|
20794
|
+
reason: "chat-type-missing"
|
|
20795
|
+
};
|
|
20796
|
+
}
|
|
20258
20797
|
if (metadata?.chatType !== "group") {
|
|
20259
20798
|
return { allowed: true };
|
|
20260
20799
|
}
|
|
@@ -20269,7 +20808,14 @@ function authorizeCommandForChat(command, metadata) {
|
|
|
20269
20808
|
reason: "group-owner-required"
|
|
20270
20809
|
};
|
|
20271
20810
|
}
|
|
20272
|
-
function renderCommandAccessDenied(command) {
|
|
20811
|
+
function renderCommandAccessDenied(command, reason) {
|
|
20812
|
+
if (reason === "chat-type-missing") {
|
|
20813
|
+
return [
|
|
20814
|
+
`⚠️ ${renderCommandLabel(command)}${t().misc.commandAccessDeniedChatTypeMissingSuffix}`,
|
|
20815
|
+
t().misc.commandAccessDeniedChatTypeMissingHint
|
|
20816
|
+
].join(`
|
|
20817
|
+
`);
|
|
20818
|
+
}
|
|
20273
20819
|
return [
|
|
20274
20820
|
`⚠️ ${renderCommandLabel(command)}${t().misc.commandAccessDeniedSuffix}`,
|
|
20275
20821
|
t().misc.commandAccessDeniedHint
|
|
@@ -20395,13 +20941,18 @@ async function handlePermissionModeSet(context, mode) {
|
|
|
20395
20941
|
return { text: p.noWritableConfig };
|
|
20396
20942
|
}
|
|
20397
20943
|
const previous = cloneAppConfig(context.config);
|
|
20944
|
+
const previousRaw = await context.configStore.getRawValue(["transport", "permissionMode"]);
|
|
20398
20945
|
const updated = await context.configStore.updateTransport({
|
|
20399
20946
|
permissionMode: mode
|
|
20400
20947
|
});
|
|
20401
20948
|
try {
|
|
20402
20949
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20403
20950
|
} catch (error2) {
|
|
20404
|
-
|
|
20951
|
+
if (previousRaw.present) {
|
|
20952
|
+
await context.configStore.setRawValue(["transport", "permissionMode"], previousRaw.value);
|
|
20953
|
+
} else {
|
|
20954
|
+
await context.configStore.unsetRawValue(["transport", "permissionMode"]);
|
|
20955
|
+
}
|
|
20405
20956
|
context.replaceConfig(previous);
|
|
20406
20957
|
throw error2;
|
|
20407
20958
|
}
|
|
@@ -20417,13 +20968,18 @@ async function handlePermissionAutoSet(context, policy) {
|
|
|
20417
20968
|
return { text: p.noWritableConfig };
|
|
20418
20969
|
}
|
|
20419
20970
|
const previous = cloneAppConfig(context.config);
|
|
20971
|
+
const previousRaw = await context.configStore.getRawValue(["transport", "nonInteractivePermissions"]);
|
|
20420
20972
|
const updated = await context.configStore.updateTransport({
|
|
20421
20973
|
nonInteractivePermissions: policy
|
|
20422
20974
|
});
|
|
20423
20975
|
try {
|
|
20424
20976
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20425
20977
|
} catch (error2) {
|
|
20426
|
-
|
|
20978
|
+
if (previousRaw.present) {
|
|
20979
|
+
await context.configStore.setRawValue(["transport", "nonInteractivePermissions"], previousRaw.value);
|
|
20980
|
+
} else {
|
|
20981
|
+
await context.configStore.unsetRawValue(["transport", "nonInteractivePermissions"]);
|
|
20982
|
+
}
|
|
20427
20983
|
context.replaceConfig(previous);
|
|
20428
20984
|
throw error2;
|
|
20429
20985
|
}
|
|
@@ -20473,99 +21029,83 @@ async function handleConfigSet(context, path14, rawValue) {
|
|
|
20473
21029
|
if (!context.config || !context.configStore) {
|
|
20474
21030
|
return { text: c.noWritableConfig };
|
|
20475
21031
|
}
|
|
20476
|
-
const
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
if ("error" in result) {
|
|
20480
|
-
return { text: result.error };
|
|
21032
|
+
const plan = planSupportedConfigUpdate(context.config, path14, rawValue);
|
|
21033
|
+
if ("error" in plan) {
|
|
21034
|
+
return { text: plan.error };
|
|
20481
21035
|
}
|
|
20482
|
-
|
|
21036
|
+
const previousConfig = cloneAppConfig(context.config);
|
|
21037
|
+
const previousRaw = await context.configStore.getRawValue(plan.rawPath);
|
|
21038
|
+
const updated = await context.configStore.setRawValue(plan.rawPath, plan.value);
|
|
20483
21039
|
if (path14 === "transport.permissionMode" || path14 === "transport.nonInteractivePermissions" || path14 === "transport.permissionPolicy") {
|
|
20484
21040
|
try {
|
|
20485
21041
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20486
21042
|
} catch (error2) {
|
|
20487
|
-
|
|
20488
|
-
|
|
21043
|
+
if (previousRaw.present) {
|
|
21044
|
+
await context.configStore.setRawValue(plan.rawPath, previousRaw.value);
|
|
21045
|
+
} else {
|
|
21046
|
+
await context.configStore.unsetRawValue(plan.rawPath);
|
|
21047
|
+
}
|
|
21048
|
+
context.replaceConfig(previousConfig);
|
|
20489
21049
|
throw error2;
|
|
20490
21050
|
}
|
|
20491
21051
|
}
|
|
20492
21052
|
context.replaceConfig(updated);
|
|
20493
|
-
return { text: c.updated(path14,
|
|
21053
|
+
return { text: c.updated(path14, plan.renderedValue) };
|
|
20494
21054
|
}
|
|
20495
|
-
function
|
|
21055
|
+
function planSupportedConfigUpdate(config4, path14, rawValue) {
|
|
20496
21056
|
const c = t().config;
|
|
20497
21057
|
switch (path14) {
|
|
20498
21058
|
case "language": {
|
|
20499
21059
|
if (!isLocale(rawValue))
|
|
20500
21060
|
return { error: c.languageInvalid };
|
|
20501
|
-
|
|
20502
|
-
return { renderedValue: rawValue };
|
|
21061
|
+
return { rawPath: ["language"], value: rawValue, renderedValue: rawValue };
|
|
20503
21062
|
}
|
|
20504
21063
|
case "transport.type": {
|
|
20505
21064
|
const parsed = parseEnum(rawValue, ["acpx-cli", "acpx-bridge"]);
|
|
20506
21065
|
if (!parsed)
|
|
20507
21066
|
return { error: c.transportTypeInvalid };
|
|
20508
|
-
|
|
20509
|
-
return { renderedValue: parsed };
|
|
21067
|
+
return { rawPath: ["transport", "type"], value: parsed, renderedValue: parsed };
|
|
20510
21068
|
}
|
|
20511
21069
|
case "transport.command":
|
|
20512
21070
|
if (!rawValue.trim())
|
|
20513
21071
|
return { error: c.transportCommandEmpty };
|
|
20514
|
-
|
|
20515
|
-
return { renderedValue: rawValue };
|
|
21072
|
+
return { rawPath: ["transport", "command"], value: rawValue, renderedValue: rawValue };
|
|
20516
21073
|
case "transport.sessionInitTimeoutMs": {
|
|
20517
21074
|
const parsed = parsePositiveNumber(rawValue, "transport.sessionInitTimeoutMs");
|
|
20518
21075
|
if ("error" in parsed)
|
|
20519
21076
|
return parsed;
|
|
20520
|
-
|
|
20521
|
-
return { renderedValue: String(parsed.value) };
|
|
21077
|
+
return { rawPath: ["transport", "sessionInitTimeoutMs"], value: parsed.value, renderedValue: String(parsed.value) };
|
|
20522
21078
|
}
|
|
20523
21079
|
case "transport.permissionMode": {
|
|
20524
21080
|
const parsed = parseEnum(rawValue, ["approve-all", "approve-reads", "deny-all"]);
|
|
20525
21081
|
if (!parsed)
|
|
20526
21082
|
return { error: c.transportPermissionModeInvalid };
|
|
20527
|
-
|
|
20528
|
-
return { renderedValue: parsed };
|
|
21083
|
+
return { rawPath: ["transport", "permissionMode"], value: parsed, renderedValue: parsed };
|
|
20529
21084
|
}
|
|
20530
21085
|
case "transport.nonInteractivePermissions": {
|
|
20531
21086
|
const parsed = parseEnum(rawValue, ["deny", "fail"]);
|
|
20532
21087
|
if (!parsed)
|
|
20533
21088
|
return { error: c.transportNonInteractiveInvalid };
|
|
20534
|
-
|
|
20535
|
-
return { renderedValue: parsed };
|
|
21089
|
+
return { rawPath: ["transport", "nonInteractivePermissions"], value: parsed, renderedValue: parsed };
|
|
20536
21090
|
}
|
|
20537
21091
|
case "transport.permissionPolicy":
|
|
20538
21092
|
if (!rawValue.trim())
|
|
20539
21093
|
return { error: c.transportPermissionPolicyEmpty };
|
|
20540
|
-
|
|
20541
|
-
return { renderedValue: rawValue };
|
|
21094
|
+
return { rawPath: ["transport", "permissionPolicy"], value: rawValue, renderedValue: rawValue };
|
|
20542
21095
|
case "logging.level": {
|
|
20543
21096
|
const parsed = parseEnum(rawValue, ["error", "info", "debug"]);
|
|
20544
21097
|
if (!parsed)
|
|
20545
21098
|
return { error: c.loggingLevelInvalid };
|
|
20546
|
-
|
|
20547
|
-
return { renderedValue: parsed };
|
|
20548
|
-
}
|
|
20549
|
-
case "logging.maxSizeBytes": {
|
|
20550
|
-
const parsed = parsePositiveNumber(rawValue, "logging.maxSizeBytes");
|
|
20551
|
-
if ("error" in parsed)
|
|
20552
|
-
return parsed;
|
|
20553
|
-
config4.logging.maxSizeBytes = parsed.value;
|
|
20554
|
-
return { renderedValue: String(parsed.value) };
|
|
20555
|
-
}
|
|
20556
|
-
case "logging.maxFiles": {
|
|
20557
|
-
const parsed = parsePositiveNumber(rawValue, "logging.maxFiles");
|
|
20558
|
-
if ("error" in parsed)
|
|
20559
|
-
return parsed;
|
|
20560
|
-
config4.logging.maxFiles = parsed.value;
|
|
20561
|
-
return { renderedValue: String(parsed.value) };
|
|
21099
|
+
return { rawPath: ["logging", "level"], value: parsed, renderedValue: parsed };
|
|
20562
21100
|
}
|
|
21101
|
+
case "logging.maxSizeBytes":
|
|
21102
|
+
case "logging.maxFiles":
|
|
20563
21103
|
case "logging.retentionDays": {
|
|
20564
|
-
const
|
|
21104
|
+
const field = path14.slice("logging.".length);
|
|
21105
|
+
const parsed = parsePositiveNumber(rawValue, path14);
|
|
20565
21106
|
if ("error" in parsed)
|
|
20566
21107
|
return parsed;
|
|
20567
|
-
|
|
20568
|
-
return { renderedValue: String(parsed.value) };
|
|
21108
|
+
return { rawPath: ["logging", field], value: parsed.value, renderedValue: String(parsed.value) };
|
|
20569
21109
|
}
|
|
20570
21110
|
case "channel.type":
|
|
20571
21111
|
return { error: c.channelTypeDisabled };
|
|
@@ -20573,15 +21113,15 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20573
21113
|
const parsed = parseEnum(rawValue, ["stream", "final", "verbose"]);
|
|
20574
21114
|
if (!parsed)
|
|
20575
21115
|
return { error: c.channelReplyModeInvalid };
|
|
20576
|
-
|
|
20577
|
-
return { renderedValue: parsed };
|
|
21116
|
+
return { rawPath: ["channel", "replyMode"], value: parsed, renderedValue: parsed };
|
|
20578
21117
|
}
|
|
20579
21118
|
case "wechat.replyMode": {
|
|
20580
21119
|
const parsed = parseEnum(rawValue, ["stream", "final", "verbose"]);
|
|
20581
21120
|
if (!parsed)
|
|
20582
21121
|
return { error: c.wechatReplyModeInvalid };
|
|
20583
|
-
config4.channel.replyMode = parsed;
|
|
20584
21122
|
return {
|
|
21123
|
+
rawPath: ["channel", "replyMode"],
|
|
21124
|
+
value: parsed,
|
|
20585
21125
|
renderedValue: c.wechatReplyModeMapped(parsed)
|
|
20586
21126
|
};
|
|
20587
21127
|
}
|
|
@@ -20589,42 +21129,30 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20589
21129
|
const agentMatch = path14.match(/^agents\.([^.]+)\.(driver|command)$/);
|
|
20590
21130
|
if (agentMatch) {
|
|
20591
21131
|
const [, name, field] = agentMatch;
|
|
20592
|
-
if (!name || !field) {
|
|
21132
|
+
if (!name || !field || isPrototypePollutingKey(name)) {
|
|
20593
21133
|
return { error: c.pathNotSupported(path14) };
|
|
20594
21134
|
}
|
|
20595
|
-
|
|
20596
|
-
if (!agent3) {
|
|
21135
|
+
if (!Object.hasOwn(config4.agents, name)) {
|
|
20597
21136
|
return { error: c.agentNotFound(name) };
|
|
20598
21137
|
}
|
|
20599
21138
|
if (!rawValue.trim()) {
|
|
20600
21139
|
return { error: c.fieldEmpty(path14) };
|
|
20601
21140
|
}
|
|
20602
|
-
|
|
20603
|
-
agent3.driver = rawValue;
|
|
20604
|
-
} else {
|
|
20605
|
-
agent3.command = rawValue;
|
|
20606
|
-
}
|
|
20607
|
-
return { renderedValue: rawValue };
|
|
21141
|
+
return { rawPath: ["agents", name, field], value: rawValue, renderedValue: rawValue };
|
|
20608
21142
|
}
|
|
20609
21143
|
const workspaceMatch = path14.match(/^workspaces\.([^.]+)\.(cwd|description)$/);
|
|
20610
21144
|
if (workspaceMatch) {
|
|
20611
21145
|
const [, name, field] = workspaceMatch;
|
|
20612
|
-
if (!name || !field) {
|
|
21146
|
+
if (!name || !field || isPrototypePollutingKey(name)) {
|
|
20613
21147
|
return { error: c.pathNotSupported(path14) };
|
|
20614
21148
|
}
|
|
20615
|
-
|
|
20616
|
-
if (!workspace3) {
|
|
21149
|
+
if (!Object.hasOwn(config4.workspaces, name)) {
|
|
20617
21150
|
return { error: c.workspaceNotFound(name) };
|
|
20618
21151
|
}
|
|
20619
21152
|
if (!rawValue.trim()) {
|
|
20620
21153
|
return { error: c.fieldEmpty(path14) };
|
|
20621
21154
|
}
|
|
20622
|
-
|
|
20623
|
-
workspace3.cwd = rawValue;
|
|
20624
|
-
} else {
|
|
20625
|
-
workspace3.description = rawValue;
|
|
20626
|
-
}
|
|
20627
|
-
return { renderedValue: rawValue };
|
|
21155
|
+
return { rawPath: ["workspaces", name, field], value: rawValue, renderedValue: rawValue };
|
|
20628
21156
|
}
|
|
20629
21157
|
const channelMatch = path14.match(/^channels\.([^.]+)\.replyMode$/);
|
|
20630
21158
|
if (channelMatch) {
|
|
@@ -20640,11 +21168,21 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20640
21168
|
if (!parsed) {
|
|
20641
21169
|
return { error: c.channelRuntimeReplyModeInvalid(id) };
|
|
20642
21170
|
}
|
|
20643
|
-
|
|
20644
|
-
|
|
21171
|
+
return {
|
|
21172
|
+
rawPath: [
|
|
21173
|
+
"channels",
|
|
21174
|
+
{ id, createWith: { id: channel.id, type: channel.type, enabled: channel.enabled } },
|
|
21175
|
+
"replyMode"
|
|
21176
|
+
],
|
|
21177
|
+
value: parsed,
|
|
21178
|
+
renderedValue: parsed
|
|
21179
|
+
};
|
|
20645
21180
|
}
|
|
20646
21181
|
return { error: c.pathNotSupported(path14) };
|
|
20647
21182
|
}
|
|
21183
|
+
function isPrototypePollutingKey(key) {
|
|
21184
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
21185
|
+
}
|
|
20648
21186
|
function parseEnum(value, allowed) {
|
|
20649
21187
|
return allowed.includes(value) ? value : null;
|
|
20650
21188
|
}
|
|
@@ -21186,6 +21724,10 @@ async function handleSessions(context, chatKey) {
|
|
|
21186
21724
|
async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
|
|
21187
21725
|
const channelId = getChannelIdFromChatKey(chatKey);
|
|
21188
21726
|
const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
|
|
21727
|
+
const existing = context.sessions.getResolvedSessionByInternalAlias(internalAlias);
|
|
21728
|
+
if (existing) {
|
|
21729
|
+
return { text: t().session.sessionAlreadyExists(alias, existing.agent, existing.workspace) };
|
|
21730
|
+
}
|
|
21189
21731
|
const session3 = context.lifecycle.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
|
|
21190
21732
|
const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session3.transportSession);
|
|
21191
21733
|
try {
|
|
@@ -21456,7 +21998,9 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
21456
21998
|
}
|
|
21457
21999
|
}
|
|
21458
22000
|
const sharedAliasCount = context.sessions.countAliasesSharingTransport(session3.transportSession, internalAlias);
|
|
22001
|
+
const wasCurrentInThisChat = context.sessions.peekCurrentSessionAlias(chatKey) === internalAlias;
|
|
21459
22002
|
const { wasActive } = await context.sessions.removeSession(internalAlias);
|
|
22003
|
+
const promotedAlias = wasCurrentInThisChat ? context.sessions.peekCurrentSessionAlias(chatKey) || undefined : undefined;
|
|
21460
22004
|
let orchestrationPurgeWarning;
|
|
21461
22005
|
if (context.orchestration) {
|
|
21462
22006
|
try {
|
|
@@ -21494,7 +22038,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
21494
22038
|
const s = t().session;
|
|
21495
22039
|
const lines = [s.sessionRemoved(alias)];
|
|
21496
22040
|
if (wasActive) {
|
|
21497
|
-
lines.push(s.sessionRemovedWasActive);
|
|
22041
|
+
lines.push(promotedAlias ? s.sessionRemovedWasActivePromoted(toDisplaySessionAlias(promotedAlias)) : s.sessionRemovedWasActive);
|
|
21498
22042
|
}
|
|
21499
22043
|
if (!shouldTeardownTransport) {
|
|
21500
22044
|
lines.push(s.sessionTransportShared(session3.transportSession, sharedAliasCount));
|
|
@@ -21516,7 +22060,7 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
|
|
|
21516
22060
|
if (context.orchestration) {
|
|
21517
22061
|
try {
|
|
21518
22062
|
await context.orchestration.recordCoordinatorRouteContext?.({
|
|
21519
|
-
coordinatorSession: session3.transportSession,
|
|
22063
|
+
coordinatorSession: stableCoordinatorSession(session3.transportSession),
|
|
21520
22064
|
chatKey,
|
|
21521
22065
|
sessionAlias: session3.alias,
|
|
21522
22066
|
...replyContextToken ? { replyContextToken } : {},
|
|
@@ -21599,7 +22143,7 @@ async function preparePromptWithFallback(context, session3, chatKey, text, reply
|
|
|
21599
22143
|
try {
|
|
21600
22144
|
return await buildCoordinatorPrompt({
|
|
21601
22145
|
orchestration: orchestration3,
|
|
21602
|
-
coordinatorSession: session3.transportSession,
|
|
22146
|
+
coordinatorSession: stableCoordinatorSession(session3.transportSession),
|
|
21603
22147
|
chatKey,
|
|
21604
22148
|
userText: text,
|
|
21605
22149
|
...replyContextToken ? { replyContextToken } : {},
|
|
@@ -21934,6 +22478,7 @@ async function handleDelegateRequest(context, chatKey, targetAgent, task, role,
|
|
|
21934
22478
|
if (!session3) {
|
|
21935
22479
|
return { text: t().orchestration.noCurrentSession };
|
|
21936
22480
|
}
|
|
22481
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
21937
22482
|
const orchestration3 = getOrchestration(context);
|
|
21938
22483
|
if (!orchestration3) {
|
|
21939
22484
|
return { text: renderOrchestrationUnavailable() };
|
|
@@ -21941,7 +22486,7 @@ async function handleDelegateRequest(context, chatKey, targetAgent, task, role,
|
|
|
21941
22486
|
const result = await orchestration3.requestDelegate({
|
|
21942
22487
|
sourceHandle: session3.transportSession,
|
|
21943
22488
|
sourceKind: "coordinator",
|
|
21944
|
-
coordinatorSession
|
|
22489
|
+
coordinatorSession,
|
|
21945
22490
|
workspace: session3.workspace,
|
|
21946
22491
|
targetAgent,
|
|
21947
22492
|
task,
|
|
@@ -21958,12 +22503,13 @@ async function handleGroupCreate(context, chatKey, title) {
|
|
|
21958
22503
|
if (!session3) {
|
|
21959
22504
|
return { text: t().orchestration.noCurrentSession };
|
|
21960
22505
|
}
|
|
22506
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
21961
22507
|
const orchestration3 = getOrchestration(context);
|
|
21962
22508
|
if (!orchestration3) {
|
|
21963
22509
|
return { text: renderOrchestrationUnavailable() };
|
|
21964
22510
|
}
|
|
21965
22511
|
const group = await orchestration3.createGroup({
|
|
21966
|
-
coordinatorSession
|
|
22512
|
+
coordinatorSession,
|
|
21967
22513
|
title
|
|
21968
22514
|
});
|
|
21969
22515
|
return { text: renderGroupCreated(group) };
|
|
@@ -21973,12 +22519,13 @@ async function handleGroupList(context, chatKey, filter) {
|
|
|
21973
22519
|
if (!session3) {
|
|
21974
22520
|
return { text: t().orchestration.noCurrentSession };
|
|
21975
22521
|
}
|
|
22522
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
21976
22523
|
const orchestration3 = getOrchestration(context);
|
|
21977
22524
|
if (!orchestration3) {
|
|
21978
22525
|
return { text: renderOrchestrationUnavailable() };
|
|
21979
22526
|
}
|
|
21980
22527
|
const groups = await orchestration3.listGroupSummaries({
|
|
21981
|
-
coordinatorSession
|
|
22528
|
+
coordinatorSession,
|
|
21982
22529
|
...filter ?? {}
|
|
21983
22530
|
});
|
|
21984
22531
|
return { text: renderGroupList(groups) };
|
|
@@ -21988,13 +22535,14 @@ async function handleGroupGet(context, chatKey, groupId) {
|
|
|
21988
22535
|
if (!session3) {
|
|
21989
22536
|
return { text: t().orchestration.noCurrentSession };
|
|
21990
22537
|
}
|
|
22538
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
21991
22539
|
const orchestration3 = getOrchestration(context);
|
|
21992
22540
|
if (!orchestration3) {
|
|
21993
22541
|
return { text: renderOrchestrationUnavailable() };
|
|
21994
22542
|
}
|
|
21995
22543
|
const group = await orchestration3.getGroupSummary({
|
|
21996
22544
|
groupId,
|
|
21997
|
-
coordinatorSession
|
|
22545
|
+
coordinatorSession
|
|
21998
22546
|
});
|
|
21999
22547
|
if (!group) {
|
|
22000
22548
|
return { text: t().orchestration.groupNotFound };
|
|
@@ -22006,20 +22554,21 @@ async function handleGroupCancel(context, chatKey, groupId) {
|
|
|
22006
22554
|
if (!session3) {
|
|
22007
22555
|
return { text: t().orchestration.noCurrentSession };
|
|
22008
22556
|
}
|
|
22557
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22009
22558
|
const orchestration3 = getOrchestration(context);
|
|
22010
22559
|
if (!orchestration3) {
|
|
22011
22560
|
return { text: renderOrchestrationUnavailable() };
|
|
22012
22561
|
}
|
|
22013
22562
|
const group = await orchestration3.getGroupSummary({
|
|
22014
22563
|
groupId,
|
|
22015
|
-
coordinatorSession
|
|
22564
|
+
coordinatorSession
|
|
22016
22565
|
});
|
|
22017
22566
|
if (!group) {
|
|
22018
22567
|
return { text: t().orchestration.groupNotFound };
|
|
22019
22568
|
}
|
|
22020
22569
|
const cancelled = await orchestration3.cancelGroup({
|
|
22021
22570
|
groupId,
|
|
22022
|
-
coordinatorSession
|
|
22571
|
+
coordinatorSession
|
|
22023
22572
|
});
|
|
22024
22573
|
return { text: renderGroupCancelSuccess(cancelled) };
|
|
22025
22574
|
}
|
|
@@ -22028,13 +22577,14 @@ async function handleGroupDelegate(context, chatKey, groupId, targetAgent, task,
|
|
|
22028
22577
|
if (!session3) {
|
|
22029
22578
|
return { text: t().orchestration.noCurrentSession };
|
|
22030
22579
|
}
|
|
22580
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22031
22581
|
const orchestration3 = getOrchestration(context);
|
|
22032
22582
|
if (!orchestration3) {
|
|
22033
22583
|
return { text: renderOrchestrationUnavailable() };
|
|
22034
22584
|
}
|
|
22035
22585
|
const group = await orchestration3.getGroupSummary({
|
|
22036
22586
|
groupId,
|
|
22037
|
-
coordinatorSession
|
|
22587
|
+
coordinatorSession
|
|
22038
22588
|
});
|
|
22039
22589
|
if (!group) {
|
|
22040
22590
|
return { text: t().orchestration.groupNotFound };
|
|
@@ -22046,12 +22596,13 @@ async function handleTaskList(context, chatKey, filter) {
|
|
|
22046
22596
|
if (!session3) {
|
|
22047
22597
|
return { text: t().orchestration.noCurrentSession };
|
|
22048
22598
|
}
|
|
22599
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22049
22600
|
const orchestration3 = getOrchestration(context);
|
|
22050
22601
|
if (!orchestration3) {
|
|
22051
22602
|
return { text: renderOrchestrationUnavailable() };
|
|
22052
22603
|
}
|
|
22053
22604
|
const tasks = await orchestration3.listTasks({
|
|
22054
|
-
coordinatorSession
|
|
22605
|
+
coordinatorSession,
|
|
22055
22606
|
...filter ?? {}
|
|
22056
22607
|
});
|
|
22057
22608
|
return { text: renderTaskList2(tasks) };
|
|
@@ -22061,12 +22612,13 @@ async function handleTaskGet(context, chatKey, taskId) {
|
|
|
22061
22612
|
if (!session3) {
|
|
22062
22613
|
return { text: t().orchestration.noCurrentSession };
|
|
22063
22614
|
}
|
|
22615
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22064
22616
|
const orchestration3 = getOrchestration(context);
|
|
22065
22617
|
if (!orchestration3) {
|
|
22066
22618
|
return { text: renderOrchestrationUnavailable() };
|
|
22067
22619
|
}
|
|
22068
22620
|
const task = await orchestration3.getTask(taskId);
|
|
22069
|
-
if (!task || task.coordinatorSession
|
|
22621
|
+
if (!task || !sameCoordinatorSession(task.coordinatorSession, coordinatorSession)) {
|
|
22070
22622
|
return { text: t().orchestration.taskNotFound };
|
|
22071
22623
|
}
|
|
22072
22624
|
return { text: renderTaskSummary2(task) };
|
|
@@ -22076,12 +22628,13 @@ async function handleTaskApprove(context, chatKey, taskId) {
|
|
|
22076
22628
|
if (!session3) {
|
|
22077
22629
|
return { text: t().orchestration.noCurrentSession };
|
|
22078
22630
|
}
|
|
22631
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22079
22632
|
const orchestration3 = getOrchestration(context);
|
|
22080
22633
|
if (!orchestration3) {
|
|
22081
22634
|
return { text: renderOrchestrationUnavailable() };
|
|
22082
22635
|
}
|
|
22083
22636
|
const task = await orchestration3.getTask(taskId);
|
|
22084
|
-
if (!task || task.coordinatorSession
|
|
22637
|
+
if (!task || !sameCoordinatorSession(task.coordinatorSession, coordinatorSession)) {
|
|
22085
22638
|
return { text: t().orchestration.taskNotFound };
|
|
22086
22639
|
}
|
|
22087
22640
|
if (task.status !== "needs_confirmation") {
|
|
@@ -22089,7 +22642,7 @@ async function handleTaskApprove(context, chatKey, taskId) {
|
|
|
22089
22642
|
}
|
|
22090
22643
|
const approved = await orchestration3.approveTask({
|
|
22091
22644
|
taskId,
|
|
22092
|
-
coordinatorSession
|
|
22645
|
+
coordinatorSession
|
|
22093
22646
|
});
|
|
22094
22647
|
return { text: renderTaskApprovalSuccess2(approved) };
|
|
22095
22648
|
}
|
|
@@ -22098,12 +22651,13 @@ async function handleTaskReject(context, chatKey, taskId) {
|
|
|
22098
22651
|
if (!session3) {
|
|
22099
22652
|
return { text: t().orchestration.noCurrentSession };
|
|
22100
22653
|
}
|
|
22654
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22101
22655
|
const orchestration3 = getOrchestration(context);
|
|
22102
22656
|
if (!orchestration3) {
|
|
22103
22657
|
return { text: renderOrchestrationUnavailable() };
|
|
22104
22658
|
}
|
|
22105
22659
|
const task = await orchestration3.getTask(taskId);
|
|
22106
|
-
if (!task || task.coordinatorSession
|
|
22660
|
+
if (!task || !sameCoordinatorSession(task.coordinatorSession, coordinatorSession)) {
|
|
22107
22661
|
return { text: t().orchestration.taskNotFound };
|
|
22108
22662
|
}
|
|
22109
22663
|
if (task.status !== "needs_confirmation") {
|
|
@@ -22111,7 +22665,7 @@ async function handleTaskReject(context, chatKey, taskId) {
|
|
|
22111
22665
|
}
|
|
22112
22666
|
const rejected = await orchestration3.cancelTask({
|
|
22113
22667
|
taskId,
|
|
22114
|
-
coordinatorSession
|
|
22668
|
+
coordinatorSession
|
|
22115
22669
|
});
|
|
22116
22670
|
return { text: renderTaskRejectSuccess(rejected) };
|
|
22117
22671
|
}
|
|
@@ -22120,17 +22674,18 @@ async function handleTaskCancel(context, chatKey, taskId) {
|
|
|
22120
22674
|
if (!session3) {
|
|
22121
22675
|
return { text: t().orchestration.noCurrentSession };
|
|
22122
22676
|
}
|
|
22677
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22123
22678
|
const orchestration3 = getOrchestration(context);
|
|
22124
22679
|
if (!orchestration3) {
|
|
22125
22680
|
return { text: renderOrchestrationUnavailable() };
|
|
22126
22681
|
}
|
|
22127
22682
|
const task = await orchestration3.getTask(taskId);
|
|
22128
|
-
if (!task || task.coordinatorSession
|
|
22683
|
+
if (!task || !sameCoordinatorSession(task.coordinatorSession, coordinatorSession)) {
|
|
22129
22684
|
return { text: t().orchestration.taskNotFound };
|
|
22130
22685
|
}
|
|
22131
22686
|
const cancelled = await orchestration3.requestTaskCancellation({
|
|
22132
22687
|
taskId,
|
|
22133
|
-
coordinatorSession
|
|
22688
|
+
coordinatorSession
|
|
22134
22689
|
});
|
|
22135
22690
|
return { text: renderTaskCancelSuccess(cancelled) };
|
|
22136
22691
|
}
|
|
@@ -22139,11 +22694,12 @@ async function handleTasksClean(context, chatKey) {
|
|
|
22139
22694
|
if (!session3) {
|
|
22140
22695
|
return { text: t().orchestration.noCurrentSession };
|
|
22141
22696
|
}
|
|
22697
|
+
const coordinatorSession = stableCoordinatorSession(session3.transportSession);
|
|
22142
22698
|
const orchestration3 = getOrchestration(context);
|
|
22143
22699
|
if (!orchestration3) {
|
|
22144
22700
|
return { text: renderOrchestrationUnavailable() };
|
|
22145
22701
|
}
|
|
22146
|
-
const result = await orchestration3.cleanTasks(
|
|
22702
|
+
const result = await orchestration3.cleanTasks(coordinatorSession);
|
|
22147
22703
|
return { text: renderTasksCleanResult(result.removedTasks, result.removedBindings) };
|
|
22148
22704
|
}
|
|
22149
22705
|
async function getCurrentSession(context, chatKey) {
|
|
@@ -22310,7 +22866,7 @@ async function handleWorkspaceCreate(context, workspaceName, cwd, options = {})
|
|
|
22310
22866
|
name = allocateWorkspaceName(base, context.config.workspaces);
|
|
22311
22867
|
notice = w.nameSanitized(workspaceName, name);
|
|
22312
22868
|
}
|
|
22313
|
-
const updated = await context.configStore.upsertWorkspace(name,
|
|
22869
|
+
const updated = await context.configStore.upsertWorkspace(name, cwd);
|
|
22314
22870
|
context.replaceConfig(updated);
|
|
22315
22871
|
const savedLine = w.saved(name);
|
|
22316
22872
|
return { text: notice ? `${notice}
|
|
@@ -22426,10 +22982,10 @@ function validateResult(executeAt, messageStartIndex, tokens, now, pastTodayValu
|
|
|
22426
22982
|
if (tokens.slice(messageStartIndex).join(" ").trim().length === 0)
|
|
22427
22983
|
return { ok: false, code: "missing_message" };
|
|
22428
22984
|
const delta = executeAt.getTime() - now.getTime();
|
|
22985
|
+
if (isNaN(delta) || delta > LATER_MAX_DELAY_MS)
|
|
22986
|
+
return { ok: false, code: "out_of_range" };
|
|
22429
22987
|
if (delta < LATER_MIN_DELAY_MS)
|
|
22430
22988
|
return { ok: false, code: "too_soon" };
|
|
22431
|
-
if (delta > LATER_MAX_DELAY_MS)
|
|
22432
|
-
return { ok: false, code: "out_of_range" };
|
|
22433
22989
|
return { ok: true, executeAt, messageStartIndex };
|
|
22434
22990
|
}
|
|
22435
22991
|
var WEEKDAYS, ZH_MIN = "分钟", ZH_HOUR = "小时", ZH_DAY_UNIT = "天", ZH_TODAY = "今天", ZH_TOMORROW = "明天", ZH_DAY_AFTER = "后天", ZH_AFTER = "后", ZH_RELATIVE_RE;
|
|
@@ -22505,16 +23061,17 @@ function laterHelp() {
|
|
|
22505
23061
|
function handleLaterHelp() {
|
|
22506
23062
|
return { text: renderLaterHelp() };
|
|
22507
23063
|
}
|
|
22508
|
-
async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
23064
|
+
async function handleLaterCreate(tokens, tails, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
22509
23065
|
const l = t().later;
|
|
22510
|
-
let
|
|
23066
|
+
let restStart = 0;
|
|
22511
23067
|
const seenFlags = new Set;
|
|
22512
23068
|
let flagMode;
|
|
22513
|
-
while (
|
|
22514
|
-
seenFlags.add(
|
|
22515
|
-
flagMode =
|
|
22516
|
-
|
|
23069
|
+
while (restStart < tokens.length && (tokens[restStart] === "--bind" || tokens[restStart] === "--temp")) {
|
|
23070
|
+
seenFlags.add(tokens[restStart] ?? "");
|
|
23071
|
+
flagMode = tokens[restStart] === "--bind" ? "bound" : "temp";
|
|
23072
|
+
restStart += 1;
|
|
22517
23073
|
}
|
|
23074
|
+
const rest = tokens.slice(restStart);
|
|
22518
23075
|
if (seenFlags.size > 1) {
|
|
22519
23076
|
return { text: l.bindAndTempMutuallyExclusive };
|
|
22520
23077
|
}
|
|
@@ -22535,7 +23092,7 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, def
|
|
|
22535
23092
|
if (!result.ok) {
|
|
22536
23093
|
return { text: renderTimeParseError(result.code, result.value) };
|
|
22537
23094
|
}
|
|
22538
|
-
const message =
|
|
23095
|
+
const message = (tails[restStart + result.messageStartIndex] ?? "").trim();
|
|
22539
23096
|
if (message.startsWith("/")) {
|
|
22540
23097
|
return {
|
|
22541
23098
|
text: [
|
|
@@ -22559,12 +23116,12 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, def
|
|
|
22559
23116
|
});
|
|
22560
23117
|
return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSession.alias)) };
|
|
22561
23118
|
}
|
|
22562
|
-
function handleLaterList(scheduled) {
|
|
22563
|
-
const tasks = scheduled.listPending();
|
|
23119
|
+
function handleLaterList(scheduled, chatKey) {
|
|
23120
|
+
const tasks = scheduled.listPending(chatKey);
|
|
22564
23121
|
return { text: renderLaterList(tasks, (alias) => toDisplaySessionAlias(alias)) };
|
|
22565
23122
|
}
|
|
22566
|
-
async function handleLaterCancel(id, scheduled) {
|
|
22567
|
-
const ok = await scheduled.cancelPending(id);
|
|
23123
|
+
async function handleLaterCancel(id, scheduled, chatKey) {
|
|
23124
|
+
const ok = await scheduled.cancelPending(id, chatKey);
|
|
22568
23125
|
const displayId = id.replace(/^#/, "").toLowerCase();
|
|
22569
23126
|
if (ok) {
|
|
22570
23127
|
return { text: t().later.cancelSuccess(displayId) };
|
|
@@ -23401,7 +23958,7 @@ var init_session_recovery_handler = __esm(() => {
|
|
|
23401
23958
|
// src/recovery/auto-install-optional-dep.ts
|
|
23402
23959
|
import { spawn as spawn5 } from "node:child_process";
|
|
23403
23960
|
import { createWriteStream } from "node:fs";
|
|
23404
|
-
import { mkdir as
|
|
23961
|
+
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
23405
23962
|
import { homedir as homedir6 } from "node:os";
|
|
23406
23963
|
import { join as join14 } from "node:path";
|
|
23407
23964
|
async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
|
|
@@ -23519,7 +24076,7 @@ ${err.message}`, reason: "spawn" });
|
|
|
23519
24076
|
});
|
|
23520
24077
|
}, defaultLogSink = async () => {
|
|
23521
24078
|
const dir = join14(coreHomeDir(homedir6()), "logs");
|
|
23522
|
-
await
|
|
24079
|
+
await mkdir8(dir, { recursive: true });
|
|
23523
24080
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
|
|
23524
24081
|
const path14 = join14(dir, `auto-install-${timestamp}.log`);
|
|
23525
24082
|
const stream = createWriteStream(path14, { flags: "a" });
|
|
@@ -23548,7 +24105,7 @@ import { spawn as spawn6 } from "node:child_process";
|
|
|
23548
24105
|
import { createRequire as createRequire3 } from "node:module";
|
|
23549
24106
|
import { access as access3 } from "node:fs/promises";
|
|
23550
24107
|
import { homedir as homedir7 } from "node:os";
|
|
23551
|
-
import { dirname as
|
|
24108
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
23552
24109
|
function deriveParentPackageName(platformPackage) {
|
|
23553
24110
|
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
23554
24111
|
}
|
|
@@ -23621,7 +24178,7 @@ function defaultResolveFromCwd(name, cwd) {
|
|
|
23621
24178
|
const pkgJson = require2.resolve(`${name}/package.json`, {
|
|
23622
24179
|
paths: [cwd, ...require2.resolve.paths(name) ?? []]
|
|
23623
24180
|
});
|
|
23624
|
-
return
|
|
24181
|
+
return dirname10(pkgJson);
|
|
23625
24182
|
} catch {
|
|
23626
24183
|
return null;
|
|
23627
24184
|
}
|
|
@@ -23756,11 +24313,11 @@ async function handleSessionResetCommand(context, ops, chatKey) {
|
|
|
23756
24313
|
chatKey,
|
|
23757
24314
|
native: wasNative && Boolean(freshAgentSessionId)
|
|
23758
24315
|
});
|
|
23759
|
-
if (
|
|
24316
|
+
if (context.transport.removeSession && context.sessions.countAliasesSharingTransport(previous.transportSession) === 0) {
|
|
23760
24317
|
try {
|
|
23761
24318
|
await context.transport.removeSession(previous);
|
|
23762
24319
|
} catch (error2) {
|
|
23763
|
-
await context.logger.info("session.reset.close_previous_failed", "failed to close previous
|
|
24320
|
+
await context.logger.info("session.reset.close_previous_failed", "failed to close previous session after reset", {
|
|
23764
24321
|
transportSession: previous.transportSession,
|
|
23765
24322
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
23766
24323
|
});
|
|
@@ -23822,9 +24379,20 @@ class CommandRouter {
|
|
|
23822
24379
|
chatKey,
|
|
23823
24380
|
kind: command.kind
|
|
23824
24381
|
});
|
|
24382
|
+
await this.refreshConfigFromStore();
|
|
24383
|
+
perfSpan?.mark("router.config_refreshed");
|
|
24384
|
+
metadata = withEffectiveOwner(metadata, this.config);
|
|
23825
24385
|
const access4 = authorizeCommandForChat(command, metadata);
|
|
23826
24386
|
perfSpan?.mark("router.authorized", { decision: access4.allowed ? "allow" : "deny" });
|
|
23827
24387
|
if (!access4.allowed) {
|
|
24388
|
+
if (access4.reason === "chat-type-missing") {
|
|
24389
|
+
await this.logger.error("channel.chat_type_missing", "channel turn carried no chatType; denying privileged command (channel metadata contract violation)", {
|
|
24390
|
+
chatKey,
|
|
24391
|
+
kind: command.kind,
|
|
24392
|
+
channel: metadata?.channel,
|
|
24393
|
+
senderId: metadata?.senderId
|
|
24394
|
+
});
|
|
24395
|
+
}
|
|
23828
24396
|
await this.logger.info("command.blocked", "blocked command by chat policy", {
|
|
23829
24397
|
chatKey,
|
|
23830
24398
|
kind: command.kind,
|
|
@@ -23832,10 +24400,8 @@ class CommandRouter {
|
|
|
23832
24400
|
channel: metadata?.channel,
|
|
23833
24401
|
senderId: metadata?.senderId
|
|
23834
24402
|
});
|
|
23835
|
-
return { text: renderCommandAccessDenied(command) };
|
|
24403
|
+
return { text: renderCommandAccessDenied(command, access4.reason) };
|
|
23836
24404
|
}
|
|
23837
|
-
await this.refreshConfigFromStore();
|
|
23838
|
-
perfSpan?.mark("router.config_refreshed");
|
|
23839
24405
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
23840
24406
|
switch (command.kind) {
|
|
23841
24407
|
case "invalid":
|
|
@@ -23937,7 +24503,7 @@ class CommandRouter {
|
|
|
23937
24503
|
case "later.list":
|
|
23938
24504
|
if (!this.scheduled)
|
|
23939
24505
|
return { text: t().later.serviceNotEnabled };
|
|
23940
|
-
return handleLaterList(this.scheduled);
|
|
24506
|
+
return handleLaterList(this.scheduled, chatKey);
|
|
23941
24507
|
case "later.create": {
|
|
23942
24508
|
if (!this.scheduled)
|
|
23943
24509
|
return { text: t().later.serviceNotEnabled };
|
|
@@ -23945,12 +24511,12 @@ class CommandRouter {
|
|
|
23945
24511
|
return { text: renderLaterUnsupportedChannel() };
|
|
23946
24512
|
}
|
|
23947
24513
|
const currentSession = await this.sessions.getCurrentSession(chatKey);
|
|
23948
|
-
return await handleLaterCreate(command.tokens, this.scheduled, chatKey, currentSession ? { alias: currentSession.alias, agent: currentSession.agent, workspace: currentSession.workspace } : null, this.config?.later?.defaultMode === "bind" ? "bound" : "temp", accountId, replyContextToken);
|
|
24514
|
+
return await handleLaterCreate(command.tokens, command.tails, this.scheduled, chatKey, currentSession ? { alias: currentSession.alias, agent: currentSession.agent, workspace: currentSession.workspace } : null, this.config?.later?.defaultMode === "bind" ? "bound" : "temp", accountId, replyContextToken);
|
|
23949
24515
|
}
|
|
23950
24516
|
case "later.cancel":
|
|
23951
24517
|
if (!this.scheduled)
|
|
23952
24518
|
return { text: t().later.serviceNotEnabled };
|
|
23953
|
-
return await handleLaterCancel(command.id, this.scheduled);
|
|
24519
|
+
return await handleLaterCancel(command.id, this.scheduled, chatKey);
|
|
23954
24520
|
case "prompt": {
|
|
23955
24521
|
const sessionContext = this.createSessionHandlerContext(undefined, perfSpan);
|
|
23956
24522
|
if (metadata?.scheduledSessionDescriptor) {
|
|
@@ -24204,7 +24770,7 @@ class CommandRouter {
|
|
|
24204
24770
|
return await this.measureTransportCall("has_session", session3, () => this.transport.hasSession(session3));
|
|
24205
24771
|
}
|
|
24206
24772
|
async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
24207
|
-
session3.mcpCoordinatorSession ??= session3.transportSession;
|
|
24773
|
+
session3.mcpCoordinatorSession ??= stableCoordinatorSession(session3.transportSession);
|
|
24208
24774
|
let done = false;
|
|
24209
24775
|
let abortRequested = false;
|
|
24210
24776
|
let cancelOnAbort;
|
|
@@ -24454,8 +25020,8 @@ var init_console_agent = __esm(() => {
|
|
|
24454
25020
|
});
|
|
24455
25021
|
|
|
24456
25022
|
// src/orchestration/orchestration-server.ts
|
|
24457
|
-
import { rm as rm8 } from "node:fs/promises";
|
|
24458
|
-
import {
|
|
25023
|
+
import { chmod as chmod4, rm as rm8 } from "node:fs/promises";
|
|
25024
|
+
import { createServer } from "node:net";
|
|
24459
25025
|
|
|
24460
25026
|
class OrchestrationServer {
|
|
24461
25027
|
endpoint;
|
|
@@ -24501,6 +25067,7 @@ class OrchestrationServer {
|
|
|
24501
25067
|
});
|
|
24502
25068
|
});
|
|
24503
25069
|
await this.listenWithUnixSocketRecovery();
|
|
25070
|
+
await this.hardenUnixSocketPermissions();
|
|
24504
25071
|
this.started = true;
|
|
24505
25072
|
}
|
|
24506
25073
|
async stop() {
|
|
@@ -24683,23 +25250,25 @@ class OrchestrationServer {
|
|
|
24683
25250
|
if (!task) {
|
|
24684
25251
|
return null;
|
|
24685
25252
|
}
|
|
24686
|
-
if (task.coordinatorSession
|
|
25253
|
+
if (!sameCoordinatorSession(task.coordinatorSession, coordinatorSession)) {
|
|
24687
25254
|
return null;
|
|
24688
25255
|
}
|
|
24689
25256
|
return task;
|
|
24690
25257
|
}
|
|
24691
25258
|
parseRequestDelegateRpcInput(params) {
|
|
24692
|
-
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId"], "params");
|
|
25259
|
+
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId", "parallel"], "params");
|
|
24693
25260
|
const cwd = requireOptionalString(params, "cwd");
|
|
24694
25261
|
const role = requireOptionalString(params, "role");
|
|
24695
25262
|
const groupId = requireOptionalString(params, "groupId");
|
|
25263
|
+
const parallel = requireOptionalBoolean(params, "parallel");
|
|
24696
25264
|
return {
|
|
24697
25265
|
sourceHandle: requireString(params, "sourceHandle"),
|
|
24698
25266
|
targetAgent: requireString(params, "targetAgent"),
|
|
24699
25267
|
task: requireString(params, "task"),
|
|
24700
25268
|
...cwd !== undefined ? { cwd } : {},
|
|
24701
25269
|
...role !== undefined ? { role } : {},
|
|
24702
|
-
...groupId !== undefined ? { groupId } : {}
|
|
25270
|
+
...groupId !== undefined ? { groupId } : {},
|
|
25271
|
+
...parallel !== undefined ? { parallel } : {}
|
|
24703
25272
|
};
|
|
24704
25273
|
}
|
|
24705
25274
|
parseTaskListFilter(params) {
|
|
@@ -24782,6 +25351,17 @@ class OrchestrationServer {
|
|
|
24782
25351
|
whatIsNeeded: requireString(params, "whatIsNeeded")
|
|
24783
25352
|
};
|
|
24784
25353
|
}
|
|
25354
|
+
async hardenUnixSocketPermissions() {
|
|
25355
|
+
if (this.endpoint.kind !== "unix") {
|
|
25356
|
+
return;
|
|
25357
|
+
}
|
|
25358
|
+
const chmodFile = this.deps.chmodFile ?? chmod4;
|
|
25359
|
+
try {
|
|
25360
|
+
await chmodFile(this.endpoint.path, 384);
|
|
25361
|
+
} catch (error2) {
|
|
25362
|
+
this.deps.onSocketHardenError?.(error2);
|
|
25363
|
+
}
|
|
25364
|
+
}
|
|
24785
25365
|
async cleanupEndpoint() {
|
|
24786
25366
|
if (this.endpoint.kind !== "unix") {
|
|
24787
25367
|
return;
|
|
@@ -24948,29 +25528,6 @@ function requireTaskQuestions(params, key) {
|
|
|
24948
25528
|
};
|
|
24949
25529
|
});
|
|
24950
25530
|
}
|
|
24951
|
-
async function canConnectToEndpoint(path14) {
|
|
24952
|
-
return await new Promise((resolve3) => {
|
|
24953
|
-
const socket = createConnection2(path14);
|
|
24954
|
-
let settled = false;
|
|
24955
|
-
const finish = (result) => {
|
|
24956
|
-
if (settled) {
|
|
24957
|
-
return;
|
|
24958
|
-
}
|
|
24959
|
-
settled = true;
|
|
24960
|
-
socket.destroy();
|
|
24961
|
-
resolve3(result);
|
|
24962
|
-
};
|
|
24963
|
-
socket.once("connect", () => finish(true));
|
|
24964
|
-
socket.once("error", (error2) => {
|
|
24965
|
-
const code = error2.code;
|
|
24966
|
-
if (code === "ENOENT" || code === "ECONNREFUSED") {
|
|
24967
|
-
finish(false);
|
|
24968
|
-
return;
|
|
24969
|
-
}
|
|
24970
|
-
finish(true);
|
|
24971
|
-
});
|
|
24972
|
-
});
|
|
24973
|
-
}
|
|
24974
25531
|
async function listen(server, path14) {
|
|
24975
25532
|
await new Promise((resolve3, reject) => {
|
|
24976
25533
|
const onError = (error2) => {
|
|
@@ -24993,6 +25550,7 @@ var OrchestrationInvalidRequestError, ORCHESTRATION_RPC_METHODS;
|
|
|
24993
25550
|
var init_orchestration_server = __esm(() => {
|
|
24994
25551
|
init_orchestration_ipc();
|
|
24995
25552
|
init_task_watch_timeouts();
|
|
25553
|
+
init_endpoint_probe();
|
|
24996
25554
|
OrchestrationInvalidRequestError = class OrchestrationInvalidRequestError extends Error {
|
|
24997
25555
|
};
|
|
24998
25556
|
ORCHESTRATION_RPC_METHODS = new Set([
|
|
@@ -25193,7 +25751,7 @@ class OrchestrationService {
|
|
|
25193
25751
|
async getGroupSummary(input) {
|
|
25194
25752
|
const state = await this.deps.loadState();
|
|
25195
25753
|
const group = this.ensureGroups(state)[input.groupId];
|
|
25196
|
-
if (!group || group.coordinatorSession
|
|
25754
|
+
if (!group || !sameCoordinatorSession(group.coordinatorSession, input.coordinatorSession)) {
|
|
25197
25755
|
return null;
|
|
25198
25756
|
}
|
|
25199
25757
|
return this.buildGroupSummary(group, Object.values(state.orchestration.tasks).filter((task) => task.groupId === input.groupId));
|
|
@@ -25205,7 +25763,7 @@ class OrchestrationService {
|
|
|
25205
25763
|
const now = this.deps.now().getTime();
|
|
25206
25764
|
const sortField = input.sort ?? "updatedAt";
|
|
25207
25765
|
const order = input.order ?? "desc";
|
|
25208
|
-
return Object.values(this.ensureGroups(state)).filter((group) => group.coordinatorSession
|
|
25766
|
+
return Object.values(this.ensureGroups(state)).filter((group) => sameCoordinatorSession(group.coordinatorSession, input.coordinatorSession)).map((group) => ({
|
|
25209
25767
|
group,
|
|
25210
25768
|
summary: this.buildGroupSummary(group, tasks.filter((task) => task.groupId === group.groupId))
|
|
25211
25769
|
})).filter(({ summary }) => {
|
|
@@ -25695,7 +26253,7 @@ class OrchestrationService {
|
|
|
25695
26253
|
const workerSession = task.workerSession;
|
|
25696
26254
|
const taskStillOwnsWorkerSession = current?.workerSession === workerSession;
|
|
25697
26255
|
const currentBinding = state.orchestration.workerBindings[workerSession];
|
|
25698
|
-
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && currentBinding.coordinatorSession
|
|
26256
|
+
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && sameCoordinatorSession(currentBinding.coordinatorSession, task.coordinatorSession) && currentBinding.workspace === task.workspace && currentBinding.cwd === task.cwd && currentBinding.targetAgent === task.targetAgent && currentBinding.role === task.role;
|
|
25699
26257
|
const otherActiveOwner = Object.values(state.orchestration.tasks).some((candidate) => candidate.taskId !== task.taskId && candidate.workerSession === workerSession && (!this.isTerminalStatus(candidate.status) || candidate.reviewPending !== undefined));
|
|
25700
26258
|
const restoreOrDeleteBinding = () => {
|
|
25701
26259
|
if (!bindingStillBelongsToThisStartup || otherActiveOwner) {
|
|
@@ -25754,7 +26312,7 @@ class OrchestrationService {
|
|
|
25754
26312
|
current.updatedAt = now;
|
|
25755
26313
|
this.bumpGroupUpdated(state, current.groupId, now);
|
|
25756
26314
|
const currentBinding = state.orchestration.workerBindings[workerSession];
|
|
25757
|
-
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && currentBinding.coordinatorSession
|
|
26315
|
+
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && sameCoordinatorSession(currentBinding.coordinatorSession, task.coordinatorSession) && currentBinding.workspace === task.workspace && currentBinding.cwd === task.cwd && currentBinding.targetAgent === task.targetAgent && currentBinding.role === task.role;
|
|
25758
26316
|
const otherActiveOwner = Object.values(state.orchestration.tasks).some((candidate) => candidate.taskId !== task.taskId && candidate.workerSession === workerSession && (!this.isTerminalStatus(candidate.status) || candidate.reviewPending !== undefined));
|
|
25759
26317
|
if (bindingStillBelongsToThisStartup && !otherActiveOwner) {
|
|
25760
26318
|
if (input.previousBinding) {
|
|
@@ -25773,7 +26331,7 @@ class OrchestrationService {
|
|
|
25773
26331
|
const state = await this.deps.loadState();
|
|
25774
26332
|
const workerSession = task.workerSession;
|
|
25775
26333
|
const currentBinding = state.orchestration.workerBindings[workerSession];
|
|
25776
|
-
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && currentBinding.coordinatorSession
|
|
26334
|
+
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && sameCoordinatorSession(currentBinding.coordinatorSession, task.coordinatorSession) && currentBinding.workspace === task.workspace && currentBinding.cwd === task.cwd && currentBinding.targetAgent === task.targetAgent && currentBinding.role === task.role;
|
|
25777
26335
|
if (!bindingStillBelongsToThisStartup) {
|
|
25778
26336
|
return false;
|
|
25779
26337
|
}
|
|
@@ -25946,7 +26504,7 @@ class OrchestrationService {
|
|
|
25946
26504
|
while (true) {
|
|
25947
26505
|
const state = await this.deps.loadState();
|
|
25948
26506
|
const task = state.orchestration.tasks[input.taskId];
|
|
25949
|
-
if (!task || task.coordinatorSession
|
|
26507
|
+
if (!task || !sameCoordinatorSession(task.coordinatorSession, input.coordinatorSession)) {
|
|
25950
26508
|
return { status: "not_found", task: null, events: [], nextAfterSeq: afterSeq };
|
|
25951
26509
|
}
|
|
25952
26510
|
const snapshot = { ...task };
|
|
@@ -26004,7 +26562,8 @@ class OrchestrationService {
|
|
|
26004
26562
|
return await this.mutate(async () => {
|
|
26005
26563
|
const state = await this.deps.loadState();
|
|
26006
26564
|
const now = this.deps.now().toISOString();
|
|
26007
|
-
const
|
|
26565
|
+
const routeKey = stableCoordinatorSession(input.coordinatorSession);
|
|
26566
|
+
const existing = this.ensureCoordinatorRoutes(state)[routeKey];
|
|
26008
26567
|
const sameChat = existing?.chatKey === input.chatKey;
|
|
26009
26568
|
const hasAccountId = input.accountId !== undefined;
|
|
26010
26569
|
const hasReplyContextToken = input.replyContextToken !== undefined;
|
|
@@ -26018,14 +26577,14 @@ class OrchestrationService {
|
|
|
26018
26577
|
replyContextToken: existing.replyContextToken
|
|
26019
26578
|
} : undefined;
|
|
26020
26579
|
const route = {
|
|
26021
|
-
coordinatorSession:
|
|
26580
|
+
coordinatorSession: routeKey,
|
|
26022
26581
|
chatKey: input.chatKey,
|
|
26023
26582
|
...input.sessionAlias ? { sessionAlias: input.sessionAlias } : {},
|
|
26024
26583
|
...replyRoute ? replyRoute : {},
|
|
26025
26584
|
...buildCoordinatorRouteChatMetadata(input, sameChat ? existing : undefined),
|
|
26026
26585
|
updatedAt: now
|
|
26027
26586
|
};
|
|
26028
|
-
this.ensureCoordinatorRoutes(state)[
|
|
26587
|
+
this.ensureCoordinatorRoutes(state)[routeKey] = route;
|
|
26029
26588
|
await this.deps.saveState(state);
|
|
26030
26589
|
return { ...route };
|
|
26031
26590
|
});
|
|
@@ -26269,7 +26828,7 @@ class OrchestrationService {
|
|
|
26269
26828
|
return task;
|
|
26270
26829
|
});
|
|
26271
26830
|
const now = this.deps.now().toISOString();
|
|
26272
|
-
const route = this.snapshotCoordinatorDeliveryRoute(this.ensureCoordinatorRoutes(state)[input.coordinatorSession]);
|
|
26831
|
+
const route = this.snapshotCoordinatorDeliveryRoute(this.ensureCoordinatorRoutes(state)[stableCoordinatorSession(input.coordinatorSession)]);
|
|
26273
26832
|
if (coordinatorState.activePackageId) {
|
|
26274
26833
|
const activePackage = this.ensureHumanQuestionPackages(state)[coordinatorState.activePackageId];
|
|
26275
26834
|
if (!activePackage) {
|
|
@@ -26371,7 +26930,7 @@ class OrchestrationService {
|
|
|
26371
26930
|
if (!packageRecord) {
|
|
26372
26931
|
throw new Error(`package "${input.packageId}" does not exist`);
|
|
26373
26932
|
}
|
|
26374
|
-
if (packageRecord.coordinatorSession
|
|
26933
|
+
if (!sameCoordinatorSession(packageRecord.coordinatorSession, input.coordinatorSession)) {
|
|
26375
26934
|
throw new Error(`package "${input.packageId}" belongs to coordinator "${packageRecord.coordinatorSession}", not "${input.coordinatorSession}"`);
|
|
26376
26935
|
}
|
|
26377
26936
|
if (packageRecord.status !== "active") {
|
|
@@ -26386,7 +26945,7 @@ class OrchestrationService {
|
|
|
26386
26945
|
}
|
|
26387
26946
|
let route = this.resolveFrozenPackageMessageRoute(message);
|
|
26388
26947
|
if (!route) {
|
|
26389
|
-
route = this.snapshotCoordinatorDeliveryRoute(this.ensureCoordinatorRoutes(state)[input.coordinatorSession]) ?? null;
|
|
26948
|
+
route = this.snapshotCoordinatorDeliveryRoute(this.ensureCoordinatorRoutes(state)[stableCoordinatorSession(input.coordinatorSession)]) ?? null;
|
|
26390
26949
|
if (route) {
|
|
26391
26950
|
Object.assign(message, this.serializeFrozenDeliveryRoute(route));
|
|
26392
26951
|
}
|
|
@@ -26455,7 +27014,7 @@ class OrchestrationService {
|
|
|
26455
27014
|
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
26456
27015
|
return null;
|
|
26457
27016
|
}
|
|
26458
|
-
const coordinatorState = state.orchestration.coordinatorQuestionState[coordinatorSession];
|
|
27017
|
+
const coordinatorState = state.orchestration.coordinatorQuestionState[stableCoordinatorSession(coordinatorSession)];
|
|
26459
27018
|
const activePackageId = coordinatorState?.activePackageId;
|
|
26460
27019
|
if (!activePackageId) {
|
|
26461
27020
|
return null;
|
|
@@ -26595,7 +27154,7 @@ class OrchestrationService {
|
|
|
26595
27154
|
const bindings = state.orchestration.workerBindings;
|
|
26596
27155
|
const terminalTaskIds = [];
|
|
26597
27156
|
for (const [taskId, task] of Object.entries(tasks)) {
|
|
26598
|
-
if (task.coordinatorSession
|
|
27157
|
+
if (sameCoordinatorSession(task.coordinatorSession, coordinatorSession) && this.isTerminalStatus(task.status) && task.reviewPending === undefined) {
|
|
26599
27158
|
terminalTaskIds.push(taskId);
|
|
26600
27159
|
}
|
|
26601
27160
|
}
|
|
@@ -26605,7 +27164,7 @@ class OrchestrationService {
|
|
|
26605
27164
|
const remainingWorkerSessions = new Set(Object.values(tasks).map((task) => task.workerSession).filter(Boolean));
|
|
26606
27165
|
let removedBindings = 0;
|
|
26607
27166
|
for (const [workerSession, binding] of Object.entries(bindings)) {
|
|
26608
|
-
if (binding.coordinatorSession
|
|
27167
|
+
if (!sameCoordinatorSession(binding.coordinatorSession, coordinatorSession)) {
|
|
26609
27168
|
continue;
|
|
26610
27169
|
}
|
|
26611
27170
|
if (!remainingWorkerSessions.has(workerSession)) {
|
|
@@ -26625,16 +27184,17 @@ class OrchestrationService {
|
|
|
26625
27184
|
}
|
|
26626
27185
|
async listSessionBlockingTasks(transportSession) {
|
|
26627
27186
|
const state = await this.deps.loadState();
|
|
26628
|
-
return Object.values(state.orchestration.tasks).filter((task) => (!this.isTerminalStatus(task.status) || task.reviewPending !== undefined) && (task.coordinatorSession
|
|
27187
|
+
return Object.values(state.orchestration.tasks).filter((task) => (!this.isTerminalStatus(task.status) || task.reviewPending !== undefined) && (sameCoordinatorSession(task.coordinatorSession, transportSession) || task.workerSession !== undefined && sameCoordinatorSession(task.workerSession, transportSession))).map((task) => ({ ...task }));
|
|
26629
27188
|
}
|
|
26630
27189
|
async purgeSessionReferences(transportSession) {
|
|
26631
27190
|
return await this.mutate(async () => {
|
|
26632
27191
|
const state = await this.deps.loadState();
|
|
27192
|
+
const sessionIdentity = stableCoordinatorSession(transportSession);
|
|
26633
27193
|
const tasks = state.orchestration.tasks;
|
|
26634
27194
|
const bindings = state.orchestration.workerBindings;
|
|
26635
27195
|
const removedTaskIds = [];
|
|
26636
27196
|
for (const [taskId, task] of Object.entries(tasks)) {
|
|
26637
|
-
if (this.isTerminalStatus(task.status) && task.reviewPending === undefined && (task.coordinatorSession
|
|
27197
|
+
if (this.isTerminalStatus(task.status) && task.reviewPending === undefined && (sameCoordinatorSession(task.coordinatorSession, transportSession) || task.workerSession !== undefined && sameCoordinatorSession(task.workerSession, transportSession))) {
|
|
26638
27198
|
removedTaskIds.push(taskId);
|
|
26639
27199
|
}
|
|
26640
27200
|
}
|
|
@@ -26644,14 +27204,14 @@ class OrchestrationService {
|
|
|
26644
27204
|
const remainingWorkerSessions = new Set(Object.values(tasks).map((task) => task.workerSession).filter(Boolean));
|
|
26645
27205
|
let removedBindings = 0;
|
|
26646
27206
|
for (const [workerSession, binding] of Object.entries(bindings)) {
|
|
26647
|
-
const shouldPurgeBinding = workerSession
|
|
27207
|
+
const shouldPurgeBinding = sameCoordinatorSession(workerSession, transportSession) || sameCoordinatorSession(binding.coordinatorSession, transportSession);
|
|
26648
27208
|
if (shouldPurgeBinding && !remainingWorkerSessions.has(workerSession)) {
|
|
26649
27209
|
delete bindings[workerSession];
|
|
26650
27210
|
removedBindings += 1;
|
|
26651
27211
|
}
|
|
26652
27212
|
}
|
|
26653
|
-
const removedEmptyGroups = this.removeEmptyGroupsForCoordinator(state,
|
|
26654
|
-
const removedCoordinatorMetadata = this.removeCoordinatorMetadataIfUnused(state,
|
|
27213
|
+
const removedEmptyGroups = this.removeEmptyGroupsForCoordinator(state, sessionIdentity);
|
|
27214
|
+
const removedCoordinatorMetadata = this.removeCoordinatorMetadataIfUnused(state, sessionIdentity);
|
|
26655
27215
|
if (removedTaskIds.length > 0 || removedBindings > 0 || removedEmptyGroups || removedCoordinatorMetadata) {
|
|
26656
27216
|
await this.deps.saveState(state);
|
|
26657
27217
|
}
|
|
@@ -26661,106 +27221,28 @@ class OrchestrationService {
|
|
|
26661
27221
|
};
|
|
26662
27222
|
});
|
|
26663
27223
|
}
|
|
26664
|
-
async purgeExpiredResetCoordinators(input) {
|
|
26665
|
-
try {
|
|
26666
|
-
if (!Number.isFinite(input.cutoffDays) || input.cutoffDays < 0) {
|
|
26667
|
-
throw new Error(`cutoffDays must be a non-negative number, got ${String(input.cutoffDays)}`);
|
|
26668
|
-
}
|
|
26669
|
-
const result = await this.mutate(async () => {
|
|
26670
|
-
const state = await this.deps.loadState();
|
|
26671
|
-
const candidates = this.collectResetCoordinatorCandidates(state);
|
|
26672
|
-
const activeTransportSessions = new Set(Object.values(state.sessions).map((session3) => session3.transport_session));
|
|
26673
|
-
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
26674
|
-
const cutoffMs = input.cutoffDays * MS_PER_DAY;
|
|
26675
|
-
const nowMs = this.deps.now().getTime();
|
|
26676
|
-
let purgedCoordinators = 0;
|
|
26677
|
-
const removed = {
|
|
26678
|
-
tasks: 0,
|
|
26679
|
-
workerBindings: 0,
|
|
26680
|
-
groups: 0,
|
|
26681
|
-
coordinatorRoutes: 0,
|
|
26682
|
-
humanQuestionPackages: 0,
|
|
26683
|
-
coordinatorQuestionState: 0
|
|
26684
|
-
};
|
|
26685
|
-
for (const coordinatorSession of candidates) {
|
|
26686
|
-
if (activeTransportSessions.has(coordinatorSession)) {
|
|
26687
|
-
continue;
|
|
26688
|
-
}
|
|
26689
|
-
const activityAtMs = this.resolveResetCoordinatorActivityAtMs(state, coordinatorSession);
|
|
26690
|
-
if (activityAtMs === null) {
|
|
26691
|
-
continue;
|
|
26692
|
-
}
|
|
26693
|
-
if (nowMs - activityAtMs <= cutoffMs) {
|
|
26694
|
-
continue;
|
|
26695
|
-
}
|
|
26696
|
-
const delta = this.cascadeRemoveCoordinatorRecords(state, coordinatorSession);
|
|
26697
|
-
const changed = delta.tasks > 0 || delta.workerBindings > 0 || delta.groups > 0 || delta.coordinatorRoutes > 0 || delta.humanQuestionPackages > 0 || delta.coordinatorQuestionState > 0;
|
|
26698
|
-
if (!changed) {
|
|
26699
|
-
continue;
|
|
26700
|
-
}
|
|
26701
|
-
purgedCoordinators += 1;
|
|
26702
|
-
removed.tasks += delta.tasks;
|
|
26703
|
-
removed.workerBindings += delta.workerBindings;
|
|
26704
|
-
removed.groups += delta.groups;
|
|
26705
|
-
removed.coordinatorRoutes += delta.coordinatorRoutes;
|
|
26706
|
-
removed.humanQuestionPackages += delta.humanQuestionPackages;
|
|
26707
|
-
removed.coordinatorQuestionState += delta.coordinatorQuestionState;
|
|
26708
|
-
}
|
|
26709
|
-
const removedAny = removed.tasks > 0 || removed.workerBindings > 0 || removed.groups > 0 || removed.coordinatorRoutes > 0 || removed.humanQuestionPackages > 0 || removed.coordinatorQuestionState > 0;
|
|
26710
|
-
if (removedAny) {
|
|
26711
|
-
await this.deps.saveState(state);
|
|
26712
|
-
}
|
|
26713
|
-
return {
|
|
26714
|
-
candidates: candidates.length,
|
|
26715
|
-
purgedCoordinators,
|
|
26716
|
-
removed
|
|
26717
|
-
};
|
|
26718
|
-
});
|
|
26719
|
-
if (this.deps.logger) {
|
|
26720
|
-
this.deps.logger.info("orchestration.reset_gc.completed", "reset coordinator gc completed", {
|
|
26721
|
-
trigger: input.trigger,
|
|
26722
|
-
cutoffDays: input.cutoffDays,
|
|
26723
|
-
candidates: result.candidates,
|
|
26724
|
-
purgedCoordinators: result.purgedCoordinators,
|
|
26725
|
-
deletedCounts: result.removed
|
|
26726
|
-
});
|
|
26727
|
-
}
|
|
26728
|
-
return result;
|
|
26729
|
-
} catch (error2) {
|
|
26730
|
-
const logger2 = this.deps.logger;
|
|
26731
|
-
if (logger2) {
|
|
26732
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
26733
|
-
logger2.error("orchestration.reset_gc.failed", "reset coordinator gc failed", {
|
|
26734
|
-
trigger: input.trigger,
|
|
26735
|
-
cutoffDays: input.cutoffDays,
|
|
26736
|
-
message
|
|
26737
|
-
});
|
|
26738
|
-
}
|
|
26739
|
-
throw error2;
|
|
26740
|
-
}
|
|
26741
|
-
}
|
|
26742
27224
|
async listPendingCoordinatorResults(coordinatorSession) {
|
|
26743
27225
|
const state = await this.deps.loadState();
|
|
26744
27226
|
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
26745
27227
|
return [];
|
|
26746
27228
|
}
|
|
26747
|
-
return Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession
|
|
27229
|
+
return Object.values(state.orchestration.tasks).filter((task) => sameCoordinatorSession(task.coordinatorSession, coordinatorSession) && this.canInjectTaskIntoCoordinator(state, task) && (task.injectionPending === true || task.coordinatorInjectedAt === undefined)).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((task) => ({ ...task }));
|
|
26748
27230
|
}
|
|
26749
27231
|
async listPendingCoordinatorBlockers(coordinatorSession) {
|
|
26750
27232
|
const state = await this.deps.loadState();
|
|
26751
27233
|
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
26752
27234
|
return [];
|
|
26753
27235
|
}
|
|
26754
|
-
const coordinatorState = state.orchestration.coordinatorQuestionState[coordinatorSession];
|
|
27236
|
+
const coordinatorState = state.orchestration.coordinatorQuestionState[stableCoordinatorSession(coordinatorSession)];
|
|
26755
27237
|
const hiddenQueuedQuestionKeys = coordinatorState?.activePackageId ? new Set((coordinatorState.queuedQuestions ?? []).map((entry) => `${entry.taskId}:${entry.questionId}`)) : null;
|
|
26756
|
-
return Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession
|
|
27238
|
+
return Object.values(state.orchestration.tasks).filter((task) => sameCoordinatorSession(task.coordinatorSession, coordinatorSession) && task.status === "blocked" && task.openQuestion?.status === "open" && !hiddenQueuedQuestionKeys?.has(`${task.taskId}:${task.openQuestion.questionId}`)).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((task) => ({ ...task }));
|
|
26757
27239
|
}
|
|
26758
27240
|
async listContestedCoordinatorResults(coordinatorSession) {
|
|
26759
27241
|
const state = await this.deps.loadState();
|
|
26760
27242
|
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
26761
27243
|
return [];
|
|
26762
27244
|
}
|
|
26763
|
-
return Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession
|
|
27245
|
+
return Object.values(state.orchestration.tasks).filter((task) => sameCoordinatorSession(task.coordinatorSession, coordinatorSession) && task.reviewPending !== undefined).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((task) => ({ ...task }));
|
|
26764
27246
|
}
|
|
26765
27247
|
async listPendingCoordinatorGroups(coordinatorSession) {
|
|
26766
27248
|
const state = await this.deps.loadState();
|
|
@@ -26769,7 +27251,7 @@ class OrchestrationService {
|
|
|
26769
27251
|
}
|
|
26770
27252
|
const groups = this.ensureGroups(state);
|
|
26771
27253
|
const tasks = Object.values(state.orchestration.tasks);
|
|
26772
|
-
return Object.values(groups).filter((group) => group.coordinatorSession
|
|
27254
|
+
return Object.values(groups).filter((group) => sameCoordinatorSession(group.coordinatorSession, coordinatorSession)).filter((group) => {
|
|
26773
27255
|
const groupTasks = tasks.filter((task) => task.groupId === group.groupId);
|
|
26774
27256
|
return this.canInjectGroupIntoCoordinator(state, group.groupId, groupTasks);
|
|
26775
27257
|
}).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((group) => ({ ...group }));
|
|
@@ -26984,7 +27466,7 @@ class OrchestrationService {
|
|
|
26984
27466
|
if (input.sourceHandle !== undefined && task.sourceHandle !== input.sourceHandle) {
|
|
26985
27467
|
throw new Error(`task "${input.taskId}" belongs to source "${task.sourceHandle}", not "${input.sourceHandle}"`);
|
|
26986
27468
|
}
|
|
26987
|
-
if (input.coordinatorSession !== undefined && task.coordinatorSession
|
|
27469
|
+
if (input.coordinatorSession !== undefined && !sameCoordinatorSession(task.coordinatorSession, input.coordinatorSession)) {
|
|
26988
27470
|
throw new Error(`task "${input.taskId}" belongs to coordinator "${task.coordinatorSession}", not "${input.coordinatorSession}"`);
|
|
26989
27471
|
}
|
|
26990
27472
|
if (this.isTerminalStatus(task.status)) {
|
|
@@ -27411,11 +27893,11 @@ class OrchestrationService {
|
|
|
27411
27893
|
...binding.cwd ? { cwd: binding.cwd } : {}
|
|
27412
27894
|
};
|
|
27413
27895
|
}
|
|
27414
|
-
const coordinatorSession = Object.values(state.sessions).find((session3) => session3.transport_session
|
|
27896
|
+
const coordinatorSession = Object.values(state.sessions).find((session3) => sameCoordinatorSession(session3.transport_session, sourceHandle));
|
|
27415
27897
|
if (coordinatorSession) {
|
|
27416
27898
|
return {
|
|
27417
27899
|
sourceKind: "coordinator",
|
|
27418
|
-
coordinatorSession: sourceHandle,
|
|
27900
|
+
coordinatorSession: stableCoordinatorSession(sourceHandle),
|
|
27419
27901
|
workspace: coordinatorSession.workspace
|
|
27420
27902
|
};
|
|
27421
27903
|
}
|
|
@@ -27453,7 +27935,7 @@ class OrchestrationService {
|
|
|
27453
27935
|
if (role && policy.allowedAgentRequestRoles.length > 0 && !policy.allowedAgentRequestRoles.includes(role)) {
|
|
27454
27936
|
throw new Error(`role "${role}" is not allowed for agent-requested delegation`);
|
|
27455
27937
|
}
|
|
27456
|
-
const outstandingRequests = Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession
|
|
27938
|
+
const outstandingRequests = Object.values(state.orchestration.tasks).filter((task) => sameCoordinatorSession(task.coordinatorSession, coordinatorSession) && task.sourceKind !== "human" && (task.status === "needs_confirmation" || task.status === "running" || task.status === "queued"));
|
|
27457
27939
|
if (outstandingRequests.length >= policy.maxPendingAgentRequestsPerCoordinator) {
|
|
27458
27940
|
throw new Error("agent-requested delegation quota exceeded for this coordinator");
|
|
27459
27941
|
}
|
|
@@ -27557,13 +28039,13 @@ class OrchestrationService {
|
|
|
27557
28039
|
if (!filter) {
|
|
27558
28040
|
return true;
|
|
27559
28041
|
}
|
|
27560
|
-
return (filter.sourceHandle === undefined || task.sourceHandle === filter.sourceHandle) && (filter.coordinatorSession === undefined || task.coordinatorSession
|
|
28042
|
+
return (filter.sourceHandle === undefined || task.sourceHandle === filter.sourceHandle) && (filter.coordinatorSession === undefined || sameCoordinatorSession(task.coordinatorSession, filter.coordinatorSession)) && (filter.workspace === undefined || task.workspace === filter.workspace) && (filter.targetAgent === undefined || task.targetAgent === filter.targetAgent) && (filter.role === undefined || task.role === filter.role) && (filter.status === undefined || task.status === filter.status);
|
|
27561
28043
|
}
|
|
27562
28044
|
isTerminalStatus(status) {
|
|
27563
28045
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
27564
28046
|
}
|
|
27565
28047
|
assertCoordinatorOwnership(task, coordinatorSession) {
|
|
27566
|
-
if (task.coordinatorSession
|
|
28048
|
+
if (!sameCoordinatorSession(task.coordinatorSession, coordinatorSession)) {
|
|
27567
28049
|
throw new Error(`task "${task.taskId}" belongs to coordinator "${task.coordinatorSession}", not "${coordinatorSession}"`);
|
|
27568
28050
|
}
|
|
27569
28051
|
}
|
|
@@ -27576,7 +28058,7 @@ class OrchestrationService {
|
|
|
27576
28058
|
if (!group) {
|
|
27577
28059
|
throw new Error(`group "${groupId}" does not exist`);
|
|
27578
28060
|
}
|
|
27579
|
-
if (group.coordinatorSession
|
|
28061
|
+
if (!sameCoordinatorSession(group.coordinatorSession, coordinatorSession)) {
|
|
27580
28062
|
throw new Error(`group "${groupId}" belongs to coordinator "${group.coordinatorSession}", not "${coordinatorSession}"`);
|
|
27581
28063
|
}
|
|
27582
28064
|
}
|
|
@@ -27587,13 +28069,14 @@ class OrchestrationService {
|
|
|
27587
28069
|
return state.orchestration.humanQuestionPackages;
|
|
27588
28070
|
}
|
|
27589
28071
|
ensureCoordinatorQuestionState(state, coordinatorSession) {
|
|
28072
|
+
const key = stableCoordinatorSession(coordinatorSession);
|
|
27590
28073
|
if (!("coordinatorQuestionState" in state.orchestration) || !state.orchestration.coordinatorQuestionState) {
|
|
27591
28074
|
state.orchestration.coordinatorQuestionState = {};
|
|
27592
28075
|
}
|
|
27593
|
-
state.orchestration.coordinatorQuestionState[
|
|
28076
|
+
state.orchestration.coordinatorQuestionState[key] ??= {
|
|
27594
28077
|
queuedQuestions: []
|
|
27595
28078
|
};
|
|
27596
|
-
return state.orchestration.coordinatorQuestionState[
|
|
28079
|
+
return state.orchestration.coordinatorQuestionState[key];
|
|
27597
28080
|
}
|
|
27598
28081
|
ensureCoordinatorRoutes(state) {
|
|
27599
28082
|
if (!("coordinatorRoutes" in state.orchestration) || !state.orchestration.coordinatorRoutes) {
|
|
@@ -27774,7 +28257,7 @@ class OrchestrationService {
|
|
|
27774
28257
|
const referencedGroupIds = new Set(Object.values(state.orchestration.tasks).map((task) => task.groupId).filter((groupId) => typeof groupId === "string"));
|
|
27775
28258
|
let removedAny = false;
|
|
27776
28259
|
for (const [groupId, group] of Object.entries(groups)) {
|
|
27777
|
-
if (group.coordinatorSession
|
|
28260
|
+
if (!sameCoordinatorSession(group.coordinatorSession, coordinatorSession)) {
|
|
27778
28261
|
continue;
|
|
27779
28262
|
}
|
|
27780
28263
|
if (!referencedGroupIds.has(groupId)) {
|
|
@@ -27785,125 +28268,30 @@ class OrchestrationService {
|
|
|
27785
28268
|
return removedAny;
|
|
27786
28269
|
}
|
|
27787
28270
|
removeCoordinatorMetadataIfUnused(state, coordinatorSession) {
|
|
27788
|
-
const
|
|
27789
|
-
const
|
|
28271
|
+
const key = stableCoordinatorSession(coordinatorSession);
|
|
28272
|
+
const hasCoordinatorTasks = Object.values(state.orchestration.tasks).some((task) => sameCoordinatorSession(task.coordinatorSession, coordinatorSession));
|
|
28273
|
+
const hasCoordinatorBindings = Object.values(state.orchestration.workerBindings).some((binding) => sameCoordinatorSession(binding.coordinatorSession, coordinatorSession));
|
|
27790
28274
|
if (hasCoordinatorTasks || hasCoordinatorBindings) {
|
|
27791
28275
|
return false;
|
|
27792
28276
|
}
|
|
27793
28277
|
let removedAny = false;
|
|
27794
28278
|
const packages = this.ensureHumanQuestionPackages(state);
|
|
27795
28279
|
for (const [packageId, packageRecord] of Object.entries(packages)) {
|
|
27796
|
-
if (packageRecord.coordinatorSession
|
|
28280
|
+
if (sameCoordinatorSession(packageRecord.coordinatorSession, coordinatorSession)) {
|
|
27797
28281
|
delete packages[packageId];
|
|
27798
28282
|
removedAny = true;
|
|
27799
28283
|
}
|
|
27800
28284
|
}
|
|
27801
|
-
if (state.orchestration.coordinatorQuestionState?.[
|
|
27802
|
-
delete state.orchestration.coordinatorQuestionState[
|
|
28285
|
+
if (state.orchestration.coordinatorQuestionState?.[key] !== undefined) {
|
|
28286
|
+
delete state.orchestration.coordinatorQuestionState[key];
|
|
27803
28287
|
removedAny = true;
|
|
27804
28288
|
}
|
|
27805
|
-
if (state.orchestration.coordinatorRoutes?.[
|
|
27806
|
-
delete state.orchestration.coordinatorRoutes[
|
|
28289
|
+
if (state.orchestration.coordinatorRoutes?.[key] !== undefined) {
|
|
28290
|
+
delete state.orchestration.coordinatorRoutes[key];
|
|
27807
28291
|
removedAny = true;
|
|
27808
28292
|
}
|
|
27809
28293
|
return removedAny;
|
|
27810
28294
|
}
|
|
27811
|
-
isResetCoordinatorSession(coordinatorSession) {
|
|
27812
|
-
return coordinatorSession.includes(":reset-");
|
|
27813
|
-
}
|
|
27814
|
-
collectResetCoordinatorCandidates(state) {
|
|
27815
|
-
const candidates = new Set;
|
|
27816
|
-
for (const coordinatorSession of Object.keys(state.orchestration.coordinatorRoutes ?? {})) {
|
|
27817
|
-
if (this.isResetCoordinatorSession(coordinatorSession)) {
|
|
27818
|
-
candidates.add(coordinatorSession);
|
|
27819
|
-
}
|
|
27820
|
-
}
|
|
27821
|
-
for (const task of Object.values(state.orchestration.tasks ?? {})) {
|
|
27822
|
-
if (this.isResetCoordinatorSession(task.coordinatorSession)) {
|
|
27823
|
-
candidates.add(task.coordinatorSession);
|
|
27824
|
-
}
|
|
27825
|
-
}
|
|
27826
|
-
return [...candidates];
|
|
27827
|
-
}
|
|
27828
|
-
parseDateMs(value) {
|
|
27829
|
-
if (!value) {
|
|
27830
|
-
return null;
|
|
27831
|
-
}
|
|
27832
|
-
const ms = new Date(value).getTime();
|
|
27833
|
-
return Number.isFinite(ms) ? ms : null;
|
|
27834
|
-
}
|
|
27835
|
-
resolveResetCoordinatorActivityAtMs(state, coordinatorSession) {
|
|
27836
|
-
const routeUpdatedAt = state.orchestration.coordinatorRoutes?.[coordinatorSession]?.updatedAt;
|
|
27837
|
-
const routeMs = this.parseDateMs(routeUpdatedAt);
|
|
27838
|
-
let tasksMs = null;
|
|
27839
|
-
for (const task of Object.values(state.orchestration.tasks ?? {})) {
|
|
27840
|
-
if (task.coordinatorSession !== coordinatorSession) {
|
|
27841
|
-
continue;
|
|
27842
|
-
}
|
|
27843
|
-
const candidate = this.parseDateMs(task.updatedAt);
|
|
27844
|
-
if (candidate === null) {
|
|
27845
|
-
continue;
|
|
27846
|
-
}
|
|
27847
|
-
tasksMs = tasksMs === null ? candidate : Math.max(tasksMs, candidate);
|
|
27848
|
-
}
|
|
27849
|
-
if (routeMs === null && tasksMs === null) {
|
|
27850
|
-
return null;
|
|
27851
|
-
}
|
|
27852
|
-
if (routeMs === null) {
|
|
27853
|
-
return tasksMs;
|
|
27854
|
-
}
|
|
27855
|
-
if (tasksMs === null) {
|
|
27856
|
-
return routeMs;
|
|
27857
|
-
}
|
|
27858
|
-
return Math.max(routeMs, tasksMs);
|
|
27859
|
-
}
|
|
27860
|
-
cascadeRemoveCoordinatorRecords(state, coordinatorSession) {
|
|
27861
|
-
const removed = {
|
|
27862
|
-
tasks: 0,
|
|
27863
|
-
workerBindings: 0,
|
|
27864
|
-
groups: 0,
|
|
27865
|
-
coordinatorRoutes: 0,
|
|
27866
|
-
humanQuestionPackages: 0,
|
|
27867
|
-
coordinatorQuestionState: 0
|
|
27868
|
-
};
|
|
27869
|
-
const tasks = state.orchestration.tasks;
|
|
27870
|
-
for (const [taskId, task] of Object.entries(tasks ?? {})) {
|
|
27871
|
-
if (task.coordinatorSession === coordinatorSession) {
|
|
27872
|
-
delete tasks[taskId];
|
|
27873
|
-
removed.tasks += 1;
|
|
27874
|
-
}
|
|
27875
|
-
}
|
|
27876
|
-
const workerBindings = state.orchestration.workerBindings;
|
|
27877
|
-
for (const [workerSession, binding] of Object.entries(workerBindings ?? {})) {
|
|
27878
|
-
if (binding.coordinatorSession === coordinatorSession) {
|
|
27879
|
-
delete workerBindings[workerSession];
|
|
27880
|
-
removed.workerBindings += 1;
|
|
27881
|
-
}
|
|
27882
|
-
}
|
|
27883
|
-
const groups = this.ensureGroups(state);
|
|
27884
|
-
for (const [groupId, group] of Object.entries(groups ?? {})) {
|
|
27885
|
-
if (group.coordinatorSession === coordinatorSession) {
|
|
27886
|
-
delete groups[groupId];
|
|
27887
|
-
removed.groups += 1;
|
|
27888
|
-
}
|
|
27889
|
-
}
|
|
27890
|
-
if (state.orchestration.coordinatorRoutes?.[coordinatorSession] !== undefined) {
|
|
27891
|
-
delete state.orchestration.coordinatorRoutes[coordinatorSession];
|
|
27892
|
-
removed.coordinatorRoutes += 1;
|
|
27893
|
-
}
|
|
27894
|
-
const packages = this.ensureHumanQuestionPackages(state);
|
|
27895
|
-
for (const [packageId, packageRecord] of Object.entries(packages ?? {})) {
|
|
27896
|
-
if (packageRecord.coordinatorSession === coordinatorSession) {
|
|
27897
|
-
delete packages[packageId];
|
|
27898
|
-
removed.humanQuestionPackages += 1;
|
|
27899
|
-
}
|
|
27900
|
-
}
|
|
27901
|
-
if (state.orchestration.coordinatorQuestionState?.[coordinatorSession] !== undefined) {
|
|
27902
|
-
delete state.orchestration.coordinatorQuestionState[coordinatorSession];
|
|
27903
|
-
removed.coordinatorQuestionState += 1;
|
|
27904
|
-
}
|
|
27905
|
-
return removed;
|
|
27906
|
-
}
|
|
27907
28295
|
bumpGroupUpdated(state, groupId, now) {
|
|
27908
28296
|
if (!groupId) {
|
|
27909
28297
|
return;
|
|
@@ -28077,7 +28465,7 @@ class OrchestrationService {
|
|
|
28077
28465
|
}
|
|
28078
28466
|
const validQueuedQuestions = coordinatorState.queuedQuestions.filter((entry) => {
|
|
28079
28467
|
const task = state.orchestration.tasks[entry.taskId];
|
|
28080
|
-
return task
|
|
28468
|
+
return task !== undefined && sameCoordinatorSession(task.coordinatorSession, coordinatorSession) && task.status === "blocked" && task.openQuestion?.status === "open" && task.openQuestion.questionId === entry.questionId;
|
|
28081
28469
|
});
|
|
28082
28470
|
if (validQueuedQuestions.length !== coordinatorState.queuedQuestions.length) {
|
|
28083
28471
|
coordinatorState.queuedQuestions = validQueuedQuestions;
|
|
@@ -28455,18 +28843,39 @@ class ScheduledTaskScheduler {
|
|
|
28455
28843
|
return;
|
|
28456
28844
|
this.ticking = true;
|
|
28457
28845
|
try {
|
|
28458
|
-
|
|
28846
|
+
let dueTasks;
|
|
28847
|
+
try {
|
|
28848
|
+
dueTasks = await this.service.claimDueTasks();
|
|
28849
|
+
} catch (claimError) {
|
|
28850
|
+
await this.logger?.error("scheduled.claim.failed", "claimDueTasks threw; skipping tick", { message: claimError instanceof Error ? claimError.message : String(claimError) });
|
|
28851
|
+
return;
|
|
28852
|
+
}
|
|
28459
28853
|
for (const task of dueTasks) {
|
|
28460
28854
|
try {
|
|
28461
28855
|
await this.dispatchWithTimeout(task);
|
|
28462
|
-
await this.service.markExecuted(task.id);
|
|
28463
28856
|
} catch (error2) {
|
|
28464
28857
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
28465
28858
|
await this.logger?.error("scheduled.dispatch.failed", "failed to dispatch scheduled task", {
|
|
28466
28859
|
taskId: task.id,
|
|
28467
28860
|
message
|
|
28468
28861
|
});
|
|
28469
|
-
|
|
28862
|
+
try {
|
|
28863
|
+
await this.service.markFailed(task.id, error2);
|
|
28864
|
+
} catch (markError) {
|
|
28865
|
+
await this.logger?.error("scheduled.dispatch.mark_failed", "markFailed threw; task state may be stale", {
|
|
28866
|
+
taskId: task.id,
|
|
28867
|
+
message: markError instanceof Error ? markError.message : String(markError)
|
|
28868
|
+
});
|
|
28869
|
+
}
|
|
28870
|
+
continue;
|
|
28871
|
+
}
|
|
28872
|
+
try {
|
|
28873
|
+
await this.service.markExecuted(task.id);
|
|
28874
|
+
} catch (markError) {
|
|
28875
|
+
await this.logger?.error("scheduled.dispatch.mark_executed_failed", "markExecuted threw after a successful dispatch; leaving task state for startup reconciliation", {
|
|
28876
|
+
taskId: task.id,
|
|
28877
|
+
message: markError instanceof Error ? markError.message : String(markError)
|
|
28878
|
+
});
|
|
28470
28879
|
}
|
|
28471
28880
|
}
|
|
28472
28881
|
} finally {
|
|
@@ -28566,7 +28975,7 @@ async function createScheduledTaskFromRoute(input, deps) {
|
|
|
28566
28975
|
if (coordinatorSession.length === 0) {
|
|
28567
28976
|
throw new Error("coordinatorSession must be a non-empty string");
|
|
28568
28977
|
}
|
|
28569
|
-
const route = deps.state.orchestration.coordinatorRoutes[coordinatorSession];
|
|
28978
|
+
const route = deps.state.orchestration.coordinatorRoutes[stableCoordinatorSession(coordinatorSession)];
|
|
28570
28979
|
if (!route) {
|
|
28571
28980
|
throw new Error(`no chat route is recorded for coordinator session "${coordinatorSession}"`);
|
|
28572
28981
|
}
|
|
@@ -28593,7 +29002,7 @@ async function createScheduledTaskFromRoute(input, deps) {
|
|
|
28593
29002
|
if (!session3) {
|
|
28594
29003
|
throw new Error(`session "${route.sessionAlias}" recorded for coordinator session "${coordinatorSession}" was not found`);
|
|
28595
29004
|
}
|
|
28596
|
-
if (session3.transportSession
|
|
29005
|
+
if (!sameCoordinatorSession(session3.transportSession, coordinatorSession)) {
|
|
28597
29006
|
throw new Error(`session "${route.sessionAlias}" is no longer attached to coordinator session "${coordinatorSession}"`);
|
|
28598
29007
|
}
|
|
28599
29008
|
const executeAt = parseRouteScheduledTime(input.timeText, deps.now?.() ?? new Date);
|
|
@@ -28646,12 +29055,12 @@ var init_scheduled_route_create = __esm(() => {
|
|
|
28646
29055
|
|
|
28647
29056
|
// src/scheduled/scheduled-route-manage.ts
|
|
28648
29057
|
async function listScheduledTasksFromRoute(input, deps) {
|
|
28649
|
-
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
28650
|
-
return deps.scheduled.listPending();
|
|
29058
|
+
const route = resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
29059
|
+
return deps.scheduled.listPending(route.chatKey);
|
|
28651
29060
|
}
|
|
28652
29061
|
async function cancelScheduledTaskFromRoute(input, deps) {
|
|
28653
|
-
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
28654
|
-
const cancelled = await deps.scheduled.cancelPending(input.id);
|
|
29062
|
+
const route = resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
29063
|
+
const cancelled = await deps.scheduled.cancelPending(input.id, route.chatKey);
|
|
28655
29064
|
return { id: normalizeId(input.id), cancelled };
|
|
28656
29065
|
}
|
|
28657
29066
|
function resolveOwnedCoordinatorRoute(coordinatorSession, state, label) {
|
|
@@ -28659,7 +29068,7 @@ function resolveOwnedCoordinatorRoute(coordinatorSession, state, label) {
|
|
|
28659
29068
|
if (session3.length === 0) {
|
|
28660
29069
|
throw new Error("coordinatorSession must be a non-empty string");
|
|
28661
29070
|
}
|
|
28662
|
-
const route = state.orchestration.coordinatorRoutes[session3];
|
|
29071
|
+
const route = state.orchestration.coordinatorRoutes[stableCoordinatorSession(session3)];
|
|
28663
29072
|
if (!route) {
|
|
28664
29073
|
throw new Error(`no chat route is recorded for coordinator session "${session3}"`);
|
|
28665
29074
|
}
|
|
@@ -28696,25 +29105,37 @@ class SessionService {
|
|
|
28696
29105
|
const seen = new Set;
|
|
28697
29106
|
const resolved = [];
|
|
28698
29107
|
for (const session3 of Object.values(this.state.sessions)) {
|
|
28699
|
-
|
|
29108
|
+
let candidate;
|
|
29109
|
+
try {
|
|
29110
|
+
candidate = this.toResolvedSession(session3);
|
|
29111
|
+
} catch {
|
|
28700
29112
|
continue;
|
|
28701
29113
|
}
|
|
28702
|
-
|
|
28703
|
-
|
|
28704
|
-
|
|
28705
|
-
|
|
29114
|
+
const key = JSON.stringify([
|
|
29115
|
+
candidate.agent,
|
|
29116
|
+
candidate.agentCommand ?? null,
|
|
29117
|
+
candidate.cwd,
|
|
29118
|
+
candidate.transportSession
|
|
29119
|
+
]);
|
|
29120
|
+
if (seen.has(key)) {
|
|
29121
|
+
continue;
|
|
29122
|
+
}
|
|
29123
|
+
seen.add(key);
|
|
29124
|
+
resolved.push(candidate);
|
|
28706
29125
|
}
|
|
28707
29126
|
return resolved;
|
|
28708
29127
|
}
|
|
28709
29128
|
resolveSession(alias, agent3, workspace3, transportSession) {
|
|
28710
29129
|
this.validateSession(alias, agent3, workspace3);
|
|
29130
|
+
const existing = this.state.sessions[alias];
|
|
29131
|
+
const sameAgentExisting = existing && existing.agent === agent3 ? existing : undefined;
|
|
28711
29132
|
return this.toResolvedSession({
|
|
28712
29133
|
alias,
|
|
28713
29134
|
agent: agent3,
|
|
28714
29135
|
workspace: workspace3,
|
|
28715
29136
|
transport_session: transportSession,
|
|
28716
|
-
transport_agent_command:
|
|
28717
|
-
created_at:
|
|
29137
|
+
transport_agent_command: sameAgentExisting?.transport_agent_command,
|
|
29138
|
+
created_at: existing?.created_at ?? new Date().toISOString(),
|
|
28718
29139
|
last_used_at: new Date().toISOString()
|
|
28719
29140
|
});
|
|
28720
29141
|
}
|
|
@@ -28751,9 +29172,10 @@ class SessionService {
|
|
|
28751
29172
|
return this.state.chat_contexts[chatKey]?.current_session;
|
|
28752
29173
|
}
|
|
28753
29174
|
async getPreferredSessionForTransport(transportSession) {
|
|
28754
|
-
const
|
|
28755
|
-
const
|
|
28756
|
-
const
|
|
29175
|
+
const target = stableCoordinatorSession(transportSession);
|
|
29176
|
+
const matches = Object.values(this.state.sessions).filter((session3) => stableCoordinatorSession(session3.transport_session) === target).sort((left, right) => right.last_used_at.localeCompare(left.last_used_at));
|
|
29177
|
+
const expectedAlias = target.split(":").at(-1);
|
|
29178
|
+
const expectedWorkspace = target.split(":")[0];
|
|
28757
29179
|
const preferred = matches.find((session3) => session3.alias === expectedAlias && session3.workspace === expectedWorkspace) ?? matches[0];
|
|
28758
29180
|
return preferred ? this.toResolvedSession(preferred) : null;
|
|
28759
29181
|
}
|
|
@@ -28785,10 +29207,13 @@ class SessionService {
|
|
|
28785
29207
|
const previousCurrent = prevCtx?.current_session;
|
|
28786
29208
|
const carriedPrevious = previousCurrent && previousCurrent !== internalAlias ? previousCurrent : prevCtx?.previous_session;
|
|
28787
29209
|
session3.last_used_at = new Date().toISOString();
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
}
|
|
29210
|
+
const nextCtx = { ...prevCtx, current_session: internalAlias };
|
|
29211
|
+
if (carriedPrevious) {
|
|
29212
|
+
nextCtx.previous_session = carriedPrevious;
|
|
29213
|
+
} else {
|
|
29214
|
+
delete nextCtx.previous_session;
|
|
29215
|
+
}
|
|
29216
|
+
this.state.chat_contexts[chatKey] = nextCtx;
|
|
28792
29217
|
await this.persist();
|
|
28793
29218
|
return {
|
|
28794
29219
|
alias: toDisplaySessionAlias(session3.alias),
|
|
@@ -28815,10 +29240,13 @@ class SessionService {
|
|
|
28815
29240
|
}
|
|
28816
29241
|
const currentInternal = ctx?.current_session;
|
|
28817
29242
|
prevSession.last_used_at = new Date().toISOString();
|
|
28818
|
-
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
}
|
|
29243
|
+
const nextCtx = { ...ctx, current_session: prevInternal };
|
|
29244
|
+
if (currentInternal && currentInternal !== prevInternal) {
|
|
29245
|
+
nextCtx.previous_session = currentInternal;
|
|
29246
|
+
} else {
|
|
29247
|
+
delete nextCtx.previous_session;
|
|
29248
|
+
}
|
|
29249
|
+
this.state.chat_contexts[chatKey] = nextCtx;
|
|
28822
29250
|
await this.persist();
|
|
28823
29251
|
return {
|
|
28824
29252
|
alias: toDisplaySessionAlias(prevSession.alias),
|
|
@@ -28989,13 +29417,26 @@ class SessionService {
|
|
|
28989
29417
|
const wasActive = Object.values(this.state.chat_contexts).some((ctx) => ctx.current_session === alias);
|
|
28990
29418
|
delete this.state.sessions[alias];
|
|
28991
29419
|
for (const [chatKey, ctx] of Object.entries(this.state.chat_contexts)) {
|
|
28992
|
-
if (ctx.current_session === alias) {
|
|
28993
|
-
delete this.state.chat_contexts[chatKey];
|
|
28994
|
-
continue;
|
|
28995
|
-
}
|
|
28996
29420
|
if (ctx.previous_session === alias) {
|
|
28997
29421
|
delete ctx.previous_session;
|
|
28998
29422
|
}
|
|
29423
|
+
if (ctx.current_session === alias) {
|
|
29424
|
+
if (ctx.previous_session) {
|
|
29425
|
+
ctx.current_session = ctx.previous_session;
|
|
29426
|
+
delete ctx.previous_session;
|
|
29427
|
+
} else {
|
|
29428
|
+
ctx.current_session = "";
|
|
29429
|
+
}
|
|
29430
|
+
}
|
|
29431
|
+
if (ctx.background_results && alias in ctx.background_results) {
|
|
29432
|
+
delete ctx.background_results[alias];
|
|
29433
|
+
if (Object.keys(ctx.background_results).length === 0) {
|
|
29434
|
+
delete ctx.background_results;
|
|
29435
|
+
}
|
|
29436
|
+
}
|
|
29437
|
+
if (!ctx.current_session && !ctx.previous_session && !ctx.background_results) {
|
|
29438
|
+
delete this.state.chat_contexts[chatKey];
|
|
29439
|
+
}
|
|
28999
29440
|
}
|
|
29000
29441
|
await this.persist();
|
|
29001
29442
|
return { wasActive };
|
|
@@ -29109,6 +29550,7 @@ class SessionService {
|
|
|
29109
29550
|
throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
|
|
29110
29551
|
}
|
|
29111
29552
|
const existingSession = this.state.sessions[alias];
|
|
29553
|
+
const sameAgentExisting = existingSession && existingSession.agent === agent3 ? existingSession : undefined;
|
|
29112
29554
|
const now = new Date(this.now()).toISOString();
|
|
29113
29555
|
const normalizedTransportAgentCommand = transportAgentCommand?.trim();
|
|
29114
29556
|
const session3 = {
|
|
@@ -29121,9 +29563,9 @@ class SessionService {
|
|
|
29121
29563
|
agent_session_title: native?.title ?? undefined,
|
|
29122
29564
|
agent_session_updated_at: native?.updatedAt,
|
|
29123
29565
|
attached_at: native ? now : undefined,
|
|
29124
|
-
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } :
|
|
29125
|
-
mode_id:
|
|
29126
|
-
reply_mode:
|
|
29566
|
+
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : sameAgentExisting?.transport_agent_command ? { transport_agent_command: sameAgentExisting.transport_agent_command } : {},
|
|
29567
|
+
mode_id: sameAgentExisting?.mode_id,
|
|
29568
|
+
reply_mode: sameAgentExisting?.reply_mode,
|
|
29127
29569
|
created_at: existingSession?.created_at ?? now,
|
|
29128
29570
|
last_used_at: now
|
|
29129
29571
|
};
|
|
@@ -29305,7 +29747,6 @@ async function runConsole(paths, deps) {
|
|
|
29305
29747
|
let runtime = null;
|
|
29306
29748
|
let consumerLock;
|
|
29307
29749
|
let heartbeatTimer = null;
|
|
29308
|
-
let gcResetTimer = null;
|
|
29309
29750
|
let consumerLockAcquired = false;
|
|
29310
29751
|
let daemonRuntimeStarted = false;
|
|
29311
29752
|
const shutdownController = new AbortController;
|
|
@@ -29319,12 +29760,6 @@ async function runConsole(paths, deps) {
|
|
|
29319
29760
|
if (deps.afterBuild) {
|
|
29320
29761
|
await deps.afterBuild(runtime);
|
|
29321
29762
|
}
|
|
29322
|
-
try {
|
|
29323
|
-
await runtime.orchestration.service.purgeExpiredResetCoordinators({
|
|
29324
|
-
cutoffDays: 7,
|
|
29325
|
-
trigger: "startup"
|
|
29326
|
-
});
|
|
29327
|
-
} catch {}
|
|
29328
29763
|
try {
|
|
29329
29764
|
await runtime.orchestration.service.reconcileParallelSlots();
|
|
29330
29765
|
} catch (reconcileError) {
|
|
@@ -29380,6 +29815,7 @@ async function runConsole(paths, deps) {
|
|
|
29380
29815
|
throw error2;
|
|
29381
29816
|
}
|
|
29382
29817
|
}
|
|
29818
|
+
await runtime.reapStaleQueueOwners();
|
|
29383
29819
|
if (deps.beforeReady) {
|
|
29384
29820
|
await deps.beforeReady(runtime);
|
|
29385
29821
|
}
|
|
@@ -29393,10 +29829,6 @@ async function runConsole(paths, deps) {
|
|
|
29393
29829
|
heartbeatTimer = setIntervalFn(() => {
|
|
29394
29830
|
deps.daemonRuntime?.heartbeat().catch(() => {});
|
|
29395
29831
|
}, deps.heartbeatIntervalMs ?? 30000);
|
|
29396
|
-
const runtimeForGc = runtime;
|
|
29397
|
-
gcResetTimer = setIntervalFn(() => {
|
|
29398
|
-
runtimeForGc.orchestration.service.purgeExpiredResetCoordinators({ cutoffDays: 7, trigger: "interval" }).catch(() => {});
|
|
29399
|
-
}, 86400000);
|
|
29400
29832
|
}
|
|
29401
29833
|
const channelStartPromise = deps.channels.startAll({
|
|
29402
29834
|
agent: runtime.agent,
|
|
@@ -29451,7 +29883,6 @@ async function runConsole(paths, deps) {
|
|
|
29451
29883
|
signalHandler,
|
|
29452
29884
|
clearIntervalFn,
|
|
29453
29885
|
heartbeatTimer,
|
|
29454
|
-
gcResetTimer,
|
|
29455
29886
|
...deps.daemonRuntime ? { daemonRuntime: deps.daemonRuntime } : {},
|
|
29456
29887
|
runtime,
|
|
29457
29888
|
consumerLock,
|
|
@@ -29477,9 +29908,6 @@ async function runCleanupSequence(input) {
|
|
|
29477
29908
|
if (input.heartbeatTimer !== null) {
|
|
29478
29909
|
input.clearIntervalFn(input.heartbeatTimer);
|
|
29479
29910
|
}
|
|
29480
|
-
if (input.gcResetTimer !== null) {
|
|
29481
|
-
input.clearIntervalFn(input.gcResetTimer);
|
|
29482
|
-
}
|
|
29483
29911
|
if (input.daemonRuntime && input.runtime) {
|
|
29484
29912
|
try {
|
|
29485
29913
|
await input.runtime.orchestration.server.stop();
|
|
@@ -29583,15 +30011,15 @@ class AcpxBridgeClient {
|
|
|
29583
30011
|
onEvent
|
|
29584
30012
|
});
|
|
29585
30013
|
try {
|
|
29586
|
-
|
|
30014
|
+
this.writeLine(encodeBridgeRequest({
|
|
29587
30015
|
id,
|
|
29588
30016
|
method,
|
|
29589
30017
|
params
|
|
29590
|
-
}))
|
|
29591
|
-
|
|
29592
|
-
|
|
29593
|
-
|
|
29594
|
-
}
|
|
30018
|
+
}), (error2) => {
|
|
30019
|
+
if (error2 && this.pending.delete(id)) {
|
|
30020
|
+
reject(error2);
|
|
30021
|
+
}
|
|
30022
|
+
});
|
|
29595
30023
|
} catch (error2) {
|
|
29596
30024
|
this.pending.delete(id);
|
|
29597
30025
|
reject(error2);
|
|
@@ -29671,6 +30099,17 @@ class AcpxBridgeClient {
|
|
|
29671
30099
|
}
|
|
29672
30100
|
}
|
|
29673
30101
|
}
|
|
30102
|
+
function buildBridgeSpawnEnv(options = {}) {
|
|
30103
|
+
return {
|
|
30104
|
+
XACPX_LANG: getLocale(),
|
|
30105
|
+
XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
|
|
30106
|
+
XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
|
|
30107
|
+
XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
|
|
30108
|
+
...typeof options.permissionPolicy === "string" && options.permissionPolicy.trim().length > 0 ? { XACPX_BRIDGE_PERMISSION_POLICY: options.permissionPolicy } : {},
|
|
30109
|
+
...typeof options.queueOwnerTtlSeconds === "number" && Number.isFinite(options.queueOwnerTtlSeconds) ? { XACPX_BRIDGE_QUEUE_OWNER_TTL_SECONDS: String(options.queueOwnerTtlSeconds) } : {},
|
|
30110
|
+
...typeof options.sessionInitTimeoutMs === "number" && Number.isFinite(options.sessionInitTimeoutMs) && options.sessionInitTimeoutMs > 0 ? { XACPX_BRIDGE_SESSION_INIT_TIMEOUT_MS: String(options.sessionInitTimeoutMs) } : {}
|
|
30111
|
+
};
|
|
30112
|
+
}
|
|
29674
30113
|
function buildBridgeSpawnSpec(options) {
|
|
29675
30114
|
if (options.execPath.endsWith("bun")) {
|
|
29676
30115
|
return {
|
|
@@ -29693,15 +30132,17 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
29693
30132
|
cwd: options.cwd ?? process.cwd(),
|
|
29694
30133
|
env: {
|
|
29695
30134
|
...process.env,
|
|
29696
|
-
|
|
29697
|
-
XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
|
|
29698
|
-
XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
|
|
29699
|
-
XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
|
|
29700
|
-
...typeof options.queueOwnerTtlSeconds === "number" && Number.isFinite(options.queueOwnerTtlSeconds) ? { XACPX_BRIDGE_QUEUE_OWNER_TTL_SECONDS: String(options.queueOwnerTtlSeconds) } : {}
|
|
30135
|
+
...buildBridgeSpawnEnv(options)
|
|
29701
30136
|
},
|
|
29702
30137
|
stdio: ["pipe", "pipe", "inherit"]
|
|
29703
30138
|
});
|
|
29704
|
-
const client =
|
|
30139
|
+
const client = manageBridgeChild(child);
|
|
30140
|
+
await client.waitUntilReady();
|
|
30141
|
+
return client;
|
|
30142
|
+
}
|
|
30143
|
+
function manageBridgeChild(child) {
|
|
30144
|
+
const client = new AcpxBridgeClient((line, onWriteError) => child.stdin.write(line, onWriteError));
|
|
30145
|
+
child.stdin.on("error", () => {});
|
|
29705
30146
|
const output = createInterface({
|
|
29706
30147
|
input: child.stdout,
|
|
29707
30148
|
crlfDelay: Infinity
|
|
@@ -29727,7 +30168,6 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
29727
30168
|
await terminateProcessTree(child.pid ?? 0, { detachedProcessGroup: false });
|
|
29728
30169
|
}
|
|
29729
30170
|
};
|
|
29730
|
-
await client.waitUntilReady();
|
|
29731
30171
|
return client;
|
|
29732
30172
|
}
|
|
29733
30173
|
function awaitable(executor) {
|
|
@@ -30569,19 +31009,19 @@ var init_streaming_prompt = __esm(() => {
|
|
|
30569
31009
|
|
|
30570
31010
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
30571
31011
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
30572
|
-
import { dirname as
|
|
31012
|
+
import { dirname as dirname11, join as join16 } from "node:path";
|
|
30573
31013
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
30574
31014
|
if (platform === "win32") {
|
|
30575
31015
|
return null;
|
|
30576
31016
|
}
|
|
30577
|
-
return join16(
|
|
31017
|
+
return join16(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
30578
31018
|
}
|
|
30579
|
-
async function ensureNodePtyHelperExecutable(helperPath,
|
|
31019
|
+
async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
|
|
30580
31020
|
if (!helperPath) {
|
|
30581
31021
|
return;
|
|
30582
31022
|
}
|
|
30583
31023
|
try {
|
|
30584
|
-
await
|
|
31024
|
+
await chmod5(helperPath, 493);
|
|
30585
31025
|
} catch (error2) {
|
|
30586
31026
|
if (error2.code === "ENOENT") {
|
|
30587
31027
|
return;
|
|
@@ -30594,7 +31034,7 @@ var init_node_pty_helper = () => {};
|
|
|
30594
31034
|
// src/transport/acpx-queue-owner-launcher.ts
|
|
30595
31035
|
import { createHash as createHash3 } from "node:crypto";
|
|
30596
31036
|
import { spawn as spawn8 } from "node:child_process";
|
|
30597
|
-
import { readFile as
|
|
31037
|
+
import { readFile as readFile13, unlink } from "node:fs/promises";
|
|
30598
31038
|
import { homedir as homedir8 } from "node:os";
|
|
30599
31039
|
import { join as join17 } from "node:path";
|
|
30600
31040
|
function buildXacpxMcpServerSpec(input) {
|
|
@@ -30757,7 +31197,7 @@ async function terminateAcpxQueueOwner(sessionId) {
|
|
|
30757
31197
|
const lockPath = queueLockFilePath(sessionId);
|
|
30758
31198
|
let owner;
|
|
30759
31199
|
try {
|
|
30760
|
-
owner = JSON.parse(await
|
|
31200
|
+
owner = JSON.parse(await readFile13(lockPath, "utf8"));
|
|
30761
31201
|
} catch {
|
|
30762
31202
|
return;
|
|
30763
31203
|
}
|
|
@@ -31416,10 +31856,11 @@ async function reapQueueOwners(acpxCommand, targets, deps = {}) {
|
|
|
31416
31856
|
const timeoutMs = deps.timeoutMs ?? 5000;
|
|
31417
31857
|
const seen = new Set;
|
|
31418
31858
|
const unique = targets.filter((target) => {
|
|
31419
|
-
|
|
31859
|
+
const key = JSON.stringify([target.agent, target.agentCommand ?? null, target.cwd, target.transportSession]);
|
|
31860
|
+
if (seen.has(key)) {
|
|
31420
31861
|
return false;
|
|
31421
31862
|
}
|
|
31422
|
-
seen.add(
|
|
31863
|
+
seen.add(key);
|
|
31423
31864
|
return true;
|
|
31424
31865
|
});
|
|
31425
31866
|
let terminated = 0;
|
|
@@ -31503,6 +31944,17 @@ var init_queue_owner_reaper = __esm(() => {
|
|
|
31503
31944
|
});
|
|
31504
31945
|
|
|
31505
31946
|
// src/transport/collect-reap-targets.ts
|
|
31947
|
+
function collectReapTargets(sessions, orchestration3, config4) {
|
|
31948
|
+
return [
|
|
31949
|
+
...sessions.listAllResolvedSessions().map((session3) => ({
|
|
31950
|
+
agent: session3.agent,
|
|
31951
|
+
...session3.agentCommand ? { agentCommand: session3.agentCommand } : {},
|
|
31952
|
+
cwd: session3.cwd,
|
|
31953
|
+
transportSession: session3.transportSession
|
|
31954
|
+
})),
|
|
31955
|
+
...workerBindingReapTargets(orchestration3, config4)
|
|
31956
|
+
];
|
|
31957
|
+
}
|
|
31506
31958
|
function workerBindingReapTargets(orchestration3, config4) {
|
|
31507
31959
|
const targets = [];
|
|
31508
31960
|
for (const [workerSession, binding] of Object.entries(orchestration3.workerBindings)) {
|
|
@@ -31560,10 +32012,21 @@ class MessageChannelRegistry {
|
|
|
31560
32012
|
throw new Error("all channels failed to start");
|
|
31561
32013
|
}
|
|
31562
32014
|
}
|
|
31563
|
-
stopAll() {
|
|
32015
|
+
async stopAll() {
|
|
32016
|
+
let firstError;
|
|
31564
32017
|
for (const channel of this.channels.values()) {
|
|
31565
|
-
|
|
32018
|
+
try {
|
|
32019
|
+
if (channel.stop) {
|
|
32020
|
+
await channel.stop();
|
|
32021
|
+
} else {
|
|
32022
|
+
channel.logout();
|
|
32023
|
+
}
|
|
32024
|
+
} catch (error2) {
|
|
32025
|
+
firstError ??= error2;
|
|
32026
|
+
}
|
|
31566
32027
|
}
|
|
32028
|
+
if (firstError !== undefined)
|
|
32029
|
+
throw firstError;
|
|
31567
32030
|
}
|
|
31568
32031
|
getByChatKey(chatKey) {
|
|
31569
32032
|
return this.channels.get(getChannelIdFromChatKey(chatKey)) ?? null;
|
|
@@ -31811,7 +32274,7 @@ __export(exports_main, {
|
|
|
31811
32274
|
});
|
|
31812
32275
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
31813
32276
|
import { homedir as homedir9 } from "node:os";
|
|
31814
|
-
import { dirname as
|
|
32277
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
31815
32278
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
31816
32279
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
31817
32280
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
@@ -31883,6 +32346,35 @@ async function buildApp(paths, deps = {}) {
|
|
|
31883
32346
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config4.transport.command });
|
|
31884
32347
|
const stateStore = new StateStore(paths.statePath);
|
|
31885
32348
|
const state = await stateStore.load();
|
|
32349
|
+
const stateLoadReport = stateStore.lastLoadReport;
|
|
32350
|
+
if (stateLoadReport) {
|
|
32351
|
+
for (const record3 of stateLoadReport.dropped) {
|
|
32352
|
+
await logger2.error("state.record_quarantined", "dropped malformed state.json record", {
|
|
32353
|
+
statePath: paths.statePath,
|
|
32354
|
+
section: record3.section,
|
|
32355
|
+
key: record3.key,
|
|
32356
|
+
reason: record3.reason
|
|
32357
|
+
});
|
|
32358
|
+
}
|
|
32359
|
+
if (stateLoadReport.corruptPath) {
|
|
32360
|
+
await logger2.error("state.file_corrupt", "state.json was unreadable; renamed aside and starting empty", {
|
|
32361
|
+
statePath: paths.statePath,
|
|
32362
|
+
corruptPath: stateLoadReport.corruptPath
|
|
32363
|
+
});
|
|
32364
|
+
}
|
|
32365
|
+
if (stateLoadReport.quarantinePath) {
|
|
32366
|
+
await logger2.error("state.file_quarantined", "original state.json backed up before dropping records", {
|
|
32367
|
+
statePath: paths.statePath,
|
|
32368
|
+
quarantinePath: stateLoadReport.quarantinePath
|
|
32369
|
+
});
|
|
32370
|
+
}
|
|
32371
|
+
if (stateLoadReport.backupError) {
|
|
32372
|
+
await logger2.error("state.quarantine_backup_failed", "failed to back up the original state.json", {
|
|
32373
|
+
statePath: paths.statePath,
|
|
32374
|
+
message: stateLoadReport.backupError
|
|
32375
|
+
});
|
|
32376
|
+
}
|
|
32377
|
+
}
|
|
31886
32378
|
const stateMutex = new AsyncMutex;
|
|
31887
32379
|
const debouncedStateStore = new DebouncedStateStore({
|
|
31888
32380
|
delegate: stateStore,
|
|
@@ -31902,7 +32394,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
31902
32394
|
bridgeEntryPath: resolveBridgeEntryPath(),
|
|
31903
32395
|
permissionMode: config4.transport.permissionMode,
|
|
31904
32396
|
nonInteractivePermissions: config4.transport.nonInteractivePermissions,
|
|
31905
|
-
...typeof config4.transport.
|
|
32397
|
+
...typeof config4.transport.permissionPolicy === "string" ? { permissionPolicy: config4.transport.permissionPolicy } : {},
|
|
32398
|
+
...typeof config4.transport.queueOwnerTtlSeconds === "number" ? { queueOwnerTtlSeconds: config4.transport.queueOwnerTtlSeconds } : {},
|
|
32399
|
+
...typeof config4.transport.sessionInitTimeoutMs === "number" ? { sessionInitTimeoutMs: config4.transport.sessionInitTimeoutMs } : {}
|
|
31906
32400
|
})))) : deps.createCliTransport?.(acpxCommand) ?? new AcpxCliTransport({ ...config4.transport, command: acpxCommand });
|
|
31907
32401
|
const quota = new QuotaManager({
|
|
31908
32402
|
onInbound: (chatKey) => {
|
|
@@ -32224,7 +32718,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
32224
32718
|
}
|
|
32225
32719
|
},
|
|
32226
32720
|
findReusableWorkerSession: async ({ coordinatorSession, workspace: workspace3, cwd, targetAgent, role }) => {
|
|
32227
|
-
const binding = Object.entries(state.orchestration.workerBindings).find(([, current]) => current.ephemeral !== true && current.coordinatorSession
|
|
32721
|
+
const binding = Object.entries(state.orchestration.workerBindings).find(([, current]) => current.ephemeral !== true && sameCoordinatorSession(current.coordinatorSession, coordinatorSession) && current.workspace === workspace3 && current.cwd === cwd && current.targetAgent === targetAgent && current.role === role);
|
|
32228
32722
|
return binding?.[0] ?? null;
|
|
32229
32723
|
},
|
|
32230
32724
|
logger: logger2
|
|
@@ -32255,6 +32749,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
32255
32749
|
const progressHeartbeatInterval = startProgressHeartbeat(orchestration3, config4, logger2, deps.channel ?? null);
|
|
32256
32750
|
const orchestrationEndpoint = createOrchestrationEndpoint(paths.orchestrationSocketPath ?? resolveOrchestrationSocketPathFromConfigPath(paths.configPath));
|
|
32257
32751
|
const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration3, {
|
|
32752
|
+
onSocketHardenError: (error2) => {
|
|
32753
|
+
logger2.error("orchestration.socket.chmod_failed", "failed to restrict orchestration socket to owner-only (0600); falling back to runtime dir permissions", { message: error2 instanceof Error ? error2.message : String(error2) });
|
|
32754
|
+
},
|
|
32258
32755
|
createScheduledTaskFromRoute: async (input) => await createScheduledTaskFromRoute(input, {
|
|
32259
32756
|
state,
|
|
32260
32757
|
config: config4,
|
|
@@ -32282,6 +32779,33 @@ async function buildApp(paths, deps = {}) {
|
|
|
32282
32779
|
}),
|
|
32283
32780
|
logger: logger2
|
|
32284
32781
|
});
|
|
32782
|
+
const reapWarmQueueOwners = async (phase) => {
|
|
32783
|
+
try {
|
|
32784
|
+
const targets = collectReapTargets(sessions, state.orchestration, config4);
|
|
32785
|
+
if (targets.length === 0) {
|
|
32786
|
+
return;
|
|
32787
|
+
}
|
|
32788
|
+
const { terminated, attempted } = await reapQueueOwners(acpxCommand, targets, {
|
|
32789
|
+
onError: (target, error2) => {
|
|
32790
|
+
logger2.info("transport.queue_owner_reap.failed", "failed to reap queue owner", {
|
|
32791
|
+
phase,
|
|
32792
|
+
transport_session: target.transportSession,
|
|
32793
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
32794
|
+
}).catch(() => {});
|
|
32795
|
+
}
|
|
32796
|
+
});
|
|
32797
|
+
await logger2.info("transport.queue_owner_reap.completed", "reaped warm queue owners", {
|
|
32798
|
+
phase,
|
|
32799
|
+
terminated,
|
|
32800
|
+
attempted
|
|
32801
|
+
}).catch(() => {});
|
|
32802
|
+
} catch (err) {
|
|
32803
|
+
await logger2.error("transport.queue_owner_reap.error", "queue owner reap failed", {
|
|
32804
|
+
phase,
|
|
32805
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32806
|
+
}).catch(() => {});
|
|
32807
|
+
}
|
|
32808
|
+
};
|
|
32285
32809
|
return {
|
|
32286
32810
|
agent: agent3,
|
|
32287
32811
|
router: router3,
|
|
@@ -32302,41 +32826,14 @@ async function buildApp(paths, deps = {}) {
|
|
|
32302
32826
|
service: scheduledService,
|
|
32303
32827
|
scheduler: scheduledScheduler
|
|
32304
32828
|
},
|
|
32829
|
+
reapStaleQueueOwners: () => reapWarmQueueOwners("startup"),
|
|
32305
32830
|
dispose: async () => {
|
|
32306
32831
|
scheduledScheduler.stop();
|
|
32307
32832
|
if (progressHeartbeatInterval !== undefined) {
|
|
32308
32833
|
clearInterval(progressHeartbeatInterval);
|
|
32309
32834
|
}
|
|
32310
32835
|
await Promise.allSettled([...pendingWorkerDispatches]);
|
|
32311
|
-
|
|
32312
|
-
const targets = [
|
|
32313
|
-
...sessions.listAllResolvedSessions().map((session3) => ({
|
|
32314
|
-
agent: session3.agent,
|
|
32315
|
-
...session3.agentCommand ? { agentCommand: session3.agentCommand } : {},
|
|
32316
|
-
cwd: session3.cwd,
|
|
32317
|
-
transportSession: session3.transportSession
|
|
32318
|
-
})),
|
|
32319
|
-
...workerBindingReapTargets(state.orchestration, config4)
|
|
32320
|
-
];
|
|
32321
|
-
if (targets.length > 0) {
|
|
32322
|
-
const { terminated, attempted } = await reapQueueOwners(acpxCommand, targets, {
|
|
32323
|
-
onError: (target, error2) => {
|
|
32324
|
-
logger2.info("transport.queue_owner_reap.failed", "failed to reap queue owner on shutdown", {
|
|
32325
|
-
transport_session: target.transportSession,
|
|
32326
|
-
error: error2 instanceof Error ? error2.message : String(error2)
|
|
32327
|
-
}).catch(() => {});
|
|
32328
|
-
}
|
|
32329
|
-
});
|
|
32330
|
-
await logger2.info("transport.queue_owner_reap.completed", "reaped warm queue owners on shutdown", {
|
|
32331
|
-
terminated,
|
|
32332
|
-
attempted
|
|
32333
|
-
}).catch(() => {});
|
|
32334
|
-
}
|
|
32335
|
-
} catch (err) {
|
|
32336
|
-
await logger2.error("transport.queue_owner_reap.error", "queue owner reap failed during shutdown", {
|
|
32337
|
-
error: err instanceof Error ? err.message : String(err)
|
|
32338
|
-
}).catch(() => {});
|
|
32339
|
-
}
|
|
32836
|
+
await reapWarmQueueOwners("shutdown");
|
|
32340
32837
|
await debouncedStateStore.dispose();
|
|
32341
32838
|
if ("dispose" in transport && typeof transport.dispose === "function") {
|
|
32342
32839
|
await transport.dispose();
|
|
@@ -32392,7 +32889,7 @@ async function main() {
|
|
|
32392
32889
|
}
|
|
32393
32890
|
}
|
|
32394
32891
|
async function prepareChannelMedia(configPath, config4) {
|
|
32395
|
-
const runtimeDir = join18(
|
|
32892
|
+
const runtimeDir = join18(dirname12(configPath), "runtime");
|
|
32396
32893
|
const mediaRootDir = join18(runtimeDir, "media");
|
|
32397
32894
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
32398
32895
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -32407,7 +32904,7 @@ function resolveRuntimePaths() {
|
|
|
32407
32904
|
throw new Error("Unable to resolve the current user home directory");
|
|
32408
32905
|
}
|
|
32409
32906
|
const configPath = coreEnv("CONFIG") ?? join18(coreHomeDir(home), "config.json");
|
|
32410
|
-
const runtimeDir = join18(
|
|
32907
|
+
const runtimeDir = join18(dirname12(configPath), "runtime");
|
|
32411
32908
|
return {
|
|
32412
32909
|
configPath,
|
|
32413
32910
|
statePath: coreEnv("STATE") ?? join18(coreHomeDir(home), "state.json"),
|
|
@@ -32422,12 +32919,12 @@ function resolveBridgeEntryPath() {
|
|
|
32422
32919
|
return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
32423
32920
|
}
|
|
32424
32921
|
function resolveAppLogPath(configPath) {
|
|
32425
|
-
const rootDir =
|
|
32922
|
+
const rootDir = dirname12(configPath);
|
|
32426
32923
|
const runtimeDir = join18(rootDir, "runtime");
|
|
32427
32924
|
return join18(runtimeDir, "app.log");
|
|
32428
32925
|
}
|
|
32429
32926
|
function resolvePerfLogPath(configPath) {
|
|
32430
|
-
const rootDir =
|
|
32927
|
+
const rootDir = dirname12(configPath);
|
|
32431
32928
|
const runtimeDir = join18(rootDir, "runtime");
|
|
32432
32929
|
return join18(runtimeDir, "perf.log");
|
|
32433
32930
|
}
|
|
@@ -32823,7 +33320,7 @@ var init_orchestration_health = __esm(() => {
|
|
|
32823
33320
|
// src/doctor/checks/runtime-check.ts
|
|
32824
33321
|
import { constants } from "node:fs";
|
|
32825
33322
|
import { access as access4, stat as stat3 } from "node:fs/promises";
|
|
32826
|
-
import { dirname as
|
|
33323
|
+
import { dirname as dirname13 } from "node:path";
|
|
32827
33324
|
import { homedir as homedir11 } from "node:os";
|
|
32828
33325
|
async function checkRuntime(options = {}) {
|
|
32829
33326
|
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
@@ -32924,7 +33421,7 @@ async function checkFileCreatable(label, path15, probe, platform) {
|
|
|
32924
33421
|
detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
|
|
32925
33422
|
};
|
|
32926
33423
|
}
|
|
32927
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
33424
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname13(path15), probe, platform);
|
|
32928
33425
|
if (!parentCheck.ok) {
|
|
32929
33426
|
return {
|
|
32930
33427
|
ok: false,
|
|
@@ -32960,7 +33457,7 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
|
32960
33457
|
blockingPath: path15
|
|
32961
33458
|
};
|
|
32962
33459
|
}
|
|
32963
|
-
const parent =
|
|
33460
|
+
const parent = dirname13(path15);
|
|
32964
33461
|
if (parent === path15) {
|
|
32965
33462
|
return {
|
|
32966
33463
|
ok: false,
|
|
@@ -33485,11 +33982,13 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33485
33982
|
}
|
|
33486
33983
|
try {
|
|
33487
33984
|
const store = new StateStore(deps.runtimePaths.statePath);
|
|
33488
|
-
|
|
33489
|
-
|
|
33985
|
+
const inspection = await store.inspect();
|
|
33986
|
+
const result = await checkOrchestrationHealth({
|
|
33987
|
+
loadState: async () => inspection.state,
|
|
33490
33988
|
now: () => new Date,
|
|
33491
33989
|
heartbeatThresholdSeconds: config4.orchestration.progressHeartbeatSeconds
|
|
33492
33990
|
});
|
|
33991
|
+
return applyStateInspectionReport(result, inspection.report, deps.runtimePaths.statePath);
|
|
33493
33992
|
} catch (error2) {
|
|
33494
33993
|
return {
|
|
33495
33994
|
id: "orchestration",
|
|
@@ -33500,6 +33999,27 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33500
33999
|
};
|
|
33501
34000
|
}
|
|
33502
34001
|
}
|
|
34002
|
+
function applyStateInspectionReport(result, report, statePath) {
|
|
34003
|
+
if (!report) {
|
|
34004
|
+
return result;
|
|
34005
|
+
}
|
|
34006
|
+
const fileCorrupt = report.dropped.some((record3) => record3.section === "file");
|
|
34007
|
+
const details = [
|
|
34008
|
+
...result.details ?? [],
|
|
34009
|
+
`state path: ${statePath}`,
|
|
34010
|
+
...report.dropped.map((record3) => record3.section === "file" ? `state.json is unreadable: ${record3.reason}` : `invalid state record ${record3.section}["${record3.key}"]: ${record3.reason}`)
|
|
34011
|
+
];
|
|
34012
|
+
return {
|
|
34013
|
+
...result,
|
|
34014
|
+
severity: result.severity === "fail" ? "fail" : "warn",
|
|
34015
|
+
summary: fileCorrupt ? `state.json is unreadable and will be reset (renamed to state.json.corrupt-*) at next daemon startup; ${result.summary}` : `state.json has ${report.dropped.length} invalid record(s) that will be quarantined at next daemon startup; ${result.summary}`,
|
|
34016
|
+
details,
|
|
34017
|
+
suggestions: [
|
|
34018
|
+
...result.suggestions ?? [],
|
|
34019
|
+
fileCorrupt ? "back up the state file before the next daemon start if you want to attempt manual recovery" : "the daemon backs the original file up as state.json.quarantine-* before dropping these records"
|
|
34020
|
+
]
|
|
34021
|
+
};
|
|
34022
|
+
}
|
|
33503
34023
|
function formatError9(error2) {
|
|
33504
34024
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33505
34025
|
}
|
|
@@ -33542,7 +34062,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
33542
34062
|
init_core_home();
|
|
33543
34063
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
33544
34064
|
import { homedir as homedir13 } from "node:os";
|
|
33545
|
-
import { dirname as
|
|
34065
|
+
import { dirname as dirname14, join as join20, sep } from "node:path";
|
|
33546
34066
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
33547
34067
|
|
|
33548
34068
|
// src/runtime/migrate-core-home.ts
|
|
@@ -33626,8 +34146,8 @@ init_daemon_files();
|
|
|
33626
34146
|
|
|
33627
34147
|
// src/daemon/daemon-runtime.ts
|
|
33628
34148
|
init_daemon_status();
|
|
33629
|
-
|
|
33630
|
-
import {
|
|
34149
|
+
init_private_runtime_dir();
|
|
34150
|
+
import { rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
33631
34151
|
|
|
33632
34152
|
class DaemonRuntime {
|
|
33633
34153
|
paths;
|
|
@@ -33653,8 +34173,8 @@ class DaemonRuntime {
|
|
|
33653
34173
|
stdout_log: this.paths.stdoutLog,
|
|
33654
34174
|
stderr_log: this.paths.stderrLog
|
|
33655
34175
|
};
|
|
33656
|
-
await
|
|
33657
|
-
await
|
|
34176
|
+
await ensurePrivateRuntimeDir(this.paths.runtimeDir);
|
|
34177
|
+
await writeFile2(this.paths.pidFile, `${this.options.pid}
|
|
33658
34178
|
`);
|
|
33659
34179
|
await this.statusStore.save(this.currentStatus);
|
|
33660
34180
|
}
|
|
@@ -46059,6 +46579,9 @@ function requireHome(env) {
|
|
|
46059
46579
|
return home;
|
|
46060
46580
|
}
|
|
46061
46581
|
|
|
46582
|
+
// src/mcp/xacpx-mcp-server.ts
|
|
46583
|
+
init_endpoint_probe();
|
|
46584
|
+
|
|
46062
46585
|
// src/mcp/xacpx-mcp-tools.ts
|
|
46063
46586
|
init_task_watch_timeouts();
|
|
46064
46587
|
init_quota_errors();
|
|
@@ -46326,7 +46849,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
46326
46849
|
});
|
|
46327
46850
|
tools.push({
|
|
46328
46851
|
name: "scheduled_list",
|
|
46329
|
-
description: "List pending one-shot scheduled tasks
|
|
46852
|
+
description: "List pending one-shot scheduled tasks created in the current chat. Use to recover task ids before cancelling, or to see what is scheduled. Owner-only in group chats. Routing and account are resolved from the current session; pass no other arguments.",
|
|
46330
46853
|
inputSchema: exports_external.object({}).strict(),
|
|
46331
46854
|
handler: async () => await asToolResult(async () => {
|
|
46332
46855
|
const tasks = await transport.scheduledList({ coordinatorSession });
|
|
@@ -46344,7 +46867,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
46344
46867
|
});
|
|
46345
46868
|
tools.push({
|
|
46346
46869
|
name: "scheduled_cancel",
|
|
46347
|
-
description: "Cancel a pending scheduled task by id. Owner-only in group chats. Returns whether a pending task with that id was found and cancelled. Routing is resolved from the current session.",
|
|
46870
|
+
description: "Cancel a pending scheduled task by id (only tasks created in the current chat). Owner-only in group chats. Returns whether a pending task with that id was found and cancelled. Routing is resolved from the current session.",
|
|
46348
46871
|
inputSchema: exports_external.object({
|
|
46349
46872
|
id: exports_external.string().min(1).describe("The scheduled task id, e.g. 'k8f2' (a leading # is allowed).")
|
|
46350
46873
|
}).strict(),
|
|
@@ -46574,7 +47097,7 @@ function formatToolError(error2) {
|
|
|
46574
47097
|
init_orchestration_ipc();
|
|
46575
47098
|
init_task_watch_timeouts();
|
|
46576
47099
|
import { randomUUID } from "node:crypto";
|
|
46577
|
-
import { createConnection } from "node:net";
|
|
47100
|
+
import { createConnection as createConnection2 } from "node:net";
|
|
46578
47101
|
|
|
46579
47102
|
class OrchestrationClient {
|
|
46580
47103
|
endpoint;
|
|
@@ -46642,7 +47165,7 @@ class OrchestrationClient {
|
|
|
46642
47165
|
async request(method, params, timeoutMs = this.timeoutMs) {
|
|
46643
47166
|
const id = this.createId();
|
|
46644
47167
|
return await new Promise((resolve, reject) => {
|
|
46645
|
-
const socket =
|
|
47168
|
+
const socket = createConnection2(this.endpoint.path);
|
|
46646
47169
|
let buffer = "";
|
|
46647
47170
|
let settled = false;
|
|
46648
47171
|
let timer;
|
|
@@ -47285,7 +47808,9 @@ function installMcpStdioShutdownHooks(options) {
|
|
|
47285
47808
|
const setIntervalFn = options.setIntervalFn ?? ((callback, ms) => setInterval(callback, ms));
|
|
47286
47809
|
const clearIntervalFn = options.clearIntervalFn ?? ((handle) => clearInterval(handle));
|
|
47287
47810
|
const parentPid = options.parentPid ?? process.ppid;
|
|
47288
|
-
const parentCheckIntervalMs = options.parentCheckIntervalMs ??
|
|
47811
|
+
const parentCheckIntervalMs = options.parentCheckIntervalMs ?? parseIntervalMs(coreEnv("MCP_PARENT_CHECK_INTERVAL_MS"), 5000);
|
|
47812
|
+
const endpointCheckIntervalMs = options.endpointCheckIntervalMs ?? parseIntervalMs(coreEnv("MCP_ENDPOINT_CHECK_INTERVAL_MS"), 1e4);
|
|
47813
|
+
const endpointFailureThreshold = options.endpointFailureThreshold ?? 3;
|
|
47289
47814
|
let disposed = false;
|
|
47290
47815
|
let triggered = false;
|
|
47291
47816
|
const triggerShutdown = (reason, context) => {
|
|
@@ -47318,6 +47843,31 @@ function installMcpStdioShutdownHooks(options) {
|
|
|
47318
47843
|
}, parentCheckIntervalMs);
|
|
47319
47844
|
parentTimer.unref?.();
|
|
47320
47845
|
}
|
|
47846
|
+
let endpointTimer;
|
|
47847
|
+
const probeEndpoint = options.probeEndpoint;
|
|
47848
|
+
if (probeEndpoint && endpointCheckIntervalMs > 0 && endpointFailureThreshold > 0) {
|
|
47849
|
+
let consecutiveDead = 0;
|
|
47850
|
+
let probing = false;
|
|
47851
|
+
const runEndpointCheck = async () => {
|
|
47852
|
+
if (disposed || triggered || probing)
|
|
47853
|
+
return;
|
|
47854
|
+
probing = true;
|
|
47855
|
+
try {
|
|
47856
|
+
if (await probeEndpoint()) {
|
|
47857
|
+
consecutiveDead = 0;
|
|
47858
|
+
return;
|
|
47859
|
+
}
|
|
47860
|
+
consecutiveDead += 1;
|
|
47861
|
+
if (consecutiveDead >= endpointFailureThreshold) {
|
|
47862
|
+
triggerShutdown("daemon_endpoint_dead", { consecutiveFailures: consecutiveDead });
|
|
47863
|
+
}
|
|
47864
|
+
} catch {} finally {
|
|
47865
|
+
probing = false;
|
|
47866
|
+
}
|
|
47867
|
+
};
|
|
47868
|
+
endpointTimer = setIntervalFn(runEndpointCheck, endpointCheckIntervalMs);
|
|
47869
|
+
endpointTimer.unref?.();
|
|
47870
|
+
}
|
|
47321
47871
|
return () => {
|
|
47322
47872
|
if (disposed)
|
|
47323
47873
|
return;
|
|
@@ -47332,13 +47882,16 @@ function installMcpStdioShutdownHooks(options) {
|
|
|
47332
47882
|
if (parentTimer) {
|
|
47333
47883
|
clearIntervalFn(parentTimer);
|
|
47334
47884
|
}
|
|
47885
|
+
if (endpointTimer) {
|
|
47886
|
+
clearIntervalFn(endpointTimer);
|
|
47887
|
+
}
|
|
47335
47888
|
};
|
|
47336
47889
|
}
|
|
47337
|
-
function
|
|
47890
|
+
function parseIntervalMs(raw, fallback) {
|
|
47338
47891
|
if (raw === undefined || raw.trim().length === 0)
|
|
47339
|
-
return
|
|
47892
|
+
return fallback;
|
|
47340
47893
|
const parsed = Number(raw);
|
|
47341
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed :
|
|
47894
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
47342
47895
|
}
|
|
47343
47896
|
function errorContext(error2) {
|
|
47344
47897
|
const record3 = error2;
|
|
@@ -47357,7 +47910,8 @@ function defaultIsProcessRunning3(pid) {
|
|
|
47357
47910
|
}
|
|
47358
47911
|
}
|
|
47359
47912
|
async function runXacpxMcpServer(options) {
|
|
47360
|
-
const
|
|
47913
|
+
const endpoint = options.endpoint ?? resolveDefaultOrchestrationEndpoint(process.env, process.platform);
|
|
47914
|
+
const transport = options.transport ?? createOrchestrationTransport(endpoint);
|
|
47361
47915
|
const server = createXacpxMcpServer({
|
|
47362
47916
|
transport,
|
|
47363
47917
|
...options.coordinatorSession ? { coordinatorSession: options.coordinatorSession } : {},
|
|
@@ -47390,6 +47944,7 @@ async function runXacpxMcpServer(options) {
|
|
|
47390
47944
|
stdin,
|
|
47391
47945
|
stdout,
|
|
47392
47946
|
shutdown,
|
|
47947
|
+
probeEndpoint: () => canConnectToEndpoint(endpoint.path, 4000),
|
|
47393
47948
|
onDiagnostic: options.onDiagnostic
|
|
47394
47949
|
});
|
|
47395
47950
|
await server.connect(stdio);
|
|
@@ -47422,7 +47977,6 @@ function sanitizeMcpClientName(input) {
|
|
|
47422
47977
|
fallback: "mcp-host"
|
|
47423
47978
|
});
|
|
47424
47979
|
}
|
|
47425
|
-
|
|
47426
47980
|
// src/mcp/parse-string-flag.ts
|
|
47427
47981
|
function parseStringFlag(args, env, options) {
|
|
47428
47982
|
let fromFlag = null;
|
|
@@ -47523,7 +48077,10 @@ async function maybeRunFirstUseOnboarding(input) {
|
|
|
47523
48077
|
const agentExisted = Boolean(input.config.agents[agentName]);
|
|
47524
48078
|
input.config.workspaces[workspaceName] = { cwd };
|
|
47525
48079
|
input.config.agents[agentName] = template;
|
|
47526
|
-
await input.
|
|
48080
|
+
await input.saveFirstRunConfig({
|
|
48081
|
+
workspace: { name: workspaceName, cwd },
|
|
48082
|
+
agent: { name: agentName, config: template }
|
|
48083
|
+
});
|
|
47527
48084
|
const alias = `${workspaceName}:${agentName}`;
|
|
47528
48085
|
input.deps.print(t().misc.onboardingCreatedWorkspace(workspaceName, alias));
|
|
47529
48086
|
return {
|
|
@@ -47546,8 +48103,8 @@ function resolveTemplateChoice(answer, names) {
|
|
|
47546
48103
|
// src/cli-update.ts
|
|
47547
48104
|
init_plugin_home();
|
|
47548
48105
|
import { spawn as spawn4 } from "node:child_process";
|
|
47549
|
-
import { readFile as
|
|
47550
|
-
import { dirname as
|
|
48106
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
48107
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
47551
48108
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
47552
48109
|
|
|
47553
48110
|
// src/plugins/package-manager.ts
|
|
@@ -47555,9 +48112,14 @@ init_plugin_home();
|
|
|
47555
48112
|
import { spawn as spawn3 } from "node:child_process";
|
|
47556
48113
|
import { rm as rm4 } from "node:fs/promises";
|
|
47557
48114
|
import { join as join7 } from "node:path";
|
|
48115
|
+
function shellSpawnPlan(args) {
|
|
48116
|
+
const shell = process.platform === "win32";
|
|
48117
|
+
return { shell, args: shell ? args.map((arg) => `"${arg}"`) : args };
|
|
48118
|
+
}
|
|
47558
48119
|
async function defaultRunCommand(command, args, options) {
|
|
47559
48120
|
await new Promise((resolve, reject) => {
|
|
47560
|
-
const
|
|
48121
|
+
const plan = shellSpawnPlan(args);
|
|
48122
|
+
const child = spawn3(command, plan.args, { cwd: options.cwd, stdio: "inherit", shell: plan.shell });
|
|
47561
48123
|
child.on("error", reject);
|
|
47562
48124
|
child.on("exit", (code) => {
|
|
47563
48125
|
if (code === 0)
|
|
@@ -47569,7 +48131,8 @@ async function defaultRunCommand(command, args, options) {
|
|
|
47569
48131
|
}
|
|
47570
48132
|
async function silentRun(command, args, options) {
|
|
47571
48133
|
await new Promise((resolve, reject) => {
|
|
47572
|
-
const
|
|
48134
|
+
const plan = shellSpawnPlan(args);
|
|
48135
|
+
const child = spawn3(command, plan.args, { cwd: options.cwd, stdio: "ignore", shell: plan.shell });
|
|
47573
48136
|
child.on("error", reject);
|
|
47574
48137
|
child.on("exit", (code) => {
|
|
47575
48138
|
if (code === 0)
|
|
@@ -47658,7 +48221,6 @@ async function handleUpdateCli(args, deps) {
|
|
|
47658
48221
|
kind: "plugin",
|
|
47659
48222
|
name: plugin.name,
|
|
47660
48223
|
currentVersion: plugin.version,
|
|
47661
|
-
pinned: Boolean(plugin.version),
|
|
47662
48224
|
latestVersion: await latestOf(plugin.name)
|
|
47663
48225
|
});
|
|
47664
48226
|
}
|
|
@@ -47667,12 +48229,12 @@ async function handleUpdateCli(args, deps) {
|
|
|
47667
48229
|
const target = targets[index];
|
|
47668
48230
|
deps.print(`${index + 1}. ${formatTarget(target)}`);
|
|
47669
48231
|
}
|
|
47670
|
-
const unavailable = targets.filter((target) => !target.latestVersion
|
|
48232
|
+
const unavailable = targets.filter((target) => !target.latestVersion);
|
|
47671
48233
|
if (all && unavailable.length > 0) {
|
|
47672
48234
|
deps.print(t().cliUpdate.unavailableAborted(unavailable.map((target) => target.name).join(", ")));
|
|
47673
48235
|
return 1;
|
|
47674
48236
|
}
|
|
47675
|
-
const candidates = targets.filter((target) => target.latestVersion && (target.
|
|
48237
|
+
const candidates = targets.filter((target) => target.latestVersion && (target.successorPackage ? true : target.currentVersion !== target.latestVersion));
|
|
47676
48238
|
const selected = await selectTargets(targets, candidates, { all, explicitTarget: explicitTargets[0], deps });
|
|
47677
48239
|
if (!selected.ok) {
|
|
47678
48240
|
deps.print(selected.message);
|
|
@@ -47746,7 +48308,7 @@ async function handleUpdateCli(args, deps) {
|
|
|
47746
48308
|
}
|
|
47747
48309
|
if (selected.targets.some((target) => target.kind === "plugin")) {
|
|
47748
48310
|
config4.plugins = updatedPlugins;
|
|
47749
|
-
await deps.
|
|
48311
|
+
await deps.savePlugins(updatedPlugins);
|
|
47750
48312
|
}
|
|
47751
48313
|
return 0;
|
|
47752
48314
|
}
|
|
@@ -47765,8 +48327,6 @@ async function selectTargets(targets, candidates, input) {
|
|
|
47765
48327
|
return { ok: false, message: t().cliUpdate.targetNotFound(input.explicitTarget), exitCode: 1 };
|
|
47766
48328
|
if (!target.latestVersion)
|
|
47767
48329
|
return { ok: false, message: t().cliUpdate.targetVersionUnknown(target.name), exitCode: 1 };
|
|
47768
|
-
if (target.kind === "plugin" && !target.pinned)
|
|
47769
|
-
return { ok: false, message: t().cliUpdate.targetNotPinned(target.name), exitCode: 1 };
|
|
47770
48330
|
if (!target.successorPackage && target.currentVersion === target.latestVersion)
|
|
47771
48331
|
return { ok: true, targets: [] };
|
|
47772
48332
|
return { ok: true, targets: [target] };
|
|
@@ -47790,8 +48350,6 @@ async function selectTargets(targets, candidates, input) {
|
|
|
47790
48350
|
const target = targets[index - 1];
|
|
47791
48351
|
if (!target.latestVersion)
|
|
47792
48352
|
return { ok: false, message: t().cliUpdate.targetVersionUnknown(target.name), exitCode: 1 };
|
|
47793
|
-
if (target.kind === "plugin" && !target.pinned)
|
|
47794
|
-
return { ok: false, message: t().cliUpdate.targetNotPinned(target.name), exitCode: 1 };
|
|
47795
48353
|
if (!target.successorPackage && target.currentVersion === target.latestVersion)
|
|
47796
48354
|
continue;
|
|
47797
48355
|
if (!selected.includes(target))
|
|
@@ -47860,9 +48418,10 @@ function compareSemver2(a, b) {
|
|
|
47860
48418
|
return 0;
|
|
47861
48419
|
return left.prerelease ? -1 : 1;
|
|
47862
48420
|
}
|
|
48421
|
+
var spawnUsesShell = () => process.platform === "win32";
|
|
47863
48422
|
async function runCapture(command, args) {
|
|
47864
48423
|
return await new Promise((resolve, reject) => {
|
|
47865
|
-
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
48424
|
+
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: spawnUsesShell() });
|
|
47866
48425
|
let stdout2 = "";
|
|
47867
48426
|
let stderr = "";
|
|
47868
48427
|
child.stdout.setEncoding("utf8");
|
|
@@ -47879,7 +48438,7 @@ async function runCapture(command, args) {
|
|
|
47879
48438
|
}
|
|
47880
48439
|
async function runInherit(command, args) {
|
|
47881
48440
|
await new Promise((resolve, reject) => {
|
|
47882
|
-
const child = spawn4(command, args, { stdio: "inherit" });
|
|
48441
|
+
const child = spawn4(command, args, { stdio: "inherit", shell: spawnUsesShell() });
|
|
47883
48442
|
child.on("error", reject);
|
|
47884
48443
|
child.on("exit", (code) => {
|
|
47885
48444
|
if (code === 0)
|
|
@@ -47891,10 +48450,10 @@ async function runInherit(command, args) {
|
|
|
47891
48450
|
}
|
|
47892
48451
|
async function readPackageName() {
|
|
47893
48452
|
try {
|
|
47894
|
-
const here =
|
|
48453
|
+
const here = dirname8(fileURLToPath3(import.meta.url));
|
|
47895
48454
|
for (const candidate of [join11(here, "..", "package.json"), join11(here, "..", "..", "package.json")]) {
|
|
47896
48455
|
try {
|
|
47897
|
-
const parsed = JSON.parse(await
|
|
48456
|
+
const parsed = JSON.parse(await readFile9(candidate, "utf8"));
|
|
47898
48457
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
47899
48458
|
return parsed.name.trim();
|
|
47900
48459
|
} catch {}
|
|
@@ -48136,7 +48695,7 @@ async function addChannel(type, rawArgs, deps) {
|
|
|
48136
48695
|
return 1;
|
|
48137
48696
|
}
|
|
48138
48697
|
config4.channels = [...config4.channels ?? [], candidate];
|
|
48139
|
-
await deps.
|
|
48698
|
+
await deps.saveChannels(config4.channels);
|
|
48140
48699
|
deps.print(t().channelCli.channelAdded(type));
|
|
48141
48700
|
for (const line of provider.renderSummary(candidate))
|
|
48142
48701
|
deps.print(line);
|
|
@@ -48237,7 +48796,7 @@ async function removeChannel(type, rawArgs, deps) {
|
|
|
48237
48796
|
return 1;
|
|
48238
48797
|
}
|
|
48239
48798
|
config4.channels = config4.channels.filter((entry) => entry.id !== channel.id);
|
|
48240
|
-
await deps.
|
|
48799
|
+
await deps.saveChannels(config4.channels);
|
|
48241
48800
|
deps.print(t().channelCli.channelRemoved(channel.id));
|
|
48242
48801
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48243
48802
|
}
|
|
@@ -48259,7 +48818,7 @@ async function setChannelEnabled(type, enabled, rawArgs, deps) {
|
|
|
48259
48818
|
return 1;
|
|
48260
48819
|
}
|
|
48261
48820
|
channel.enabled = enabled;
|
|
48262
|
-
await deps.
|
|
48821
|
+
await deps.saveChannels(config4.channels);
|
|
48263
48822
|
deps.print(t().channelCli.channelEnabledToggled(channel.id, enabled));
|
|
48264
48823
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48265
48824
|
}
|
|
@@ -48281,7 +48840,7 @@ async function setChannelReplyMode(type, mode, rawArgs, deps) {
|
|
|
48281
48840
|
return 1;
|
|
48282
48841
|
}
|
|
48283
48842
|
channel.replyMode = mode;
|
|
48284
|
-
await deps.
|
|
48843
|
+
await deps.saveChannels(config4.channels);
|
|
48285
48844
|
deps.print(t().channelCli.channelReplyModeSet(channel.id, mode));
|
|
48286
48845
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48287
48846
|
}
|
|
@@ -48427,7 +48986,7 @@ async function addChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48427
48986
|
deps.print(validation.map((issue2) => issue2.message).join(";"));
|
|
48428
48987
|
return 1;
|
|
48429
48988
|
}
|
|
48430
|
-
await deps.
|
|
48989
|
+
await deps.saveChannels(config4.channels);
|
|
48431
48990
|
deps.print(t().channelCli.channelAccountAdded(type, accountId));
|
|
48432
48991
|
if (reEnabledChannel)
|
|
48433
48992
|
deps.print(t().channelCli.channelReEnabled(type));
|
|
@@ -48467,7 +49026,7 @@ async function removeChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48467
49026
|
return 1;
|
|
48468
49027
|
}
|
|
48469
49028
|
config4.channels = config4.channels.filter((channel) => channel.id !== existing.id);
|
|
48470
|
-
await deps.
|
|
49029
|
+
await deps.saveChannels(config4.channels);
|
|
48471
49030
|
deps.print(t().channelCli.channelAccountRemovedWithChannel(type, accountId));
|
|
48472
49031
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48473
49032
|
}
|
|
@@ -48484,7 +49043,7 @@ async function removeChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48484
49043
|
deps.print(t().channelCli.channelAccountDefaultSwitched(options.defaultAccount));
|
|
48485
49044
|
}
|
|
48486
49045
|
existing.options = options;
|
|
48487
|
-
await deps.
|
|
49046
|
+
await deps.saveChannels(config4.channels);
|
|
48488
49047
|
deps.print(t().channelCli.channelAccountRemoved(type, accountId));
|
|
48489
49048
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48490
49049
|
}
|
|
@@ -48538,7 +49097,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
48538
49097
|
return 1;
|
|
48539
49098
|
}
|
|
48540
49099
|
existing.options = options;
|
|
48541
|
-
await deps.
|
|
49100
|
+
await deps.saveChannels(config4.channels);
|
|
48542
49101
|
deps.print(t().channelCli.channelAccountEnabledToggled(type, accountId, enabled));
|
|
48543
49102
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48544
49103
|
}
|
|
@@ -48546,7 +49105,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
48546
49105
|
// src/plugins/plugin-cli.ts
|
|
48547
49106
|
init_core_home();
|
|
48548
49107
|
init_plugin_home();
|
|
48549
|
-
import { readFile as
|
|
49108
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
48550
49109
|
import { isAbsolute, join as join13, resolve } from "node:path";
|
|
48551
49110
|
init_plugin_loader();
|
|
48552
49111
|
init_validate_plugin();
|
|
@@ -48556,14 +49115,15 @@ init_channel_scope();
|
|
|
48556
49115
|
init_plugin_loader();
|
|
48557
49116
|
init_validate_plugin();
|
|
48558
49117
|
init_known_plugins();
|
|
48559
|
-
|
|
49118
|
+
init_plugin_renames();
|
|
49119
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
48560
49120
|
import { join as join12 } from "node:path";
|
|
48561
49121
|
function suggestedPluginPackageForChannel(type) {
|
|
48562
49122
|
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
48563
49123
|
}
|
|
48564
49124
|
async function readDependencyEntries(pluginHome) {
|
|
48565
49125
|
try {
|
|
48566
|
-
const raw = await
|
|
49126
|
+
const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
|
|
48567
49127
|
const parsed = JSON.parse(raw);
|
|
48568
49128
|
const out = {};
|
|
48569
49129
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -48587,8 +49147,8 @@ async function inspectPlugins(input) {
|
|
|
48587
49147
|
}
|
|
48588
49148
|
const importPlugin = input.importPlugin ?? importPluginFromHome;
|
|
48589
49149
|
const allConfigured = input.config.plugins;
|
|
48590
|
-
const filterByName = input.pluginName
|
|
48591
|
-
if (filterByName && !allConfigured.some((plugin) => plugin.name === filterByName)) {
|
|
49150
|
+
const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
|
|
49151
|
+
if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
|
|
48592
49152
|
return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
|
|
48593
49153
|
}
|
|
48594
49154
|
const pushIfRelevant = (issue2) => {
|
|
@@ -48635,6 +49195,8 @@ async function inspectPlugins(input) {
|
|
|
48635
49195
|
}
|
|
48636
49196
|
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
48637
49197
|
for (const channel of input.config.channels) {
|
|
49198
|
+
if (channel.enabled === false)
|
|
49199
|
+
continue;
|
|
48638
49200
|
if (builtInChannelTypes.has(channel.type))
|
|
48639
49201
|
continue;
|
|
48640
49202
|
const provider = channelProviders.get(channel.type);
|
|
@@ -48662,12 +49224,31 @@ async function inspectPlugins(input) {
|
|
|
48662
49224
|
init_known_plugins();
|
|
48663
49225
|
init_plugin_renames();
|
|
48664
49226
|
init_i18n();
|
|
49227
|
+
function findPluginSpecViolation(spec, platform) {
|
|
49228
|
+
if (spec.includes('"'))
|
|
49229
|
+
return "double-quote";
|
|
49230
|
+
if (platform === "win32" && spec.includes("%"))
|
|
49231
|
+
return "percent-on-windows";
|
|
49232
|
+
return null;
|
|
49233
|
+
}
|
|
49234
|
+
function invalidSpecMessage(specs, platform) {
|
|
49235
|
+
for (const spec of specs) {
|
|
49236
|
+
if (!spec)
|
|
49237
|
+
continue;
|
|
49238
|
+
const violation = findPluginSpecViolation(spec, platform);
|
|
49239
|
+
if (violation === "double-quote")
|
|
49240
|
+
return t().pluginCli.pluginSpecHasDoubleQuote(spec);
|
|
49241
|
+
if (violation === "percent-on-windows")
|
|
49242
|
+
return t().pluginCli.pluginSpecHasPercentOnWindows(spec);
|
|
49243
|
+
}
|
|
49244
|
+
return null;
|
|
49245
|
+
}
|
|
48665
49246
|
function looksLikePath(spec) {
|
|
48666
49247
|
return spec === "." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || spec.startsWith(".\\") || spec.startsWith("..\\") || spec.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(spec) || isAbsolute(spec);
|
|
48667
49248
|
}
|
|
48668
49249
|
async function readDependencyEntries2(pluginHome) {
|
|
48669
49250
|
try {
|
|
48670
|
-
const raw = await
|
|
49251
|
+
const raw = await readFile11(join13(pluginHome, "package.json"), "utf8");
|
|
48671
49252
|
const parsed = JSON.parse(raw);
|
|
48672
49253
|
const out = {};
|
|
48673
49254
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -48693,7 +49274,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
|
|
|
48693
49274
|
return name;
|
|
48694
49275
|
}
|
|
48695
49276
|
try {
|
|
48696
|
-
const raw = await
|
|
49277
|
+
const raw = await readFile11(join13(installSpec, "package.json"), "utf8");
|
|
48697
49278
|
const parsed = JSON.parse(raw);
|
|
48698
49279
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
48699
49280
|
return parsed.name.trim();
|
|
@@ -48836,6 +49417,11 @@ async function addPlugin(packageSpec, rawArgs, deps) {
|
|
|
48836
49417
|
deps.print(t().pluginCli.unrecognizedArgs(flags.rest.join(" ")));
|
|
48837
49418
|
return 1;
|
|
48838
49419
|
}
|
|
49420
|
+
const invalidSpec = invalidSpecMessage([packageSpec, flags.version], deps.platform ?? process.platform);
|
|
49421
|
+
if (invalidSpec) {
|
|
49422
|
+
deps.print(invalidSpec);
|
|
49423
|
+
return 1;
|
|
49424
|
+
}
|
|
48839
49425
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
48840
49426
|
await ensurePluginHome(pluginHome);
|
|
48841
49427
|
const installSpec = looksLikePath(packageSpec) && !isAbsolute(packageSpec) ? resolve(process.cwd(), packageSpec) : packageSpec;
|
|
@@ -48873,7 +49459,7 @@ async function addPlugin(packageSpec, rawArgs, deps) {
|
|
|
48873
49459
|
} else {
|
|
48874
49460
|
config4.plugins = [...config4.plugins, next];
|
|
48875
49461
|
}
|
|
48876
|
-
await deps.
|
|
49462
|
+
await deps.savePlugins(config4.plugins);
|
|
48877
49463
|
deps.print(t().pluginCli.pluginInstalled(recordedName));
|
|
48878
49464
|
if (summary.channels.length > 0) {
|
|
48879
49465
|
deps.print(t().pluginCli.providesChannels(summary.channels.join(", ")));
|
|
@@ -48899,7 +49485,7 @@ async function removePlugin(packageName, rawArgs, deps) {
|
|
|
48899
49485
|
}
|
|
48900
49486
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
48901
49487
|
const validate = deps.validateInstalledPlugin ?? ((name) => validateInstalledPluginDefault(name, pluginHome));
|
|
48902
|
-
const guard = await dependencyGuard(
|
|
49488
|
+
const guard = await dependencyGuard(existing.name, config4, validate);
|
|
48903
49489
|
if (!guard.allow) {
|
|
48904
49490
|
if (guard.reason)
|
|
48905
49491
|
deps.print(guard.reason);
|
|
@@ -48909,14 +49495,14 @@ async function removePlugin(packageName, rawArgs, deps) {
|
|
|
48909
49495
|
await removePluginPackage({ packageName: name, pluginHome });
|
|
48910
49496
|
});
|
|
48911
49497
|
try {
|
|
48912
|
-
await remove(
|
|
49498
|
+
await remove(existing.name);
|
|
48913
49499
|
} catch (error2) {
|
|
48914
|
-
deps.print(t().pluginCli.pluginUninstallFailed(
|
|
49500
|
+
deps.print(t().pluginCli.pluginUninstallFailed(existing.name, describeError(error2)));
|
|
48915
49501
|
return 1;
|
|
48916
49502
|
}
|
|
48917
|
-
config4.plugins = config4.plugins.filter((entry) => entry.name !==
|
|
48918
|
-
await deps.
|
|
48919
|
-
deps.print(t().pluginCli.pluginRemoved(
|
|
49503
|
+
config4.plugins = config4.plugins.filter((entry) => entry.name !== existing.name);
|
|
49504
|
+
await deps.savePlugins(config4.plugins);
|
|
49505
|
+
deps.print(t().pluginCli.pluginRemoved(existing.name));
|
|
48920
49506
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48921
49507
|
}
|
|
48922
49508
|
async function updatePlugins(args, deps) {
|
|
@@ -48936,6 +49522,11 @@ async function updatePlugins(args, deps) {
|
|
|
48936
49522
|
deps.print("--all cannot be combined with --version");
|
|
48937
49523
|
return 1;
|
|
48938
49524
|
}
|
|
49525
|
+
const invalidSpec = invalidSpecMessage([flags.version], deps.platform ?? process.platform);
|
|
49526
|
+
if (invalidSpec) {
|
|
49527
|
+
deps.print(invalidSpec);
|
|
49528
|
+
return 1;
|
|
49529
|
+
}
|
|
48939
49530
|
const config4 = await deps.loadConfig();
|
|
48940
49531
|
ensurePluginsArray(config4);
|
|
48941
49532
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
@@ -48992,7 +49583,7 @@ async function updatePlugins(args, deps) {
|
|
|
48992
49583
|
deps.print(t().pluginCli.providesChannels(summary.channels.join(", ")));
|
|
48993
49584
|
}
|
|
48994
49585
|
}
|
|
48995
|
-
await deps.
|
|
49586
|
+
await deps.savePlugins(config4.plugins);
|
|
48996
49587
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48997
49588
|
}
|
|
48998
49589
|
async function setPluginEnabled(packageName, enabled, rawArgs, deps) {
|
|
@@ -49023,7 +49614,7 @@ async function setPluginEnabled(packageName, enabled, rawArgs, deps) {
|
|
|
49023
49614
|
}
|
|
49024
49615
|
}
|
|
49025
49616
|
existing.enabled = enabled;
|
|
49026
|
-
await deps.
|
|
49617
|
+
await deps.savePlugins(config4.plugins);
|
|
49027
49618
|
deps.print(t().pluginCli.pluginEnabledToggled(packageName, enabled));
|
|
49028
49619
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
49029
49620
|
}
|
|
@@ -49205,7 +49796,7 @@ init_i18n();
|
|
|
49205
49796
|
init_bootstrap();
|
|
49206
49797
|
async function prepareMcpCoordinatorStartup(input) {
|
|
49207
49798
|
const coordinatorSession = input.coordinatorSession.trim();
|
|
49208
|
-
const existingSession = Object.values(input.state.sessions).find((session3) => session3.transport_session === coordinatorSession);
|
|
49799
|
+
const existingSession = Object.values(input.state.sessions).find((session3) => stableCoordinatorSession(session3.transport_session) === stableCoordinatorSession(coordinatorSession));
|
|
49209
49800
|
const workspace3 = input.workspace?.trim();
|
|
49210
49801
|
if (workspace3) {
|
|
49211
49802
|
if (existingSession) {
|
|
@@ -49251,10 +49842,10 @@ function createMcpStdioIdentityResolver(input) {
|
|
|
49251
49842
|
const workspace3 = input.workspace?.trim() || null;
|
|
49252
49843
|
const sourceHandle = input.sourceHandle?.trim() || null;
|
|
49253
49844
|
const resolvedWorkspace = workspace3;
|
|
49254
|
-
const resolvedCoordinatorSession = parsedCoordinatorSession ?? inferExternalCoordinatorSession({
|
|
49845
|
+
const resolvedCoordinatorSession = stableCoordinatorSession(parsedCoordinatorSession ?? inferExternalCoordinatorSession({
|
|
49255
49846
|
clientName: context.clientName,
|
|
49256
49847
|
...resolvedWorkspace ? { workspace: resolvedWorkspace } : { instanceId }
|
|
49257
|
-
});
|
|
49848
|
+
}));
|
|
49258
49849
|
const startup = await prepareMcpCoordinatorStartup({
|
|
49259
49850
|
coordinatorSession: resolvedCoordinatorSession,
|
|
49260
49851
|
...resolvedWorkspace ? { workspace: resolvedWorkspace } : {},
|
|
@@ -49541,7 +50132,9 @@ async function defaultUpdate(args, input) {
|
|
|
49541
50132
|
const store = await createCliConfigStore();
|
|
49542
50133
|
const deps = {
|
|
49543
50134
|
loadConfig: async () => await store.load(),
|
|
49544
|
-
|
|
50135
|
+
savePlugins: async (plugins) => {
|
|
50136
|
+
await store.replacePlugins(plugins);
|
|
50137
|
+
},
|
|
49545
50138
|
readCurrentVersion: readVersion,
|
|
49546
50139
|
print: input.print,
|
|
49547
50140
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
@@ -49550,6 +50143,27 @@ async function defaultUpdate(args, input) {
|
|
|
49550
50143
|
};
|
|
49551
50144
|
return await handleUpdateCli(args, deps);
|
|
49552
50145
|
}
|
|
50146
|
+
function warnStateLoadReport(store, writeStderr = (text) => process.stderr.write(text)) {
|
|
50147
|
+
const report = store.lastLoadReport;
|
|
50148
|
+
if (!report)
|
|
50149
|
+
return;
|
|
50150
|
+
for (const record3 of report.dropped) {
|
|
50151
|
+
writeStderr(`[xacpx] state.record_quarantined section=${record3.section}${record3.key ? ` key=${record3.key}` : ""} reason=${record3.reason}
|
|
50152
|
+
`);
|
|
50153
|
+
}
|
|
50154
|
+
if (report.corruptPath) {
|
|
50155
|
+
writeStderr(`[xacpx] state.file_corrupt unreadable state.json renamed to ${report.corruptPath}
|
|
50156
|
+
`);
|
|
50157
|
+
}
|
|
50158
|
+
if (report.quarantinePath) {
|
|
50159
|
+
writeStderr(`[xacpx] state.file_quarantined original state.json backed up to ${report.quarantinePath}
|
|
50160
|
+
`);
|
|
50161
|
+
}
|
|
50162
|
+
if (report.backupError) {
|
|
50163
|
+
writeStderr(`[xacpx] state.quarantine_backup_failed ${report.backupError}
|
|
50164
|
+
`);
|
|
50165
|
+
}
|
|
50166
|
+
}
|
|
49553
50167
|
async function runOnboardingBeforeStart(input) {
|
|
49554
50168
|
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
49555
50169
|
await ensureConfigExists(runtimePaths.configPath);
|
|
@@ -49557,10 +50171,14 @@ async function runOnboardingBeforeStart(input) {
|
|
|
49557
50171
|
const stateStore = new StateStore(runtimePaths.statePath);
|
|
49558
50172
|
const config4 = await configStore.load();
|
|
49559
50173
|
const state = await stateStore.load();
|
|
50174
|
+
warnStateLoadReport(stateStore);
|
|
49560
50175
|
const result = await maybeRunFirstUseOnboarding({
|
|
49561
50176
|
config: config4,
|
|
49562
50177
|
state,
|
|
49563
|
-
|
|
50178
|
+
saveFirstRunConfig: async ({ workspace: workspace3, agent: agent3 }) => {
|
|
50179
|
+
await configStore.upsertWorkspace(workspace3.name, workspace3.cwd);
|
|
50180
|
+
await configStore.upsertAgent(agent3.name, agent3.config);
|
|
50181
|
+
},
|
|
49564
50182
|
deps: {
|
|
49565
50183
|
print: input.print,
|
|
49566
50184
|
cwd: input.cwd,
|
|
@@ -49771,7 +50389,7 @@ async function handleLaterCli(args, deps) {
|
|
|
49771
50389
|
}
|
|
49772
50390
|
async function laterList(print) {
|
|
49773
50391
|
const scheduled = await createCliScheduledTaskService();
|
|
49774
|
-
print(renderLaterList(scheduled.
|
|
50392
|
+
print(renderLaterList(scheduled.listPendingAllChats(), (alias) => toDisplaySessionAlias(alias)));
|
|
49775
50393
|
return 0;
|
|
49776
50394
|
}
|
|
49777
50395
|
async function laterCancel(rawId, print) {
|
|
@@ -49781,7 +50399,7 @@ async function laterCancel(rawId, print) {
|
|
|
49781
50399
|
return 1;
|
|
49782
50400
|
}
|
|
49783
50401
|
const scheduled = await createCliScheduledTaskService();
|
|
49784
|
-
const ok = await scheduled.
|
|
50402
|
+
const ok = await scheduled.cancelPendingAnyChat(id);
|
|
49785
50403
|
if (!ok) {
|
|
49786
50404
|
print(t().cli.laterNotFound(id));
|
|
49787
50405
|
print(t().cli.laterNotFoundHint);
|
|
@@ -49794,6 +50412,7 @@ async function createCliScheduledTaskService() {
|
|
|
49794
50412
|
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
49795
50413
|
const stateStore = new StateStore(runtimePaths.statePath);
|
|
49796
50414
|
const state = await stateStore.load();
|
|
50415
|
+
warnStateLoadReport(stateStore);
|
|
49797
50416
|
return new ScheduledTaskService(state, stateStore);
|
|
49798
50417
|
}
|
|
49799
50418
|
function resolveConfigPathForCurrentEnv() {
|
|
@@ -49896,14 +50515,12 @@ async function createFirstRunSession(runtime, plan) {
|
|
|
49896
50515
|
}
|
|
49897
50516
|
async function rollbackFirstRunConfig(runtime, plan) {
|
|
49898
50517
|
try {
|
|
49899
|
-
|
|
49900
|
-
|
|
49901
|
-
delete config4.workspaces[plan.workspace];
|
|
50518
|
+
if (!plan.rollback.workspaceExisted) {
|
|
50519
|
+
await runtime.configStore.removeWorkspace(plan.workspace);
|
|
49902
50520
|
}
|
|
49903
|
-
if (!plan.rollback.agentExisted
|
|
49904
|
-
|
|
50521
|
+
if (!plan.rollback.agentExisted) {
|
|
50522
|
+
await runtime.configStore.removeAgent(plan.agent);
|
|
49905
50523
|
}
|
|
49906
|
-
await runtime.configStore.save(config4);
|
|
49907
50524
|
} catch (error2) {
|
|
49908
50525
|
await runtime.logger.error("onboarding.rollback_failed", "failed to roll back first-run config", {
|
|
49909
50526
|
alias: plan.alias,
|
|
@@ -49935,7 +50552,9 @@ async function defaultMcpStdio(args, deps = {}) {
|
|
|
49935
50552
|
await ensureConfigExists(runtimePaths.configPath);
|
|
49936
50553
|
const config4 = await loadConfig(runtimePaths.configPath);
|
|
49937
50554
|
availableAgents = Object.keys(config4.agents);
|
|
49938
|
-
const
|
|
50555
|
+
const stateStore = new StateStore(runtimePaths.statePath);
|
|
50556
|
+
const state = await stateStore.load();
|
|
50557
|
+
warnStateLoadReport(stateStore, deps.stderr ?? ((text) => process.stderr.write(text)));
|
|
49939
50558
|
const resolveIdentity = createMcpStdioIdentityResolver({
|
|
49940
50559
|
parsedCoordinatorSession,
|
|
49941
50560
|
sourceHandle,
|
|
@@ -49999,7 +50618,9 @@ async function createChannelCliDeps(input) {
|
|
|
49999
50618
|
const controller = input.controller ?? createDefaultController();
|
|
50000
50619
|
const base = {
|
|
50001
50620
|
loadConfig: async () => await store.load(),
|
|
50002
|
-
|
|
50621
|
+
saveChannels: async (channels) => {
|
|
50622
|
+
await store.replaceChannels(channels);
|
|
50623
|
+
},
|
|
50003
50624
|
print: input.print,
|
|
50004
50625
|
stderr: input.stderr ?? ((text) => process.stderr.write(text)),
|
|
50005
50626
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
@@ -50022,7 +50643,9 @@ async function createPluginCliDeps(input) {
|
|
|
50022
50643
|
const controller = input.controller ?? createDefaultController();
|
|
50023
50644
|
const base = {
|
|
50024
50645
|
loadConfig: async () => await store.load(),
|
|
50025
|
-
|
|
50646
|
+
savePlugins: async (plugins) => {
|
|
50647
|
+
await store.replacePlugins(plugins);
|
|
50648
|
+
},
|
|
50026
50649
|
print: input.print,
|
|
50027
50650
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
50028
50651
|
promptText: input.promptText ?? defaultPromptText,
|
|
@@ -50164,7 +50787,7 @@ function safeDaemonLogPaths() {
|
|
|
50164
50787
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
50165
50788
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
50166
50789
|
return {
|
|
50167
|
-
appLog: join20(
|
|
50790
|
+
appLog: join20(dirname14(configPath), "runtime", "app.log"),
|
|
50168
50791
|
stderrLog: paths.stderrLog
|
|
50169
50792
|
};
|
|
50170
50793
|
} catch {
|