@ganglion/xacpx 0.9.3 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/bridge/bridge-main.js +170 -78
- package/dist/channels/types.d.ts +11 -0
- package/dist/channels/weixin-channel.d.ts +8 -0
- package/dist/cli.js +2044 -688
- 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 -2
- package/dist/perf/perf-log-writer.d.ts +1 -0
- package/dist/plugin-api.js +24 -6
- 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
|
};
|
|
@@ -915,6 +921,8 @@ var init_plugin_cli = __esm(() => {
|
|
|
915
921
|
noPlugins: "No plugins installed yet.",
|
|
916
922
|
pluginListHeader: "Plugins:",
|
|
917
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.`,
|
|
918
926
|
pluginInstallFailed: (packageSpec, error) => `Plugin ${packageSpec} install failed: ${error}`,
|
|
919
927
|
pluginValidateFailed: (recordedName, error) => `Plugin ${recordedName} validation failed: ${error}`,
|
|
920
928
|
pluginInstalled: (recordedName) => `Plugin ${recordedName} installed`,
|
|
@@ -1026,8 +1034,6 @@ var init_weixin = __esm(() => {
|
|
|
1026
1034
|
debugEnabled: "Debug mode enabled",
|
|
1027
1035
|
debugDisabled: "Debug mode disabled",
|
|
1028
1036
|
sessionCleared: "✅ Session cleared. Starting a fresh conversation.",
|
|
1029
|
-
noAccountsLoggedIn: "No accounts are currently logged in.",
|
|
1030
|
-
logoutSuccess: "✅ Logged out. All account credentials cleared.",
|
|
1031
1037
|
commandFailed: (detail) => `❌ Command failed: ${detail}`
|
|
1032
1038
|
};
|
|
1033
1039
|
});
|
|
@@ -1065,6 +1071,7 @@ var init_misc = __esm(() => {
|
|
|
1065
1071
|
quotaOverflowSummary: (count) => `(${count} progress updates omitted due to message limit; see final result below)`,
|
|
1066
1072
|
finalHeadsUp: (total, sentSoFar, remaining) => `—
|
|
1067
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.`,
|
|
1068
1075
|
quotedMessagePrefix: (parts) => `[Quote: ${parts}]`,
|
|
1069
1076
|
scheduledTaskFailed: (message) => `Scheduled task failed: ${message}`,
|
|
1070
1077
|
orchestrationTaskCompleted: (taskId, workerSession, result) => `Delegation task "${taskId}" completed
|
|
@@ -1093,6 +1100,8 @@ var init_misc = __esm(() => {
|
|
|
1093
1100
|
delegateQPackageInstr3: "Do not forward the human's exact words to the worker",
|
|
1094
1101
|
commandAccessDeniedSuffix: " is restricted to group owner only.",
|
|
1095
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.",
|
|
1096
1105
|
commandLabelThisMessage: "This message",
|
|
1097
1106
|
sessionResetNoCurrentSession: "No session is currently selected. Run /session new ... or /use <alias> first.",
|
|
1098
1107
|
sessionResetFailed: (alias) => `Session "${alias}" reset failed. The new backend session was not created, please try again later.`,
|
|
@@ -1163,6 +1172,11 @@ var init_session2 = __esm(() => {
|
|
|
1163
1172
|
currentLabel: "[当前]",
|
|
1164
1173
|
sessionListItem: (alias, agent2, workspace2) => `- ${alias} (${agent2} @ ${workspace2})`,
|
|
1165
1174
|
sessionCreated: (alias) => `会话「${alias}」已创建并切换`,
|
|
1175
|
+
sessionAlreadyExists: (alias, agent2, workspace2) => [
|
|
1176
|
+
`会话「${alias}」已存在(${agent2} @ ${workspace2})。`,
|
|
1177
|
+
`发送 /use ${alias} 切换到它,或先执行 /session rm ${alias} 删除后再创建。`
|
|
1178
|
+
].join(`
|
|
1179
|
+
`),
|
|
1166
1180
|
sessionAttachNotFound: (alias, agent2, workspace2) => [
|
|
1167
1181
|
"没有找到可绑定的已有会话。",
|
|
1168
1182
|
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent2} --ws ${workspace2} --name <会话名>`
|
|
@@ -1199,6 +1213,7 @@ var init_session2 = __esm(() => {
|
|
|
1199
1213
|
sessionBlockedByTasksHint: "使用 /tasks 查看任务列表,或 /task cancel <id> 取消任务。",
|
|
1200
1214
|
sessionRemoved: (alias) => `已删除会话「${alias}」。`,
|
|
1201
1215
|
sessionRemovedWasActive: "该会话是当前活跃会话,已自动清除相关聊天上下文。",
|
|
1216
|
+
sessionRemovedWasActivePromoted: (alias) => `该会话是当前活跃会话,已切换回上一个会话「${alias}」。`,
|
|
1202
1217
|
sessionTransportShared: (transportSession, count) => `提示:后端会话「${transportSession}」仍被其他 ${count} 个会话引用,未关闭。`,
|
|
1203
1218
|
sessionOrchestrationPurgeFailed: (warning) => `提示:清理任务编排引用失败(${warning}),请稍后执行 /tasks clean 手动清理。`,
|
|
1204
1219
|
sessionTransportTeardownFailed: (warning) => `提示:后端会话未能自动关闭(${warning}),如有残留请手动执行 acpx sessions close。`,
|
|
@@ -1492,7 +1507,7 @@ var init_later2 = __esm(() => {
|
|
|
1492
1507
|
helpNote2: "时间必须在 10 秒之后、7 天之内",
|
|
1493
1508
|
helpNote3: "默认在为本次任务新建的临时会话里执行,跑完即销毁",
|
|
1494
1509
|
helpNote4: "加 --bind 改为发送到创建时绑定的当前会话(默认模式可用 later.defaultMode 配置)",
|
|
1495
|
-
helpNote5: "/lt list
|
|
1510
|
+
helpNote5: "/lt list 只显示本聊天创建的待执行任务;群聊中只有群主可取消",
|
|
1496
1511
|
helpNote6: "不支持延迟执行 / 开头的 xacpx 命令",
|
|
1497
1512
|
helpNote7: "完整时间格式与说明见 docs/later-command.md"
|
|
1498
1513
|
};
|
|
@@ -1985,6 +2000,8 @@ var init_plugin_cli2 = __esm(() => {
|
|
|
1985
2000
|
noPlugins: "还没有安装插件。",
|
|
1986
2001
|
pluginListHeader: "插件:",
|
|
1987
2002
|
unrecognizedArgs: (args) => `未识别的参数:${args}`,
|
|
2003
|
+
pluginSpecHasDoubleQuote: (spec) => `非法插件 spec ${spec}:npm 包 spec 不允许包含双引号 (")。`,
|
|
2004
|
+
pluginSpecHasPercentOnWindows: (spec) => `非法插件 spec ${spec}:Windows 上 cmd.exe 会展开 %,无法安全传递。请改用 npm 直接安装该包。`,
|
|
1988
2005
|
pluginInstallFailed: (packageSpec, error) => `插件 ${packageSpec} 安装失败:${error}`,
|
|
1989
2006
|
pluginValidateFailed: (recordedName, error) => `插件 ${recordedName} 校验失败:${error}`,
|
|
1990
2007
|
pluginInstalled: (recordedName) => `插件 ${recordedName} 已安装`,
|
|
@@ -2096,8 +2113,6 @@ var init_weixin2 = __esm(() => {
|
|
|
2096
2113
|
debugEnabled: "Debug 模式已开启",
|
|
2097
2114
|
debugDisabled: "Debug 模式已关闭",
|
|
2098
2115
|
sessionCleared: "✅ 会话已清除,重新开始对话",
|
|
2099
|
-
noAccountsLoggedIn: "当前没有已登录的账号",
|
|
2100
|
-
logoutSuccess: "✅ 已退出登录,清除所有账号凭证",
|
|
2101
2116
|
commandFailed: (detail) => `❌ 指令执行失败: ${detail}`
|
|
2102
2117
|
};
|
|
2103
2118
|
});
|
|
@@ -2135,6 +2150,7 @@ var init_misc2 = __esm(() => {
|
|
|
2135
2150
|
quotaOverflowSummary: (count) => `(因消息次数限制省略 ${count} 条进度,请继续查看下方最终结果)`,
|
|
2136
2151
|
finalHeadsUp: (total, sentSoFar, remaining) => `—
|
|
2137
2152
|
\uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`,
|
|
2153
|
+
finalAllParked: (count) => `\uD83D\uDCC4 已达消息上限:结果共 ${count} 段已暂存。回复 /jx 接收。`,
|
|
2138
2154
|
quotedMessagePrefix: (parts) => `[引用: ${parts}]`,
|
|
2139
2155
|
scheduledTaskFailed: (message) => `定时任务执行失败:${message}`,
|
|
2140
2156
|
orchestrationTaskCompleted: (taskId, workerSession, result) => `委派任务「${taskId}」已完成
|
|
@@ -2163,6 +2179,8 @@ var init_misc2 = __esm(() => {
|
|
|
2163
2179
|
delegateQPackageInstr3: "不要直接把 human 原话转发给 worker",
|
|
2164
2180
|
commandAccessDeniedSuffix: " 仅限群创建者/频道 owner 使用。",
|
|
2165
2181
|
commandAccessDeniedHint: "如果需要执行控制类操作,请由 owner 在群内发送,或改用私聊。",
|
|
2182
|
+
commandAccessDeniedChatTypeMissingSuffix: " 已被拦截:该频道未上报会话类型(直聊/群聊),控制类命令在此暂不可用。",
|
|
2183
|
+
commandAccessDeniedChatTypeMissingHint: "只读命令与普通对话不受影响。这是频道元数据问题,请升级或反馈该频道插件。",
|
|
2166
2184
|
commandLabelThisMessage: "该消息",
|
|
2167
2185
|
sessionResetNoCurrentSession: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。",
|
|
2168
2186
|
sessionResetFailed: (alias) => `会话「${alias}」重置失败。
|
|
@@ -4212,7 +4230,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
4212
4230
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
4213
4231
|
import { chmodSync, mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
|
|
4214
4232
|
import { dirname } from "node:path";
|
|
4215
|
-
async function
|
|
4233
|
+
async function withPrivateFileLock(path, fn) {
|
|
4216
4234
|
await mkdir(dirname(path), { recursive: true });
|
|
4217
4235
|
const release = await lockfile.lock(path, {
|
|
4218
4236
|
realpath: false,
|
|
@@ -4226,23 +4244,31 @@ async function writePrivateFileAtomic(path, content) {
|
|
|
4226
4244
|
}
|
|
4227
4245
|
});
|
|
4228
4246
|
try {
|
|
4229
|
-
|
|
4230
|
-
await retryTransientWriteErrors(async () => import_write_file_atomic.default(path, content, {
|
|
4231
|
-
mode: PRIVATE_FILE_MODE,
|
|
4232
|
-
encoding: "utf8",
|
|
4233
|
-
fsync: true
|
|
4234
|
-
}));
|
|
4235
|
-
} catch (error) {
|
|
4236
|
-
if (!isTransientWriteError(error, process.platform)) {
|
|
4237
|
-
throw error;
|
|
4238
|
-
}
|
|
4239
|
-
await writeFile(path, content, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
4240
|
-
await chmod(path, PRIVATE_FILE_MODE).catch(() => {});
|
|
4241
|
-
}
|
|
4247
|
+
return await fn((content) => writePrivateFileAtomicUnlocked(path, content));
|
|
4242
4248
|
} finally {
|
|
4243
4249
|
await release();
|
|
4244
4250
|
}
|
|
4245
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
|
+
}
|
|
4246
4272
|
function writePrivateFileSync(path, content, deps = {}) {
|
|
4247
4273
|
mkdirSync2(dirname(path), { recursive: true });
|
|
4248
4274
|
const platform = deps.platform ?? process.platform;
|
|
@@ -4399,6 +4425,14 @@ function isRecord(value) {
|
|
|
4399
4425
|
function isReplyMode(value) {
|
|
4400
4426
|
return value === "stream" || value === "final" || value === "verbose";
|
|
4401
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
|
+
}
|
|
4402
4436
|
function parseChannelConfig(channel, legacyWechat) {
|
|
4403
4437
|
if (channel !== undefined) {
|
|
4404
4438
|
if (!isRecord(channel)) {
|
|
@@ -4411,6 +4445,7 @@ function parseChannelConfig(channel, legacyWechat) {
|
|
|
4411
4445
|
throw new Error("channel.replyMode must be stream, final, or verbose");
|
|
4412
4446
|
}
|
|
4413
4447
|
const type = typeof channel.type === "string" ? channel.type : "weixin";
|
|
4448
|
+
const ownerIds = parseOwnerIds(channel.ownerIds, "channel.ownerIds");
|
|
4414
4449
|
let options = undefined;
|
|
4415
4450
|
if ("feishu" in channel && isRecord(channel.feishu)) {
|
|
4416
4451
|
options = channel.feishu;
|
|
@@ -4420,6 +4455,7 @@ function parseChannelConfig(channel, legacyWechat) {
|
|
|
4420
4455
|
return {
|
|
4421
4456
|
type,
|
|
4422
4457
|
replyMode: isReplyMode(channel.replyMode) ? channel.replyMode : DEFAULT_CHANNEL_CONFIG.replyMode,
|
|
4458
|
+
...ownerIds ? { ownerIds } : {},
|
|
4423
4459
|
...options ? { options } : {}
|
|
4424
4460
|
};
|
|
4425
4461
|
}
|
|
@@ -4658,6 +4694,7 @@ function parseRuntimeChannelConfig(raw, index) {
|
|
|
4658
4694
|
if ("replyMode" in raw && !isReplyMode(raw.replyMode)) {
|
|
4659
4695
|
throw new Error(`channels[${index}].replyMode must be stream, final, or verbose`);
|
|
4660
4696
|
}
|
|
4697
|
+
const ownerIds = parseOwnerIds(raw.ownerIds, `channels[${index}].ownerIds`);
|
|
4661
4698
|
let options = undefined;
|
|
4662
4699
|
if ("feishu" in raw && isRecord(raw.feishu)) {
|
|
4663
4700
|
options = raw.feishu;
|
|
@@ -4669,6 +4706,7 @@ function parseRuntimeChannelConfig(raw, index) {
|
|
|
4669
4706
|
type: raw.type,
|
|
4670
4707
|
enabled,
|
|
4671
4708
|
...isReplyMode(raw.replyMode) ? { replyMode: raw.replyMode } : {},
|
|
4709
|
+
...ownerIds ? { ownerIds } : {},
|
|
4672
4710
|
...options ? { options } : {}
|
|
4673
4711
|
};
|
|
4674
4712
|
}
|
|
@@ -4753,6 +4791,8 @@ var init_load_config = __esm(() => {
|
|
|
4753
4791
|
});
|
|
4754
4792
|
|
|
4755
4793
|
// src/config/config-store.ts
|
|
4794
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
4795
|
+
|
|
4756
4796
|
class ConfigStore {
|
|
4757
4797
|
path;
|
|
4758
4798
|
constructor(path2) {
|
|
@@ -4761,60 +4801,250 @@ class ConfigStore {
|
|
|
4761
4801
|
async load() {
|
|
4762
4802
|
return await loadConfig(this.path);
|
|
4763
4803
|
}
|
|
4764
|
-
async
|
|
4765
|
-
await
|
|
4766
|
-
|
|
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
|
+
});
|
|
4767
4816
|
}
|
|
4768
4817
|
async upsertWorkspace(name, cwd, description) {
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
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
|
+
});
|
|
4777
4826
|
}
|
|
4778
4827
|
async removeWorkspace(name) {
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4828
|
+
assertSafeConfigKey(name);
|
|
4829
|
+
return await this.patchRaw((raw) => {
|
|
4830
|
+
deleteRecordEntry(raw, "workspaces", name);
|
|
4831
|
+
});
|
|
4783
4832
|
}
|
|
4784
4833
|
async upsertAgent(name, agent3) {
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
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
|
+
});
|
|
4789
4842
|
}
|
|
4790
4843
|
async removeAgent(name) {
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
}
|
|
4796
|
-
async updateTransport(
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
await this.
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
async updateChannel(channel) {
|
|
4806
|
-
const config3 = await this.load();
|
|
4807
|
-
config3.channel = {
|
|
4808
|
-
...config3.channel,
|
|
4809
|
-
...channel
|
|
4810
|
-
};
|
|
4811
|
-
await this.save(config3);
|
|
4812
|
-
return config3;
|
|
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
|
+
});
|
|
4813
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(".");
|
|
4814
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];
|
|
5023
|
+
}
|
|
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;
|
|
4815
5044
|
var init_config_store = __esm(() => {
|
|
4816
5045
|
init_private_file();
|
|
4817
5046
|
init_load_config();
|
|
5047
|
+
PROTOTYPE_POLLUTING_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
4818
5048
|
});
|
|
4819
5049
|
|
|
4820
5050
|
// src/config/default-workspace.ts
|
|
@@ -4827,16 +5057,16 @@ var init_default_workspace = __esm(() => {
|
|
|
4827
5057
|
});
|
|
4828
5058
|
|
|
4829
5059
|
// src/config/ensure-config.ts
|
|
4830
|
-
import { readFile as
|
|
5060
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
4831
5061
|
async function ensureConfigExists(path2, options = {}) {
|
|
4832
5062
|
try {
|
|
4833
5063
|
await loadConfig(path2);
|
|
4834
5064
|
} catch (error) {
|
|
4835
|
-
if (!
|
|
5065
|
+
if (!isMissingFileError2(error)) {
|
|
4836
5066
|
throw error;
|
|
4837
5067
|
}
|
|
4838
|
-
const
|
|
4839
|
-
await
|
|
5068
|
+
const seed = await loadDefaultConfigTemplate(options);
|
|
5069
|
+
await writePrivateFileAtomic(path2, serializeRawConfig(seed));
|
|
4840
5070
|
}
|
|
4841
5071
|
}
|
|
4842
5072
|
async function loadDefaultConfigTemplate(options = {}) {
|
|
@@ -4844,7 +5074,7 @@ async function loadDefaultConfigTemplate(options = {}) {
|
|
|
4844
5074
|
try {
|
|
4845
5075
|
return normalizeDefaultConfigTemplate(await options.readDefaultConfigTemplate());
|
|
4846
5076
|
} catch (error) {
|
|
4847
|
-
if (!
|
|
5077
|
+
if (!isMissingFileError2(error)) {
|
|
4848
5078
|
throw error;
|
|
4849
5079
|
}
|
|
4850
5080
|
}
|
|
@@ -4856,10 +5086,10 @@ async function loadDefaultConfigTemplate(options = {}) {
|
|
|
4856
5086
|
let raw;
|
|
4857
5087
|
for (const candidate of candidates) {
|
|
4858
5088
|
try {
|
|
4859
|
-
raw = await
|
|
5089
|
+
raw = await readFile3(candidate, "utf8");
|
|
4860
5090
|
break;
|
|
4861
5091
|
} catch (error) {
|
|
4862
|
-
if (!
|
|
5092
|
+
if (!isMissingFileError2(error)) {
|
|
4863
5093
|
throw error;
|
|
4864
5094
|
}
|
|
4865
5095
|
}
|
|
@@ -4872,7 +5102,14 @@ async function loadDefaultConfigTemplate(options = {}) {
|
|
|
4872
5102
|
function normalizeDefaultConfigTemplate(raw) {
|
|
4873
5103
|
const template = parseConfig(raw);
|
|
4874
5104
|
return {
|
|
4875
|
-
|
|
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
|
+
},
|
|
4876
5113
|
agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent3]) => [
|
|
4877
5114
|
name,
|
|
4878
5115
|
{
|
|
@@ -4885,32 +5122,18 @@ function normalizeDefaultConfigTemplate(raw) {
|
|
|
4885
5122
|
}
|
|
4886
5123
|
};
|
|
4887
5124
|
}
|
|
4888
|
-
function
|
|
5125
|
+
function isMissingFileError2(error) {
|
|
4889
5126
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4890
5127
|
}
|
|
4891
5128
|
var BUILTIN_DEFAULT_CONFIG_TEMPLATE;
|
|
4892
5129
|
var init_ensure_config = __esm(() => {
|
|
5130
|
+
init_private_file();
|
|
4893
5131
|
init_config_store();
|
|
4894
5132
|
init_default_workspace();
|
|
4895
5133
|
init_load_config();
|
|
4896
5134
|
BUILTIN_DEFAULT_CONFIG_TEMPLATE = {
|
|
4897
5135
|
transport: {
|
|
4898
|
-
type: "acpx-bridge"
|
|
4899
|
-
sessionInitTimeoutMs: 120000,
|
|
4900
|
-
permissionMode: "approve-all",
|
|
4901
|
-
nonInteractivePermissions: "deny"
|
|
4902
|
-
},
|
|
4903
|
-
logging: {
|
|
4904
|
-
level: "info",
|
|
4905
|
-
maxSizeBytes: 2 * 1024 * 1024,
|
|
4906
|
-
maxFiles: 5,
|
|
4907
|
-
retentionDays: 7,
|
|
4908
|
-
perf: {
|
|
4909
|
-
enabled: false,
|
|
4910
|
-
maxSizeBytes: 5242880,
|
|
4911
|
-
maxFiles: 3,
|
|
4912
|
-
retentionDays: 7
|
|
4913
|
-
}
|
|
5136
|
+
type: "acpx-bridge"
|
|
4914
5137
|
},
|
|
4915
5138
|
channel: {
|
|
4916
5139
|
type: "weixin",
|
|
@@ -4922,8 +5145,7 @@ var init_ensure_config = __esm(() => {
|
|
|
4922
5145
|
},
|
|
4923
5146
|
workspaces: {
|
|
4924
5147
|
[DEFAULT_HOME_WORKSPACE_NAME]: { ...DEFAULT_HOME_WORKSPACE }
|
|
4925
|
-
}
|
|
4926
|
-
plugins: []
|
|
5148
|
+
}
|
|
4927
5149
|
};
|
|
4928
5150
|
});
|
|
4929
5151
|
|
|
@@ -5004,7 +5226,7 @@ var init_agent_templates = __esm(() => {
|
|
|
5004
5226
|
});
|
|
5005
5227
|
|
|
5006
5228
|
// src/daemon/daemon-status.ts
|
|
5007
|
-
import { mkdir as mkdir2, readFile as
|
|
5229
|
+
import { mkdir as mkdir2, readFile as readFile4, rm } from "node:fs/promises";
|
|
5008
5230
|
import { dirname as dirname2 } from "node:path";
|
|
5009
5231
|
|
|
5010
5232
|
class DaemonStatusStore {
|
|
@@ -5014,7 +5236,7 @@ class DaemonStatusStore {
|
|
|
5014
5236
|
}
|
|
5015
5237
|
async load() {
|
|
5016
5238
|
try {
|
|
5017
|
-
const content = await
|
|
5239
|
+
const content = await readFile4(this.path, "utf8");
|
|
5018
5240
|
if (content.trim() === "") {
|
|
5019
5241
|
return null;
|
|
5020
5242
|
}
|
|
@@ -5030,18 +5252,37 @@ class DaemonStatusStore {
|
|
|
5030
5252
|
}
|
|
5031
5253
|
}
|
|
5032
5254
|
async save(status) {
|
|
5033
|
-
await mkdir2(dirname2(this.path), { recursive: true });
|
|
5034
|
-
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" });
|
|
5035
5257
|
}
|
|
5036
5258
|
async clear() {
|
|
5037
5259
|
await rm(this.path, { force: true });
|
|
5038
5260
|
}
|
|
5039
5261
|
}
|
|
5040
|
-
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 = () => {};
|
|
5041
5283
|
|
|
5042
5284
|
// src/daemon/daemon-controller.ts
|
|
5043
|
-
import {
|
|
5044
|
-
import { dirname as dirname3 } from "node:path";
|
|
5285
|
+
import { open, readFile as readFile5, rm as rm2 } from "node:fs/promises";
|
|
5045
5286
|
|
|
5046
5287
|
class DaemonController {
|
|
5047
5288
|
paths;
|
|
@@ -5060,7 +5301,7 @@ class DaemonController {
|
|
|
5060
5301
|
this.deps = deps;
|
|
5061
5302
|
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
5062
5303
|
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
5063
|
-
this.startupTimeoutMs = deps.startupTimeoutMs ??
|
|
5304
|
+
this.startupTimeoutMs = deps.startupTimeoutMs ?? 1e4;
|
|
5064
5305
|
this.onboardingStartupTimeoutMs = deps.onboardingStartupTimeoutMs ?? 300000;
|
|
5065
5306
|
this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
|
|
5066
5307
|
this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
|
|
@@ -5129,7 +5370,7 @@ class DaemonController {
|
|
|
5129
5370
|
}
|
|
5130
5371
|
async loadPid() {
|
|
5131
5372
|
try {
|
|
5132
|
-
const content = await
|
|
5373
|
+
const content = await readFile5(this.paths.pidFile, "utf8");
|
|
5133
5374
|
const pid = Number(content.trim());
|
|
5134
5375
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
5135
5376
|
} catch (error) {
|
|
@@ -5140,7 +5381,7 @@ class DaemonController {
|
|
|
5140
5381
|
}
|
|
5141
5382
|
}
|
|
5142
5383
|
async openPidFileExclusive() {
|
|
5143
|
-
await
|
|
5384
|
+
await ensurePrivateRuntimeDir(this.paths.runtimeDir);
|
|
5144
5385
|
try {
|
|
5145
5386
|
return await open(this.paths.pidFile, "wx", 384);
|
|
5146
5387
|
} catch (error) {
|
|
@@ -5194,6 +5435,7 @@ class DaemonController {
|
|
|
5194
5435
|
}
|
|
5195
5436
|
var init_daemon_controller = __esm(() => {
|
|
5196
5437
|
init_daemon_status();
|
|
5438
|
+
init_private_runtime_dir();
|
|
5197
5439
|
});
|
|
5198
5440
|
|
|
5199
5441
|
// src/process/terminate-process-tree.ts
|
|
@@ -5245,13 +5487,15 @@ async function defaultRunProcessCommand(command, args) {
|
|
|
5245
5487
|
var init_terminate_process_tree = () => {};
|
|
5246
5488
|
|
|
5247
5489
|
// src/daemon/create-daemon-controller.ts
|
|
5248
|
-
import {
|
|
5490
|
+
import { open as open2 } from "node:fs/promises";
|
|
5249
5491
|
import { spawn as spawn2 } from "node:child_process";
|
|
5250
5492
|
function createDaemonController(paths, options) {
|
|
5251
5493
|
return new DaemonController(paths, {
|
|
5252
5494
|
isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning2,
|
|
5253
5495
|
spawnDetached: async (spawnOptions) => {
|
|
5254
|
-
await
|
|
5496
|
+
await ensurePrivateRuntimeDir(paths.runtimeDir, {
|
|
5497
|
+
...options.platform ? { platform: options.platform } : {}
|
|
5498
|
+
});
|
|
5255
5499
|
const stdoutHandle = await open2(paths.stdoutLog, "a", 384);
|
|
5256
5500
|
const stderrHandle = await open2(paths.stderrLog, "a", 384);
|
|
5257
5501
|
await stdoutHandle.chmod(384).catch(() => {});
|
|
@@ -5323,8 +5567,10 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
5323
5567
|
function buildWindowsLauncherScript() {
|
|
5324
5568
|
const script = [
|
|
5325
5569
|
"$env:XACPX_DAEMON_RUN = '1'",
|
|
5570
|
+
`$arg0 = '"' + $env:XACPX_DAEMON_ARG0 + '"'`,
|
|
5571
|
+
`$arg1 = '"' + $env:XACPX_DAEMON_ARG1 + '"'`,
|
|
5326
5572
|
"$process = Start-Process -FilePath $env:XACPX_DAEMON_COMMAND `",
|
|
5327
|
-
" -ArgumentList @($
|
|
5573
|
+
" -ArgumentList @($arg0, $arg1) `",
|
|
5328
5574
|
" -WorkingDirectory $env:XACPX_DAEMON_CWD `",
|
|
5329
5575
|
" -RedirectStandardOutput $env:XACPX_DAEMON_STDOUT `",
|
|
5330
5576
|
" -RedirectStandardError $env:XACPX_DAEMON_STDERR `",
|
|
@@ -5395,6 +5641,7 @@ async function defaultTerminateProcess(pid) {
|
|
|
5395
5641
|
}
|
|
5396
5642
|
var init_create_daemon_controller = __esm(() => {
|
|
5397
5643
|
init_daemon_controller();
|
|
5644
|
+
init_private_runtime_dir();
|
|
5398
5645
|
init_terminate_process_tree();
|
|
5399
5646
|
});
|
|
5400
5647
|
|
|
@@ -5431,7 +5678,7 @@ function encodeOrchestrationRpcResponse(response) {
|
|
|
5431
5678
|
var init_orchestration_ipc = () => {};
|
|
5432
5679
|
|
|
5433
5680
|
// src/daemon/daemon-files.ts
|
|
5434
|
-
import { dirname as
|
|
5681
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
5435
5682
|
function resolveDaemonPaths(options) {
|
|
5436
5683
|
const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join4(coreHomeDir(options.home), "runtime"));
|
|
5437
5684
|
return {
|
|
@@ -5444,11 +5691,19 @@ function resolveDaemonPaths(options) {
|
|
|
5444
5691
|
};
|
|
5445
5692
|
}
|
|
5446
5693
|
function resolveRuntimeDirFromConfigPath(configPath) {
|
|
5447
|
-
return join4(
|
|
5694
|
+
return join4(dirname3(configPath), "runtime");
|
|
5448
5695
|
}
|
|
5449
5696
|
function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
|
|
5450
5697
|
return resolveOrchestrationEndpoint(runtimeDir, platform).path;
|
|
5451
5698
|
}
|
|
5699
|
+
function isProcessAlive(pid) {
|
|
5700
|
+
try {
|
|
5701
|
+
process.kill(pid, 0);
|
|
5702
|
+
return true;
|
|
5703
|
+
} catch (error) {
|
|
5704
|
+
return error.code === "EPERM";
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5452
5707
|
var init_daemon_files = __esm(() => {
|
|
5453
5708
|
init_core_home();
|
|
5454
5709
|
init_orchestration_ipc();
|
|
@@ -12124,10 +12379,24 @@ function createEmptyState() {
|
|
|
12124
12379
|
var init_types = () => {};
|
|
12125
12380
|
|
|
12126
12381
|
// src/state/state-store.ts
|
|
12127
|
-
import { readFile as
|
|
12382
|
+
import { readFile as readFile6, rename, writeFile as writeFile3 } from "node:fs/promises";
|
|
12128
12383
|
function isRecord2(value) {
|
|
12129
12384
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12130
12385
|
}
|
|
12386
|
+
function sectionRecord(value, section, dropped) {
|
|
12387
|
+
if (value === undefined) {
|
|
12388
|
+
return {};
|
|
12389
|
+
}
|
|
12390
|
+
if (!isRecord2(value)) {
|
|
12391
|
+
dropped.push({
|
|
12392
|
+
section,
|
|
12393
|
+
key: "",
|
|
12394
|
+
reason: `field "${section}" is not an object; reset to empty`
|
|
12395
|
+
});
|
|
12396
|
+
return {};
|
|
12397
|
+
}
|
|
12398
|
+
return value;
|
|
12399
|
+
}
|
|
12131
12400
|
function isString(value) {
|
|
12132
12401
|
return typeof value === "string";
|
|
12133
12402
|
}
|
|
@@ -12232,73 +12501,82 @@ function isHumanQuestionPackageRecord(value) {
|
|
|
12232
12501
|
const messages = value.messages;
|
|
12233
12502
|
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);
|
|
12234
12503
|
}
|
|
12235
|
-
function parseOrchestrationState(raw,
|
|
12504
|
+
function parseOrchestrationState(raw, dropped) {
|
|
12236
12505
|
if (raw === undefined) {
|
|
12237
12506
|
return createEmptyOrchestrationState();
|
|
12238
12507
|
}
|
|
12239
12508
|
if (!isRecord2(raw)) {
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
const workerBindings = raw.workerBindings;
|
|
12247
|
-
if (workerBindings !== undefined && !isRecord2(workerBindings)) {
|
|
12248
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.workerBindings"`);
|
|
12249
|
-
}
|
|
12250
|
-
const groups = raw.groups;
|
|
12251
|
-
if (groups !== undefined && !isRecord2(groups)) {
|
|
12252
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.groups"`);
|
|
12253
|
-
}
|
|
12254
|
-
const humanQuestionPackages = raw.humanQuestionPackages;
|
|
12255
|
-
if (humanQuestionPackages !== undefined && !isRecord2(humanQuestionPackages)) {
|
|
12256
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.humanQuestionPackages"`);
|
|
12257
|
-
}
|
|
12258
|
-
const coordinatorQuestionState = raw.coordinatorQuestionState;
|
|
12259
|
-
if (coordinatorQuestionState !== undefined && !isRecord2(coordinatorQuestionState)) {
|
|
12260
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.coordinatorQuestionState"`);
|
|
12261
|
-
}
|
|
12262
|
-
const coordinatorRoutes = raw.coordinatorRoutes;
|
|
12263
|
-
if (coordinatorRoutes !== undefined && !isRecord2(coordinatorRoutes)) {
|
|
12264
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.coordinatorRoutes"`);
|
|
12265
|
-
}
|
|
12266
|
-
const externalCoordinators = raw.externalCoordinators;
|
|
12267
|
-
if (externalCoordinators !== undefined && !isRecord2(externalCoordinators)) {
|
|
12268
|
-
throw new Error(`state file "${path3}" must contain an object field "orchestration.externalCoordinators"`);
|
|
12509
|
+
dropped.push({
|
|
12510
|
+
section: "orchestration",
|
|
12511
|
+
key: "",
|
|
12512
|
+
reason: 'field "orchestration" is not an object; reset to empty'
|
|
12513
|
+
});
|
|
12514
|
+
return createEmptyOrchestrationState();
|
|
12269
12515
|
}
|
|
12516
|
+
const tasks = sectionRecord(raw.tasks, "orchestration.tasks", dropped);
|
|
12517
|
+
const workerBindings = sectionRecord(raw.workerBindings, "orchestration.workerBindings", dropped);
|
|
12518
|
+
const groups = sectionRecord(raw.groups, "orchestration.groups", dropped);
|
|
12519
|
+
const humanQuestionPackages = sectionRecord(raw.humanQuestionPackages, "orchestration.humanQuestionPackages", dropped);
|
|
12520
|
+
const coordinatorQuestionState = sectionRecord(raw.coordinatorQuestionState, "orchestration.coordinatorQuestionState", dropped);
|
|
12521
|
+
const coordinatorRoutes = sectionRecord(raw.coordinatorRoutes, "orchestration.coordinatorRoutes", dropped);
|
|
12522
|
+
const externalCoordinators = sectionRecord(raw.externalCoordinators, "orchestration.externalCoordinators", dropped);
|
|
12270
12523
|
const parsedTasks = {};
|
|
12271
|
-
for (const [taskId, task] of Object.entries(tasks
|
|
12524
|
+
for (const [taskId, task] of Object.entries(tasks)) {
|
|
12272
12525
|
if (!isTaskRecord(task)) {
|
|
12273
|
-
|
|
12526
|
+
dropped.push({
|
|
12527
|
+
section: "orchestration.tasks",
|
|
12528
|
+
key: taskId,
|
|
12529
|
+
reason: "malformed orchestration task record"
|
|
12530
|
+
});
|
|
12531
|
+
continue;
|
|
12274
12532
|
}
|
|
12275
12533
|
parsedTasks[taskId] = task;
|
|
12276
12534
|
}
|
|
12277
12535
|
const parsedWorkerBindings = {};
|
|
12278
|
-
for (const [workerSession, binding] of Object.entries(workerBindings
|
|
12536
|
+
for (const [workerSession, binding] of Object.entries(workerBindings)) {
|
|
12279
12537
|
if (!isWorkerBindingRecord(binding)) {
|
|
12280
|
-
|
|
12538
|
+
dropped.push({
|
|
12539
|
+
section: "orchestration.workerBindings",
|
|
12540
|
+
key: workerSession,
|
|
12541
|
+
reason: "malformed orchestration worker binding record"
|
|
12542
|
+
});
|
|
12543
|
+
continue;
|
|
12281
12544
|
}
|
|
12282
12545
|
parsedWorkerBindings[workerSession] = binding;
|
|
12283
12546
|
}
|
|
12284
12547
|
const parsedGroups = {};
|
|
12285
|
-
for (const [groupId, group] of Object.entries(groups
|
|
12548
|
+
for (const [groupId, group] of Object.entries(groups)) {
|
|
12286
12549
|
if (!isGroupRecord(group)) {
|
|
12287
|
-
|
|
12550
|
+
dropped.push({
|
|
12551
|
+
section: "orchestration.groups",
|
|
12552
|
+
key: groupId,
|
|
12553
|
+
reason: "malformed orchestration group record"
|
|
12554
|
+
});
|
|
12555
|
+
continue;
|
|
12288
12556
|
}
|
|
12289
12557
|
parsedGroups[groupId] = group;
|
|
12290
12558
|
}
|
|
12291
12559
|
const parsedHumanQuestionPackages = {};
|
|
12292
|
-
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages
|
|
12560
|
+
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages)) {
|
|
12293
12561
|
if (!isHumanQuestionPackageRecord(packageRecord)) {
|
|
12294
|
-
|
|
12562
|
+
dropped.push({
|
|
12563
|
+
section: "orchestration.humanQuestionPackages",
|
|
12564
|
+
key: packageId,
|
|
12565
|
+
reason: "malformed human question package record"
|
|
12566
|
+
});
|
|
12567
|
+
continue;
|
|
12295
12568
|
}
|
|
12296
12569
|
parsedHumanQuestionPackages[packageId] = packageRecord;
|
|
12297
12570
|
}
|
|
12298
12571
|
const parsedCoordinatorQuestionState = {};
|
|
12299
|
-
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState
|
|
12572
|
+
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState)) {
|
|
12300
12573
|
if (!isCoordinatorQuestionStateRecord(questionState)) {
|
|
12301
|
-
|
|
12574
|
+
dropped.push({
|
|
12575
|
+
section: "orchestration.coordinatorQuestionState",
|
|
12576
|
+
key: coordinatorSession,
|
|
12577
|
+
reason: "malformed coordinator question state record"
|
|
12578
|
+
});
|
|
12579
|
+
continue;
|
|
12302
12580
|
}
|
|
12303
12581
|
parsedCoordinatorQuestionState[coordinatorSession] = {
|
|
12304
12582
|
activePackageId: questionState.activePackageId,
|
|
@@ -12306,19 +12584,34 @@ function parseOrchestrationState(raw, path3) {
|
|
|
12306
12584
|
};
|
|
12307
12585
|
}
|
|
12308
12586
|
const parsedCoordinatorRoutes = {};
|
|
12309
|
-
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes
|
|
12587
|
+
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes)) {
|
|
12310
12588
|
if (!isCoordinatorRouteContextRecord(route)) {
|
|
12311
|
-
|
|
12589
|
+
dropped.push({
|
|
12590
|
+
section: "orchestration.coordinatorRoutes",
|
|
12591
|
+
key: coordinatorSession,
|
|
12592
|
+
reason: "malformed coordinator route record"
|
|
12593
|
+
});
|
|
12594
|
+
continue;
|
|
12312
12595
|
}
|
|
12313
12596
|
parsedCoordinatorRoutes[coordinatorSession] = route;
|
|
12314
12597
|
}
|
|
12315
12598
|
const parsedExternalCoordinators = {};
|
|
12316
|
-
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators
|
|
12599
|
+
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators)) {
|
|
12317
12600
|
if (!isExternalCoordinatorRecord(externalCoordinator)) {
|
|
12318
|
-
|
|
12601
|
+
dropped.push({
|
|
12602
|
+
section: "orchestration.externalCoordinators",
|
|
12603
|
+
key: coordinatorSession,
|
|
12604
|
+
reason: "malformed external coordinator record"
|
|
12605
|
+
});
|
|
12606
|
+
continue;
|
|
12319
12607
|
}
|
|
12320
12608
|
if (externalCoordinator.coordinatorSession !== coordinatorSession) {
|
|
12321
|
-
|
|
12609
|
+
dropped.push({
|
|
12610
|
+
section: "orchestration.externalCoordinators",
|
|
12611
|
+
key: coordinatorSession,
|
|
12612
|
+
reason: `coordinatorSession "${externalCoordinator.coordinatorSession}" does not match map key`
|
|
12613
|
+
});
|
|
12614
|
+
continue;
|
|
12322
12615
|
}
|
|
12323
12616
|
parsedExternalCoordinators[coordinatorSession] = externalCoordinator;
|
|
12324
12617
|
}
|
|
@@ -12344,11 +12637,12 @@ function isSessionRecord(value) {
|
|
|
12344
12637
|
}
|
|
12345
12638
|
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);
|
|
12346
12639
|
}
|
|
12347
|
-
function parseSessions(raw,
|
|
12640
|
+
function parseSessions(raw, dropped) {
|
|
12348
12641
|
const sessions = {};
|
|
12349
12642
|
for (const [alias, value] of Object.entries(raw)) {
|
|
12350
12643
|
if (!isSessionRecord(value)) {
|
|
12351
|
-
|
|
12644
|
+
dropped.push({ section: "sessions", key: alias, reason: "malformed session record" });
|
|
12645
|
+
continue;
|
|
12352
12646
|
}
|
|
12353
12647
|
sessions[alias] = value;
|
|
12354
12648
|
}
|
|
@@ -12357,11 +12651,12 @@ function parseSessions(raw, path3) {
|
|
|
12357
12651
|
function isChatContextRecord(value) {
|
|
12358
12652
|
return isRecord2(value) && isString(value.current_session);
|
|
12359
12653
|
}
|
|
12360
|
-
function parseChatContexts(raw,
|
|
12654
|
+
function parseChatContexts(raw, dropped) {
|
|
12361
12655
|
const chatContexts = {};
|
|
12362
12656
|
for (const [chatKey, value] of Object.entries(raw)) {
|
|
12363
12657
|
if (!isChatContextRecord(value)) {
|
|
12364
|
-
|
|
12658
|
+
dropped.push({ section: "chat_contexts", key: chatKey, reason: "malformed chat context record" });
|
|
12659
|
+
continue;
|
|
12365
12660
|
}
|
|
12366
12661
|
chatContexts[chatKey] = value;
|
|
12367
12662
|
}
|
|
@@ -12402,56 +12697,58 @@ function isScheduledTaskRecord(value) {
|
|
|
12402
12697
|
return false;
|
|
12403
12698
|
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);
|
|
12404
12699
|
}
|
|
12405
|
-
function parseScheduledTasks(raw,
|
|
12406
|
-
|
|
12407
|
-
return {};
|
|
12408
|
-
if (!isRecord2(raw)) {
|
|
12409
|
-
throw new Error(`state file "${path3}" must contain an object field "scheduled_tasks"`);
|
|
12410
|
-
}
|
|
12700
|
+
function parseScheduledTasks(raw, dropped) {
|
|
12701
|
+
const source = sectionRecord(raw, "scheduled_tasks", dropped);
|
|
12411
12702
|
const tasks = {};
|
|
12412
|
-
for (const [id, value] of Object.entries(
|
|
12703
|
+
for (const [id, value] of Object.entries(source)) {
|
|
12413
12704
|
if (!isScheduledTaskRecord(value) || value.id !== id) {
|
|
12414
|
-
|
|
12705
|
+
dropped.push({ section: "scheduled_tasks", key: id, reason: "malformed scheduled task record" });
|
|
12706
|
+
continue;
|
|
12415
12707
|
}
|
|
12416
12708
|
tasks[id] = value;
|
|
12417
12709
|
}
|
|
12418
12710
|
return tasks;
|
|
12419
12711
|
}
|
|
12420
|
-
function parseState(raw, path3) {
|
|
12712
|
+
function parseState(raw, path3, dropped = []) {
|
|
12421
12713
|
if (!isRecord2(raw)) {
|
|
12422
12714
|
throw new Error(`state file "${path3}" must contain a JSON object`);
|
|
12423
12715
|
}
|
|
12424
|
-
const
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
}
|
|
12428
|
-
const chatContexts = raw.chat_contexts;
|
|
12429
|
-
if (!isRecord2(chatContexts)) {
|
|
12430
|
-
throw new Error(`state file "${path3}" must contain an object field "chat_contexts"`);
|
|
12431
|
-
}
|
|
12432
|
-
const parsedSessions = parseSessions(sessions, path3);
|
|
12433
|
-
const orchestration3 = parseOrchestrationState(raw.orchestration, path3);
|
|
12434
|
-
validateExternalCoordinatorIdentityCollisions(parsedSessions, orchestration3, path3);
|
|
12716
|
+
const parsedSessions = parseSessions(sectionRecord(raw.sessions, "sessions", dropped), dropped);
|
|
12717
|
+
const orchestration3 = parseOrchestrationState(raw.orchestration, dropped);
|
|
12718
|
+
repairExternalCoordinatorIdentityCollisions(parsedSessions, orchestration3, dropped);
|
|
12435
12719
|
return {
|
|
12436
12720
|
sessions: parsedSessions,
|
|
12437
|
-
chat_contexts: parseChatContexts(
|
|
12721
|
+
chat_contexts: parseChatContexts(sectionRecord(raw.chat_contexts, "chat_contexts", dropped), dropped),
|
|
12438
12722
|
native_session_lists: parseNativeSessionLists(raw.native_session_lists),
|
|
12439
12723
|
orchestration: orchestration3,
|
|
12440
|
-
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks,
|
|
12724
|
+
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks, dropped)
|
|
12441
12725
|
};
|
|
12442
12726
|
}
|
|
12443
|
-
function
|
|
12727
|
+
function repairExternalCoordinatorIdentityCollisions(sessions, orchestration3, dropped) {
|
|
12444
12728
|
for (const coordinatorSession of Object.keys(orchestration3.externalCoordinators)) {
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
if (orchestration3.workerBindings[coordinatorSession]) {
|
|
12449
|
-
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with a worker binding`);
|
|
12450
|
-
}
|
|
12451
|
-
if (Object.values(orchestration3.tasks).some((task) => task.workerSession === coordinatorSession && (!isTerminalTaskStatus(task.status) || task.reviewPending !== undefined))) {
|
|
12452
|
-
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with an active task worker session`);
|
|
12729
|
+
const conflict = findExternalCoordinatorConflict(coordinatorSession, sessions, orchestration3);
|
|
12730
|
+
if (!conflict) {
|
|
12731
|
+
continue;
|
|
12453
12732
|
}
|
|
12733
|
+
delete orchestration3.externalCoordinators[coordinatorSession];
|
|
12734
|
+
dropped.push({
|
|
12735
|
+
section: "orchestration.externalCoordinators",
|
|
12736
|
+
key: coordinatorSession,
|
|
12737
|
+
reason: `conflicts with ${conflict}; dropped (re-registered on next coordinator connect)`
|
|
12738
|
+
});
|
|
12739
|
+
}
|
|
12740
|
+
}
|
|
12741
|
+
function findExternalCoordinatorConflict(coordinatorSession, sessions, orchestration3) {
|
|
12742
|
+
if (Object.values(sessions).some((session3) => session3.transport_session === coordinatorSession)) {
|
|
12743
|
+
return "a logical session";
|
|
12744
|
+
}
|
|
12745
|
+
if (orchestration3.workerBindings[coordinatorSession]) {
|
|
12746
|
+
return "a worker binding";
|
|
12454
12747
|
}
|
|
12748
|
+
if (Object.values(orchestration3.tasks).some((task) => task.workerSession === coordinatorSession && (!isTerminalTaskStatus(task.status) || task.reviewPending !== undefined))) {
|
|
12749
|
+
return "an active task worker session";
|
|
12750
|
+
}
|
|
12751
|
+
return null;
|
|
12455
12752
|
}
|
|
12456
12753
|
function isTerminalTaskStatus(status) {
|
|
12457
12754
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
@@ -12459,35 +12756,118 @@ function isTerminalTaskStatus(status) {
|
|
|
12459
12756
|
|
|
12460
12757
|
class StateStore {
|
|
12461
12758
|
path;
|
|
12462
|
-
|
|
12759
|
+
options;
|
|
12760
|
+
loadReport = null;
|
|
12761
|
+
constructor(path3, options = {}) {
|
|
12463
12762
|
this.path = path3;
|
|
12763
|
+
this.options = options;
|
|
12764
|
+
}
|
|
12765
|
+
get lastLoadReport() {
|
|
12766
|
+
return this.loadReport;
|
|
12464
12767
|
}
|
|
12465
12768
|
async load() {
|
|
12769
|
+
this.loadReport = null;
|
|
12770
|
+
const read = await this.readAndParse();
|
|
12771
|
+
if (read.kind === "absent") {
|
|
12772
|
+
return createEmptyState();
|
|
12773
|
+
}
|
|
12774
|
+
if (read.kind === "corrupt") {
|
|
12775
|
+
return await this.recoverFromCorruptFile(read.reason);
|
|
12776
|
+
}
|
|
12777
|
+
if (read.dropped.length === 0) {
|
|
12778
|
+
return read.state;
|
|
12779
|
+
}
|
|
12780
|
+
const report = { dropped: read.dropped };
|
|
12781
|
+
const quarantinePath = `${this.path}.quarantine-${this.fileTimestamp()}`;
|
|
12466
12782
|
try {
|
|
12467
|
-
const
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
}
|
|
12479
|
-
|
|
12783
|
+
const written = await (this.options.writeBackup ?? defaultWriteBackup)(quarantinePath, read.content);
|
|
12784
|
+
report.quarantinePath = typeof written === "string" ? written : quarantinePath;
|
|
12785
|
+
} catch (error2) {
|
|
12786
|
+
report.backupError = error2 instanceof Error ? error2.message : String(error2);
|
|
12787
|
+
}
|
|
12788
|
+
this.loadReport = report;
|
|
12789
|
+
return read.state;
|
|
12790
|
+
}
|
|
12791
|
+
async inspect() {
|
|
12792
|
+
const read = await this.readAndParse();
|
|
12793
|
+
if (read.kind === "absent") {
|
|
12794
|
+
return { state: createEmptyState(), report: null };
|
|
12795
|
+
}
|
|
12796
|
+
if (read.kind === "corrupt") {
|
|
12797
|
+
return {
|
|
12798
|
+
state: createEmptyState(),
|
|
12799
|
+
report: { dropped: [{ section: "file", key: this.path, reason: read.reason }] }
|
|
12800
|
+
};
|
|
12801
|
+
}
|
|
12802
|
+
return {
|
|
12803
|
+
state: read.state,
|
|
12804
|
+
report: read.dropped.length > 0 ? { dropped: read.dropped } : null
|
|
12805
|
+
};
|
|
12806
|
+
}
|
|
12807
|
+
async readAndParse() {
|
|
12808
|
+
let content;
|
|
12809
|
+
try {
|
|
12810
|
+
content = await readFile6(this.path, "utf8");
|
|
12480
12811
|
} catch (error2) {
|
|
12481
12812
|
if (error2.code === "ENOENT") {
|
|
12482
|
-
return
|
|
12813
|
+
return { kind: "absent" };
|
|
12483
12814
|
}
|
|
12484
12815
|
throw error2;
|
|
12485
12816
|
}
|
|
12817
|
+
if (content.trim() === "") {
|
|
12818
|
+
return { kind: "absent" };
|
|
12819
|
+
}
|
|
12820
|
+
let parsed;
|
|
12821
|
+
try {
|
|
12822
|
+
parsed = JSON.parse(content);
|
|
12823
|
+
} catch (error2) {
|
|
12824
|
+
return {
|
|
12825
|
+
kind: "corrupt",
|
|
12826
|
+
reason: `invalid JSON: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
12827
|
+
};
|
|
12828
|
+
}
|
|
12829
|
+
if (!isRecord2(parsed)) {
|
|
12830
|
+
return { kind: "corrupt", reason: "top-level value is not an object" };
|
|
12831
|
+
}
|
|
12832
|
+
const dropped = [];
|
|
12833
|
+
return { kind: "parsed", state: parseState(parsed, this.path, dropped), dropped, content };
|
|
12486
12834
|
}
|
|
12487
12835
|
async save(state) {
|
|
12488
|
-
await writePrivateFileAtomic(this.path, JSON.stringify(state, null, 2));
|
|
12836
|
+
await writePrivateFileAtomic(this.path, JSON.stringify({ version: STATE_FILE_VERSION, ...state }, null, 2));
|
|
12837
|
+
}
|
|
12838
|
+
fileTimestamp() {
|
|
12839
|
+
const now = this.options.now?.() ?? new Date;
|
|
12840
|
+
return now.toISOString().replace(/[:.]/g, "-");
|
|
12841
|
+
}
|
|
12842
|
+
async recoverFromCorruptFile(reason) {
|
|
12843
|
+
const corruptPath = `${this.path}.corrupt-${this.fileTimestamp()}`;
|
|
12844
|
+
const report = {
|
|
12845
|
+
dropped: [{ section: "file", key: this.path, reason }]
|
|
12846
|
+
};
|
|
12847
|
+
try {
|
|
12848
|
+
await rename(this.path, corruptPath);
|
|
12849
|
+
report.corruptPath = corruptPath;
|
|
12850
|
+
} catch (error2) {
|
|
12851
|
+
report.backupError = error2 instanceof Error ? error2.message : String(error2);
|
|
12852
|
+
}
|
|
12853
|
+
this.loadReport = report;
|
|
12854
|
+
return createEmptyState();
|
|
12855
|
+
}
|
|
12856
|
+
}
|
|
12857
|
+
async function defaultWriteBackup(targetPath, content) {
|
|
12858
|
+
for (let attempt = 0;; attempt += 1) {
|
|
12859
|
+
const candidate = attempt === 0 ? targetPath : `${targetPath}-${attempt}`;
|
|
12860
|
+
try {
|
|
12861
|
+
await writeFile3(candidate, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
12862
|
+
return candidate;
|
|
12863
|
+
} catch (error2) {
|
|
12864
|
+
if (error2.code !== "EEXIST" || attempt >= 9) {
|
|
12865
|
+
throw error2;
|
|
12866
|
+
}
|
|
12867
|
+
}
|
|
12489
12868
|
}
|
|
12490
12869
|
}
|
|
12870
|
+
var STATE_FILE_VERSION = 1;
|
|
12491
12871
|
var init_state_store = __esm(() => {
|
|
12492
12872
|
init_private_file();
|
|
12493
12873
|
init_types();
|
|
@@ -12726,14 +13106,23 @@ class ScheduledTaskService {
|
|
|
12726
13106
|
return task;
|
|
12727
13107
|
});
|
|
12728
13108
|
}
|
|
12729
|
-
listPending() {
|
|
13109
|
+
listPending(chatKey) {
|
|
13110
|
+
return this.listPendingAllChats().filter((task) => task.chat_key === chatKey);
|
|
13111
|
+
}
|
|
13112
|
+
listPendingAllChats() {
|
|
12730
13113
|
return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
|
|
12731
13114
|
}
|
|
12732
|
-
async cancelPending(inputId) {
|
|
13115
|
+
async cancelPending(inputId, chatKey) {
|
|
13116
|
+
return await this.cancelPendingWhere(inputId, (task) => task.chat_key === chatKey);
|
|
13117
|
+
}
|
|
13118
|
+
async cancelPendingAnyChat(inputId) {
|
|
13119
|
+
return await this.cancelPendingWhere(inputId, () => true);
|
|
13120
|
+
}
|
|
13121
|
+
async cancelPendingWhere(inputId, allowed) {
|
|
12733
13122
|
return await this.mutate(async () => {
|
|
12734
13123
|
const id = normalizeId(inputId);
|
|
12735
13124
|
const task = this.state.scheduled_tasks[id];
|
|
12736
|
-
if (!task || task.status !== "pending")
|
|
13125
|
+
if (!task || task.status !== "pending" || !allowed(task))
|
|
12737
13126
|
return false;
|
|
12738
13127
|
task.status = "cancelled";
|
|
12739
13128
|
task.cancelled_at = this.now().toISOString();
|
|
@@ -12765,7 +13154,7 @@ class ScheduledTaskService {
|
|
|
12765
13154
|
async claimDueTasks() {
|
|
12766
13155
|
return await this.mutate(async () => {
|
|
12767
13156
|
const nowMs = this.now().getTime();
|
|
12768
|
-
const due = this.
|
|
13157
|
+
const due = this.listPendingAllChats().filter((task) => Date.parse(task.execute_at) <= nowMs);
|
|
12769
13158
|
if (due.length === 0)
|
|
12770
13159
|
return [];
|
|
12771
13160
|
const at = this.now().toISOString();
|
|
@@ -12774,7 +13163,16 @@ class ScheduledTaskService {
|
|
|
12774
13163
|
task.triggered_at = at;
|
|
12775
13164
|
this.claimedInThisSession.add(task.id);
|
|
12776
13165
|
}
|
|
12777
|
-
|
|
13166
|
+
try {
|
|
13167
|
+
await this.save();
|
|
13168
|
+
} catch (error2) {
|
|
13169
|
+
for (const task of due) {
|
|
13170
|
+
task.status = "pending";
|
|
13171
|
+
delete task.triggered_at;
|
|
13172
|
+
this.claimedInThisSession.delete(task.id);
|
|
13173
|
+
}
|
|
13174
|
+
throw error2;
|
|
13175
|
+
}
|
|
12778
13176
|
return due.map((task) => ({ ...task }));
|
|
12779
13177
|
});
|
|
12780
13178
|
}
|
|
@@ -12832,20 +13230,20 @@ var init_scheduled_service = () => {};
|
|
|
12832
13230
|
|
|
12833
13231
|
// src/plugins/plugin-home.ts
|
|
12834
13232
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
12835
|
-
import { copyFile, mkdir as
|
|
13233
|
+
import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
12836
13234
|
import { homedir as homedir3 } from "node:os";
|
|
12837
|
-
import { dirname as
|
|
13235
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
12838
13236
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12839
13237
|
function resolveCoreRoot() {
|
|
12840
13238
|
try {
|
|
12841
|
-
let dir =
|
|
13239
|
+
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
12842
13240
|
for (let depth = 0;depth < 12; depth++) {
|
|
12843
13241
|
try {
|
|
12844
13242
|
const pkg = JSON.parse(readFileSync2(join6(dir, "package.json"), "utf-8"));
|
|
12845
13243
|
if (pkg.name && CORE_ROOT_NAMES.includes(pkg.name))
|
|
12846
13244
|
return dir;
|
|
12847
13245
|
} catch {}
|
|
12848
|
-
const parent =
|
|
13246
|
+
const parent = dirname4(dir);
|
|
12849
13247
|
if (parent === dir)
|
|
12850
13248
|
break;
|
|
12851
13249
|
dir = parent;
|
|
@@ -12863,7 +13261,7 @@ async function ensureCoreResolution(pluginHome) {
|
|
|
12863
13261
|
for (const name of SHIM_SPECIFIERS) {
|
|
12864
13262
|
const targetDir = join6(pluginHome, "node_modules", name);
|
|
12865
13263
|
const dstJs = join6(targetDir, "plugin-api.js");
|
|
12866
|
-
await
|
|
13264
|
+
await mkdir4(targetDir, { recursive: true });
|
|
12867
13265
|
try {
|
|
12868
13266
|
await copyFile(srcJs, dstJs);
|
|
12869
13267
|
} catch (error2) {
|
|
@@ -12906,7 +13304,7 @@ async function normalizePluginHomeManifest(pluginHome) {
|
|
|
12906
13304
|
const manifestPath = join6(pluginHome, "package.json");
|
|
12907
13305
|
let raw;
|
|
12908
13306
|
try {
|
|
12909
|
-
raw = await
|
|
13307
|
+
raw = await readFile7(manifestPath, "utf8");
|
|
12910
13308
|
} catch {
|
|
12911
13309
|
return false;
|
|
12912
13310
|
}
|
|
@@ -12924,7 +13322,7 @@ async function normalizePluginHomeManifest(pluginHome) {
|
|
|
12924
13322
|
return true;
|
|
12925
13323
|
}
|
|
12926
13324
|
async function ensurePluginHome(pluginHome) {
|
|
12927
|
-
await
|
|
13325
|
+
await mkdir4(pluginHome, { recursive: true, mode: 448 });
|
|
12928
13326
|
await writeFile4(join6(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
|
|
12929
13327
|
`, { flag: "wx" }).catch((error2) => {
|
|
12930
13328
|
if (error2.code !== "EEXIST")
|
|
@@ -15540,7 +15938,7 @@ function createConversationExecutor() {
|
|
|
15540
15938
|
var DEFAULT_SESSION_KEY = "__chat__";
|
|
15541
15939
|
|
|
15542
15940
|
// src/channels/media-store.ts
|
|
15543
|
-
import { access as access2, mkdir as
|
|
15941
|
+
import { access as access2, mkdir as mkdir5, readdir, rm as rm5, stat, writeFile as writeFile5 } from "node:fs/promises";
|
|
15544
15942
|
import path7 from "node:path";
|
|
15545
15943
|
|
|
15546
15944
|
class RuntimeMediaStore {
|
|
@@ -15558,7 +15956,7 @@ class RuntimeMediaStore {
|
|
|
15558
15956
|
const safeMessageId = safePathSegment(input.messageId || "message");
|
|
15559
15957
|
const baseFileName = sanitizeMediaFileName(input.fileName ?? "attachment", input.mimeType);
|
|
15560
15958
|
const dir = path7.join(this.rootDir, input.channelId, safeChatKey, safeMessageId);
|
|
15561
|
-
await
|
|
15959
|
+
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
15562
15960
|
const resolvedRoot = path7.resolve(this.rootDir);
|
|
15563
15961
|
const resolvedFile = path7.resolve(path7.join(dir, await uniqueFileName(dir, baseFileName)));
|
|
15564
15962
|
if (!isPathInside(resolvedFile, resolvedRoot)) {
|
|
@@ -16947,15 +17345,6 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
16947
17345
|
await drainPendingFinalForJx(ctx);
|
|
16948
17346
|
return { handled: true };
|
|
16949
17347
|
}
|
|
16950
|
-
case "/logout": {
|
|
16951
|
-
if (listWeixinAccountIds().length === 0) {
|
|
16952
|
-
await sendReply(ctx, t().weixin.noAccountsLoggedIn);
|
|
16953
|
-
return { handled: true };
|
|
16954
|
-
}
|
|
16955
|
-
clearAllWeixinAccounts();
|
|
16956
|
-
await sendReply(ctx, t().weixin.logoutSuccess);
|
|
16957
|
-
return { handled: true };
|
|
16958
|
-
}
|
|
16959
17348
|
default:
|
|
16960
17349
|
return { handled: false };
|
|
16961
17350
|
}
|
|
@@ -16968,7 +17357,6 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
16968
17357
|
}
|
|
16969
17358
|
}
|
|
16970
17359
|
var init_slash_commands = __esm(() => {
|
|
16971
|
-
init_accounts();
|
|
16972
17360
|
init_logger();
|
|
16973
17361
|
init_i18n();
|
|
16974
17362
|
init_final_heads_up();
|
|
@@ -16984,14 +17372,14 @@ function normalizeMediaArray(media) {
|
|
|
16984
17372
|
}
|
|
16985
17373
|
|
|
16986
17374
|
// src/logging/rotating-file-writer.ts
|
|
16987
|
-
import { readdir as readdir2, rename, rm as rm6, stat as stat2 } from "node:fs/promises";
|
|
16988
|
-
import { basename, dirname as
|
|
17375
|
+
import { readdir as readdir2, rename as rename2, rm as rm6, stat as stat2 } from "node:fs/promises";
|
|
17376
|
+
import { basename, dirname as dirname5, join as join8 } from "node:path";
|
|
16989
17377
|
async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
16990
17378
|
let currentSize = 0;
|
|
16991
17379
|
try {
|
|
16992
17380
|
currentSize = (await stat2(filePath)).size;
|
|
16993
17381
|
} catch (error2) {
|
|
16994
|
-
if (!
|
|
17382
|
+
if (!isMissingFileError3(error2)) {
|
|
16995
17383
|
throw error2;
|
|
16996
17384
|
}
|
|
16997
17385
|
}
|
|
@@ -17009,24 +17397,24 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
|
17009
17397
|
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
17010
17398
|
const source = `${filePath}.${index}`;
|
|
17011
17399
|
try {
|
|
17012
|
-
await
|
|
17400
|
+
await rename2(source, `${filePath}.${index + 1}`);
|
|
17013
17401
|
} catch (error2) {
|
|
17014
|
-
if (!
|
|
17402
|
+
if (!isMissingFileError3(error2)) {
|
|
17015
17403
|
throw error2;
|
|
17016
17404
|
}
|
|
17017
17405
|
}
|
|
17018
17406
|
}
|
|
17019
|
-
await
|
|
17407
|
+
await rename2(filePath, `${filePath}.1`);
|
|
17020
17408
|
}
|
|
17021
17409
|
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
17022
|
-
const parentDir =
|
|
17410
|
+
const parentDir = dirname5(filePath);
|
|
17023
17411
|
const prefix = `${basename(filePath)}.`;
|
|
17024
17412
|
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
17025
17413
|
let files = [];
|
|
17026
17414
|
try {
|
|
17027
17415
|
files = await readdir2(parentDir);
|
|
17028
17416
|
} catch (error2) {
|
|
17029
|
-
if (
|
|
17417
|
+
if (isMissingFileError3(error2)) {
|
|
17030
17418
|
return;
|
|
17031
17419
|
}
|
|
17032
17420
|
throw error2;
|
|
@@ -17036,23 +17424,31 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
|
17036
17424
|
continue;
|
|
17037
17425
|
}
|
|
17038
17426
|
const candidate = join8(parentDir, file);
|
|
17039
|
-
|
|
17427
|
+
let details;
|
|
17428
|
+
try {
|
|
17429
|
+
details = await stat2(candidate);
|
|
17430
|
+
} catch (error2) {
|
|
17431
|
+
if (isMissingFileError3(error2)) {
|
|
17432
|
+
continue;
|
|
17433
|
+
}
|
|
17434
|
+
throw error2;
|
|
17435
|
+
}
|
|
17040
17436
|
if (details.mtime.getTime() < cutoff) {
|
|
17041
17437
|
await rm6(candidate, { force: true });
|
|
17042
17438
|
}
|
|
17043
17439
|
}
|
|
17044
17440
|
}
|
|
17045
|
-
function
|
|
17441
|
+
function isMissingFileError3(error2) {
|
|
17046
17442
|
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
17047
17443
|
}
|
|
17048
17444
|
var init_rotating_file_writer = () => {};
|
|
17049
17445
|
|
|
17050
17446
|
// src/perf/perf-log-writer.ts
|
|
17051
17447
|
import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
|
|
17052
|
-
import { dirname as
|
|
17448
|
+
import { dirname as dirname6 } from "node:path";
|
|
17053
17449
|
function createPerfLogWriter(options) {
|
|
17054
17450
|
const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
|
|
17055
|
-
const
|
|
17451
|
+
const mkdir6 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
|
|
17056
17452
|
return;
|
|
17057
17453
|
}));
|
|
17058
17454
|
const now = options.now ?? (() => new Date);
|
|
@@ -17099,7 +17495,7 @@ function createPerfLogWriter(options) {
|
|
|
17099
17495
|
return;
|
|
17100
17496
|
const data = batch.join("");
|
|
17101
17497
|
try {
|
|
17102
|
-
await
|
|
17498
|
+
await mkdir6(dirname6(options.filePath), { recursive: true, mode: 448 });
|
|
17103
17499
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
|
|
17104
17500
|
await append(options.filePath, data);
|
|
17105
17501
|
consecutiveFailures = 0;
|
|
@@ -17630,13 +18026,39 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
17630
18026
|
deps.errLog(`weixin.final.dropped reason=backgrounded_no_store kind=text chatKey=${to}`);
|
|
17631
18027
|
} else {
|
|
17632
18028
|
const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
|
|
18029
|
+
const sendAllParkedNotice = async (count) => {
|
|
18030
|
+
try {
|
|
18031
|
+
await sendMessageWeixin({
|
|
18032
|
+
to,
|
|
18033
|
+
text: t().misc.finalAllParked(count),
|
|
18034
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
18035
|
+
});
|
|
18036
|
+
} catch (noticeErr) {
|
|
18037
|
+
deps.errLog(`weixin.final.parked_notice_failed chatKey=${to} err=${String(noticeErr)}`);
|
|
18038
|
+
}
|
|
18039
|
+
};
|
|
18040
|
+
const buildPendingChunk = (text, seq, total) => {
|
|
18041
|
+
const entry = { text, seq, total };
|
|
18042
|
+
if (contextToken !== undefined)
|
|
18043
|
+
entry.contextToken = contextToken;
|
|
18044
|
+
if (deps.accountId !== undefined)
|
|
18045
|
+
entry.accountId = deps.accountId;
|
|
18046
|
+
return entry;
|
|
18047
|
+
};
|
|
17633
18048
|
if (rawChunks.length > 0) {
|
|
17634
18049
|
const total = rawChunks.length;
|
|
17635
18050
|
if (total === 1) {
|
|
17636
18051
|
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
17637
18052
|
if (!reserved) {
|
|
17638
|
-
|
|
17639
|
-
|
|
18053
|
+
if (deps.enqueuePendingFinal) {
|
|
18054
|
+
deps.enqueuePendingFinal(to, [buildPendingChunk(rawChunks[0], 1, 1)]);
|
|
18055
|
+
finalChunksPending = 1;
|
|
18056
|
+
deps.errLog(`weixin.final.parked reason=quota_exhausted kind=text chatKey=${to}`);
|
|
18057
|
+
await sendAllParkedNotice(1);
|
|
18058
|
+
} else {
|
|
18059
|
+
finalDropped = true;
|
|
18060
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
|
|
18061
|
+
}
|
|
17640
18062
|
} else {
|
|
17641
18063
|
await sendMessageWeixin({
|
|
17642
18064
|
to,
|
|
@@ -17693,16 +18115,11 @@ ${buildFinalHeadsUp({
|
|
|
17693
18115
|
const restToPark = prefixed.slice(sent);
|
|
17694
18116
|
finalChunksPending = restToPark.length;
|
|
17695
18117
|
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
17696
|
-
const pending = restToPark.map((text, idx) =>
|
|
17697
|
-
const seq = sent + idx + 1;
|
|
17698
|
-
const entry = { text, seq, total };
|
|
17699
|
-
if (contextToken !== undefined)
|
|
17700
|
-
entry.contextToken = contextToken;
|
|
17701
|
-
if (deps.accountId !== undefined)
|
|
17702
|
-
entry.accountId = deps.accountId;
|
|
17703
|
-
return entry;
|
|
17704
|
-
});
|
|
18118
|
+
const pending = restToPark.map((text, idx) => buildPendingChunk(text, sent + idx + 1, total));
|
|
17705
18119
|
deps.enqueuePendingFinal(to, pending);
|
|
18120
|
+
if (sent === 0) {
|
|
18121
|
+
await sendAllParkedNotice(restToPark.length);
|
|
18122
|
+
}
|
|
17706
18123
|
}
|
|
17707
18124
|
}
|
|
17708
18125
|
}
|
|
@@ -17898,7 +18315,7 @@ function shouldFetchTypingConfig(textBody) {
|
|
|
17898
18315
|
const command = parseSlashCommand(textBody);
|
|
17899
18316
|
if (!command)
|
|
17900
18317
|
return true;
|
|
17901
|
-
return !["/cancel", "/stop", "/jx", "/echo", "/toggle-debug"
|
|
18318
|
+
return !["/cancel", "/stop", "/jx", "/echo", "/toggle-debug"].includes(command);
|
|
17902
18319
|
}
|
|
17903
18320
|
async function monitorWeixinProvider(opts) {
|
|
17904
18321
|
const {
|
|
@@ -18621,7 +19038,7 @@ ${buildFinalHeadsUp({
|
|
|
18621
19038
|
let sent = 0;
|
|
18622
19039
|
for (let index = 0;index < wave.length; index += 1) {
|
|
18623
19040
|
if (!deps.reserveFinal(quotaKey)) {
|
|
18624
|
-
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 });
|
|
19041
|
+
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 });
|
|
18625
19042
|
break;
|
|
18626
19043
|
}
|
|
18627
19044
|
const delivered = await sendTextViaAvailableAccount(wave[index], "scheduled.final_send_failed");
|
|
@@ -18630,7 +19047,7 @@ ${buildFinalHeadsUp({
|
|
|
18630
19047
|
sent += 1;
|
|
18631
19048
|
}
|
|
18632
19049
|
const restToPark = chunks.slice(sent);
|
|
18633
|
-
if (
|
|
19050
|
+
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
18634
19051
|
const pending = restToPark.map((text, index) => {
|
|
18635
19052
|
const entry = { text, seq: sent + index + 1, total };
|
|
18636
19053
|
if (deliveryContextToken)
|
|
@@ -18640,6 +19057,12 @@ ${buildFinalHeadsUp({
|
|
|
18640
19057
|
return entry;
|
|
18641
19058
|
});
|
|
18642
19059
|
deps.enqueuePendingFinal(quotaKey, pending);
|
|
19060
|
+
if (sent === 0) {
|
|
19061
|
+
const noticeDelivered = await sendTextViaAvailableAccount(t().misc.finalAllParked(restToPark.length), "scheduled.final_parked_notice_failed");
|
|
19062
|
+
if (!noticeDelivered) {
|
|
19063
|
+
await deps.logger.info("scheduled.final_parked_notice_failed", "scheduled parked-final notice could not be delivered", { chatKey: input.chatKey, parked: restToPark.length });
|
|
19064
|
+
}
|
|
19065
|
+
}
|
|
18643
19066
|
}
|
|
18644
19067
|
}
|
|
18645
19068
|
async function sendTextViaAvailableAccount(text, errorEvent) {
|
|
@@ -18677,8 +19100,8 @@ var init_scheduled_turn = __esm(() => {
|
|
|
18677
19100
|
});
|
|
18678
19101
|
|
|
18679
19102
|
// src/weixin/monitor/consumer-lock.ts
|
|
18680
|
-
import { mkdir as
|
|
18681
|
-
import { dirname as
|
|
19103
|
+
import { mkdir as mkdir6, open as open3, readFile as readFile8, rm as rm7 } from "node:fs/promises";
|
|
19104
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
18682
19105
|
import { homedir as homedir4 } from "node:os";
|
|
18683
19106
|
function createWeixinConsumerLock(options = {}) {
|
|
18684
19107
|
const lockFilePath = options.lockFilePath ?? join9(coreHomeDir(homedir4()), "runtime", "weixin-consumer.lock.json");
|
|
@@ -18686,7 +19109,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
18686
19109
|
const onDiagnostic = options.onDiagnostic;
|
|
18687
19110
|
return {
|
|
18688
19111
|
async acquire(meta2) {
|
|
18689
|
-
await
|
|
19112
|
+
await mkdir6(dirname7(lockFilePath), { recursive: true, mode: 448 });
|
|
18690
19113
|
while (true) {
|
|
18691
19114
|
try {
|
|
18692
19115
|
const handle = await open3(lockFilePath, "wx");
|
|
@@ -18759,7 +19182,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
18759
19182
|
}
|
|
18760
19183
|
async function loadLockMetadata(path14) {
|
|
18761
19184
|
try {
|
|
18762
|
-
const raw = await
|
|
19185
|
+
const raw = await readFile8(path14, "utf8");
|
|
18763
19186
|
const parsed = JSON.parse(raw);
|
|
18764
19187
|
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
18765
19188
|
return null;
|
|
@@ -18824,6 +19247,13 @@ class WeixinChannel {
|
|
|
18824
19247
|
logout() {
|
|
18825
19248
|
logout();
|
|
18826
19249
|
}
|
|
19250
|
+
stop() {
|
|
19251
|
+
this.agent = null;
|
|
19252
|
+
this.quota = null;
|
|
19253
|
+
this.logger = null;
|
|
19254
|
+
this.markDelivered = null;
|
|
19255
|
+
this.markFailed = null;
|
|
19256
|
+
}
|
|
18827
19257
|
createConsumerLock(options) {
|
|
18828
19258
|
return createWeixinConsumerLock({
|
|
18829
19259
|
...options?.lockFilePath ? { lockFilePath: options.lockFilePath } : {},
|
|
@@ -19393,6 +19823,122 @@ var init_plugin_loader = __esm(() => {
|
|
|
19393
19823
|
init_plugin_home();
|
|
19394
19824
|
});
|
|
19395
19825
|
|
|
19826
|
+
// src/plugins/plugin-doctor.ts
|
|
19827
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
19828
|
+
import { join as join12 } from "node:path";
|
|
19829
|
+
function suggestedPluginPackageForChannel(type) {
|
|
19830
|
+
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
19831
|
+
}
|
|
19832
|
+
async function readDependencyEntries(pluginHome) {
|
|
19833
|
+
try {
|
|
19834
|
+
const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
|
|
19835
|
+
const parsed = JSON.parse(raw);
|
|
19836
|
+
const out = {};
|
|
19837
|
+
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
19838
|
+
if (typeof value === "string")
|
|
19839
|
+
out[name] = value;
|
|
19840
|
+
}
|
|
19841
|
+
return out;
|
|
19842
|
+
} catch (error2) {
|
|
19843
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19844
|
+
throw new Error(`failed to read plugin home package.json: ${message}`);
|
|
19845
|
+
}
|
|
19846
|
+
}
|
|
19847
|
+
async function inspectPlugins(input) {
|
|
19848
|
+
const issues = [];
|
|
19849
|
+
let dependencies;
|
|
19850
|
+
try {
|
|
19851
|
+
dependencies = await readDependencyEntries(input.pluginHome);
|
|
19852
|
+
} catch (error2) {
|
|
19853
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19854
|
+
return [{ level: "error", message }];
|
|
19855
|
+
}
|
|
19856
|
+
const importPlugin = input.importPlugin ?? importPluginFromHome;
|
|
19857
|
+
const allConfigured = input.config.plugins;
|
|
19858
|
+
const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
|
|
19859
|
+
if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
|
|
19860
|
+
return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
|
|
19861
|
+
}
|
|
19862
|
+
const pushIfRelevant = (issue2) => {
|
|
19863
|
+
if (!filterByName || issue2.plugin === filterByName)
|
|
19864
|
+
issues.push(issue2);
|
|
19865
|
+
};
|
|
19866
|
+
const channelProviders = new Map;
|
|
19867
|
+
for (const configPlugin of allConfigured) {
|
|
19868
|
+
if (!(configPlugin.name in dependencies)) {
|
|
19869
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `package not installed in plugin home; run xacpx plugin add ${configPlugin.name}`, suggestion: `xacpx plugin add ${configPlugin.name} && xacpx restart` });
|
|
19870
|
+
continue;
|
|
19871
|
+
}
|
|
19872
|
+
let moduleValue;
|
|
19873
|
+
try {
|
|
19874
|
+
moduleValue = await importPlugin(configPlugin.name, input.pluginHome);
|
|
19875
|
+
} catch (error2) {
|
|
19876
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19877
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `failed to import plugin: ${message}`, suggestion: `xacpx plugin add ${configPlugin.name} && xacpx restart` });
|
|
19878
|
+
continue;
|
|
19879
|
+
}
|
|
19880
|
+
try {
|
|
19881
|
+
const plugin = validateWeacpxPlugin(moduleValue, configPlugin.name, {
|
|
19882
|
+
...input.currentXacpxVersion !== undefined ? { currentXacpxVersion: input.currentXacpxVersion } : {}
|
|
19883
|
+
});
|
|
19884
|
+
const channels = plugin.channels ?? [];
|
|
19885
|
+
const channelTypes = channels.map((channel) => channel.type);
|
|
19886
|
+
for (const type of channelTypes) {
|
|
19887
|
+
const existing = channelProviders.get(type);
|
|
19888
|
+
if (existing) {
|
|
19889
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `channel type ${type} is already provided by ${existing.plugin}` });
|
|
19890
|
+
} else {
|
|
19891
|
+
channelProviders.set(type, { plugin: configPlugin.name, enabled: configPlugin.enabled });
|
|
19892
|
+
}
|
|
19893
|
+
}
|
|
19894
|
+
pushIfRelevant({
|
|
19895
|
+
level: configPlugin.enabled ? "ok" : "warn",
|
|
19896
|
+
plugin: configPlugin.name,
|
|
19897
|
+
message: configPlugin.enabled ? `plugin is installed and valid; channels: ${channelTypes.length > 0 ? channelTypes.join(", ") : "none"}` : `plugin is installed and valid but disabled; run xacpx plugin enable ${configPlugin.name}`,
|
|
19898
|
+
...configPlugin.enabled ? {} : { suggestion: `xacpx plugin enable ${configPlugin.name}` }
|
|
19899
|
+
});
|
|
19900
|
+
} catch (error2) {
|
|
19901
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19902
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message });
|
|
19903
|
+
}
|
|
19904
|
+
}
|
|
19905
|
+
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
19906
|
+
for (const channel of input.config.channels) {
|
|
19907
|
+
if (channel.enabled === false)
|
|
19908
|
+
continue;
|
|
19909
|
+
if (builtInChannelTypes.has(channel.type))
|
|
19910
|
+
continue;
|
|
19911
|
+
const provider = channelProviders.get(channel.type);
|
|
19912
|
+
if (!provider) {
|
|
19913
|
+
if (!filterByName) {
|
|
19914
|
+
const suggestedPackage = suggestedPluginPackageForChannel(channel.type);
|
|
19915
|
+
issues.push({
|
|
19916
|
+
level: "error",
|
|
19917
|
+
message: `channel ${channel.type} is configured but no enabled plugin provides it; run xacpx plugin add ${suggestedPackage} or another plugin that provides type "${channel.type}"`,
|
|
19918
|
+
suggestion: `xacpx plugin add ${suggestedPackage}`
|
|
19919
|
+
});
|
|
19920
|
+
}
|
|
19921
|
+
continue;
|
|
19922
|
+
}
|
|
19923
|
+
if (!provider.enabled) {
|
|
19924
|
+
pushIfRelevant({
|
|
19925
|
+
level: "error",
|
|
19926
|
+
plugin: provider.plugin,
|
|
19927
|
+
message: `channel ${channel.type} is configured but provider plugin is disabled; run xacpx plugin enable ${provider.plugin}`,
|
|
19928
|
+
suggestion: `xacpx plugin enable ${provider.plugin}`
|
|
19929
|
+
});
|
|
19930
|
+
}
|
|
19931
|
+
}
|
|
19932
|
+
return issues;
|
|
19933
|
+
}
|
|
19934
|
+
var init_plugin_doctor = __esm(() => {
|
|
19935
|
+
init_channel_scope();
|
|
19936
|
+
init_plugin_loader();
|
|
19937
|
+
init_validate_plugin();
|
|
19938
|
+
init_known_plugins();
|
|
19939
|
+
init_plugin_renames();
|
|
19940
|
+
});
|
|
19941
|
+
|
|
19396
19942
|
// src/channels/bootstrap.ts
|
|
19397
19943
|
function bootstrapBuiltinChannels() {
|
|
19398
19944
|
bootstrapBuiltinChannelFactories();
|
|
@@ -19404,8 +19950,8 @@ var init_bootstrap = __esm(() => {
|
|
|
19404
19950
|
});
|
|
19405
19951
|
|
|
19406
19952
|
// src/logging/app-logger.ts
|
|
19407
|
-
import { appendFile, chmod as
|
|
19408
|
-
import { dirname as
|
|
19953
|
+
import { appendFile, chmod as chmod3, mkdir as mkdir7 } from "node:fs/promises";
|
|
19954
|
+
import { dirname as dirname9 } from "node:path";
|
|
19409
19955
|
function createNoopAppLogger() {
|
|
19410
19956
|
return {
|
|
19411
19957
|
debug: async () => {},
|
|
@@ -19419,6 +19965,7 @@ function createAppLogger(options) {
|
|
|
19419
19965
|
const now = options.now ?? (() => new Date);
|
|
19420
19966
|
let writeChain = Promise.resolve();
|
|
19421
19967
|
let modeEnsured = false;
|
|
19968
|
+
let writeErrorLatched = false;
|
|
19422
19969
|
return {
|
|
19423
19970
|
debug: async (event, message, context) => {
|
|
19424
19971
|
await enqueueWrite("debug", event, message, context);
|
|
@@ -19430,14 +19977,21 @@ function createAppLogger(options) {
|
|
|
19430
19977
|
await enqueueWrite("error", event, message, context);
|
|
19431
19978
|
},
|
|
19432
19979
|
cleanup: async () => {
|
|
19433
|
-
|
|
19980
|
+
try {
|
|
19981
|
+
await cleanupExpiredRotatedLogs(options.filePath, options.retentionDays, now);
|
|
19982
|
+
} catch {}
|
|
19434
19983
|
},
|
|
19435
19984
|
flush: async () => {
|
|
19436
19985
|
await writeChain;
|
|
19437
19986
|
}
|
|
19438
19987
|
};
|
|
19439
19988
|
function enqueueWrite(level, event, message, context = {}) {
|
|
19440
|
-
const writePromise = writeChain.
|
|
19989
|
+
const writePromise = writeChain.then(() => writeLog2(level, event, message, context)).catch((error2) => {
|
|
19990
|
+
if (!writeErrorLatched) {
|
|
19991
|
+
writeErrorLatched = true;
|
|
19992
|
+
console.error("[xacpx] app-logger: log file write failed — further write errors will be suppressed.", error2 instanceof Error ? error2.message : String(error2));
|
|
19993
|
+
}
|
|
19994
|
+
});
|
|
19441
19995
|
writeChain = writePromise;
|
|
19442
19996
|
return writePromise;
|
|
19443
19997
|
}
|
|
@@ -19446,13 +20000,14 @@ function createAppLogger(options) {
|
|
|
19446
20000
|
return;
|
|
19447
20001
|
}
|
|
19448
20002
|
const line = formatLogLine(now(), level, event, message, context);
|
|
19449
|
-
await
|
|
20003
|
+
await mkdir7(dirname9(options.filePath), { recursive: true, mode: 448 });
|
|
19450
20004
|
if (!modeEnsured) {
|
|
19451
20005
|
modeEnsured = true;
|
|
19452
|
-
await
|
|
20006
|
+
await chmod3(options.filePath, 384).catch(() => {});
|
|
19453
20007
|
}
|
|
19454
20008
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
|
|
19455
20009
|
await appendFile(options.filePath, line, { encoding: "utf8", mode: 384 });
|
|
20010
|
+
writeErrorLatched = false;
|
|
19456
20011
|
}
|
|
19457
20012
|
}
|
|
19458
20013
|
function formatLogLine(time3, level, event, message, context) {
|
|
@@ -19481,7 +20036,7 @@ var init_app_logger = __esm(() => {
|
|
|
19481
20036
|
});
|
|
19482
20037
|
|
|
19483
20038
|
// src/transport/acpx-session-index.ts
|
|
19484
|
-
import { readFile as
|
|
20039
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
19485
20040
|
import { homedir as homedir5 } from "node:os";
|
|
19486
20041
|
import { resolve as resolve2 } from "node:path";
|
|
19487
20042
|
async function resolveSessionAgentCommandFromIndex(session3) {
|
|
@@ -19490,7 +20045,7 @@ async function resolveSessionAgentCommandFromIndex(session3) {
|
|
|
19490
20045
|
return;
|
|
19491
20046
|
}
|
|
19492
20047
|
try {
|
|
19493
|
-
const raw = await
|
|
20048
|
+
const raw = await readFile12(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
|
|
19494
20049
|
const parsed = JSON.parse(raw);
|
|
19495
20050
|
const targetCwd = resolve2(session3.cwd);
|
|
19496
20051
|
const match = parsed.entries?.find((entry) => entry.name === session3.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
|
|
@@ -19672,7 +20227,9 @@ function parseCommand(input) {
|
|
|
19672
20227
|
if (!trimmed.startsWith("/")) {
|
|
19673
20228
|
return { kind: "prompt", text: trimmed };
|
|
19674
20229
|
}
|
|
19675
|
-
const
|
|
20230
|
+
const tokens = tokenizeCommand(trimmed);
|
|
20231
|
+
const parts = tokens.map((token) => token.value);
|
|
20232
|
+
const rawTail = (index) => index < tokens.length ? trimmed.slice(tokens[index]?.start ?? 0) : "";
|
|
19676
20233
|
const command = normalizeCommand(parts[0] ?? "");
|
|
19677
20234
|
if (command === "/help" && parts.length === 1)
|
|
19678
20235
|
return { kind: "help" };
|
|
@@ -19781,7 +20338,7 @@ function parseCommand(input) {
|
|
|
19781
20338
|
return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
|
|
19782
20339
|
}
|
|
19783
20340
|
if (command === "/group" && parts[1] === "new" && parts.length > 2) {
|
|
19784
|
-
const title =
|
|
20341
|
+
const title = rawTail(2);
|
|
19785
20342
|
if (title.trim().length > 0) {
|
|
19786
20343
|
return { kind: "group.new", title };
|
|
19787
20344
|
}
|
|
@@ -19805,7 +20362,7 @@ function parseCommand(input) {
|
|
|
19805
20362
|
}
|
|
19806
20363
|
break;
|
|
19807
20364
|
}
|
|
19808
|
-
const task =
|
|
20365
|
+
const task = rawTail(index);
|
|
19809
20366
|
if (groupId.trim().length > 0 && targetAgent.trim().length > 0 && task.trim().length > 0) {
|
|
19810
20367
|
return {
|
|
19811
20368
|
kind: "group.delegate",
|
|
@@ -19859,7 +20416,7 @@ function parseCommand(input) {
|
|
|
19859
20416
|
return { kind: "agent.rm", name: parts[2] };
|
|
19860
20417
|
}
|
|
19861
20418
|
if ((command === "/delegate" || command === "/dg") && parts[1]) {
|
|
19862
|
-
const parsedDelegate = parseDelegateRequest(parts);
|
|
20419
|
+
const parsedDelegate = parseDelegateRequest(parts, rawTail);
|
|
19863
20420
|
if (parsedDelegate) {
|
|
19864
20421
|
return parsedDelegate;
|
|
19865
20422
|
}
|
|
@@ -19887,7 +20444,11 @@ function parseCommand(input) {
|
|
|
19887
20444
|
if (parts[1] === "cancel" && parts[2] && parts.length === 3) {
|
|
19888
20445
|
return { kind: "later.cancel", id: parts[2] };
|
|
19889
20446
|
}
|
|
19890
|
-
return {
|
|
20447
|
+
return {
|
|
20448
|
+
kind: "later.create",
|
|
20449
|
+
tokens: parts.slice(1),
|
|
20450
|
+
tails: tokens.slice(1).map((_, index) => rawTail(index + 1))
|
|
20451
|
+
};
|
|
19891
20452
|
}
|
|
19892
20453
|
if (command === "/workspace" && parts[1] === "new" && parts[2]) {
|
|
19893
20454
|
const name = parts[2];
|
|
@@ -20140,17 +20701,18 @@ function readNativeAttachCommand(parts, identifierIndex) {
|
|
|
20140
20701
|
return { kind: "session.native.attach", identifier };
|
|
20141
20702
|
}
|
|
20142
20703
|
function normalizeCommand(command) {
|
|
20143
|
-
|
|
20704
|
+
const lowered = command.toLowerCase();
|
|
20705
|
+
if (lowered === "/ss")
|
|
20144
20706
|
return "/session";
|
|
20145
|
-
if (
|
|
20707
|
+
if (lowered === "/ws")
|
|
20146
20708
|
return "/workspace";
|
|
20147
|
-
if (
|
|
20709
|
+
if (lowered === "/pm")
|
|
20148
20710
|
return "/permission";
|
|
20149
|
-
if (
|
|
20711
|
+
if (lowered === "/stop")
|
|
20150
20712
|
return "/cancel";
|
|
20151
|
-
if (
|
|
20713
|
+
if (lowered === "/lt")
|
|
20152
20714
|
return "/later";
|
|
20153
|
-
return
|
|
20715
|
+
return lowered;
|
|
20154
20716
|
}
|
|
20155
20717
|
function isRecognizedCommand(command) {
|
|
20156
20718
|
return isKnownXacpxCommandPrefix(command);
|
|
@@ -20173,31 +20735,41 @@ function toNonInteractivePermission(value) {
|
|
|
20173
20735
|
function tokenizeCommand(input) {
|
|
20174
20736
|
const tokens = [];
|
|
20175
20737
|
let current = "";
|
|
20176
|
-
let
|
|
20738
|
+
let start2 = -1;
|
|
20739
|
+
let closingQuote = null;
|
|
20740
|
+
let offset = 0;
|
|
20177
20741
|
for (const char of input) {
|
|
20178
|
-
|
|
20179
|
-
|
|
20180
|
-
|
|
20742
|
+
const charStart = offset;
|
|
20743
|
+
offset += char.length;
|
|
20744
|
+
if (closingQuote) {
|
|
20745
|
+
if (char === closingQuote) {
|
|
20746
|
+
closingQuote = null;
|
|
20181
20747
|
} else {
|
|
20182
20748
|
current += char;
|
|
20183
20749
|
}
|
|
20184
20750
|
continue;
|
|
20185
20751
|
}
|
|
20186
|
-
|
|
20187
|
-
|
|
20752
|
+
const close = QUOTE_PAIRS[char];
|
|
20753
|
+
if (close) {
|
|
20754
|
+
if (start2 === -1)
|
|
20755
|
+
start2 = charStart;
|
|
20756
|
+
closingQuote = close;
|
|
20188
20757
|
continue;
|
|
20189
20758
|
}
|
|
20190
20759
|
if (/\s/.test(char)) {
|
|
20191
20760
|
if (current.length > 0) {
|
|
20192
|
-
tokens.push(current);
|
|
20761
|
+
tokens.push({ value: current, start: start2 });
|
|
20193
20762
|
current = "";
|
|
20194
20763
|
}
|
|
20764
|
+
start2 = -1;
|
|
20195
20765
|
continue;
|
|
20196
20766
|
}
|
|
20767
|
+
if (start2 === -1)
|
|
20768
|
+
start2 = charStart;
|
|
20197
20769
|
current += char;
|
|
20198
20770
|
}
|
|
20199
20771
|
if (current.length > 0) {
|
|
20200
|
-
tokens.push(current);
|
|
20772
|
+
tokens.push({ value: current, start: start2 });
|
|
20201
20773
|
}
|
|
20202
20774
|
return tokens;
|
|
20203
20775
|
}
|
|
@@ -20240,7 +20812,7 @@ function parseListFilterFlags(parts, validStatuses) {
|
|
|
20240
20812
|
}
|
|
20241
20813
|
return { filter, ok: true };
|
|
20242
20814
|
}
|
|
20243
|
-
function parseDelegateRequest(parts) {
|
|
20815
|
+
function parseDelegateRequest(parts, rawTail) {
|
|
20244
20816
|
const targetAgent = parts[1];
|
|
20245
20817
|
if (!targetAgent) {
|
|
20246
20818
|
return null;
|
|
@@ -20268,7 +20840,7 @@ function parseDelegateRequest(parts) {
|
|
|
20268
20840
|
}
|
|
20269
20841
|
break;
|
|
20270
20842
|
}
|
|
20271
|
-
const task =
|
|
20843
|
+
const task = rawTail(index);
|
|
20272
20844
|
if (task.trim().length === 0) {
|
|
20273
20845
|
return null;
|
|
20274
20846
|
}
|
|
@@ -20280,9 +20852,16 @@ function parseDelegateRequest(parts) {
|
|
|
20280
20852
|
task
|
|
20281
20853
|
};
|
|
20282
20854
|
}
|
|
20283
|
-
var TASK_STATUS_VALUES, GROUP_STATUS_VALUES;
|
|
20855
|
+
var QUOTE_PAIRS, TASK_STATUS_VALUES, GROUP_STATUS_VALUES;
|
|
20284
20856
|
var init_parse_command = __esm(() => {
|
|
20285
20857
|
init_command_list();
|
|
20858
|
+
QUOTE_PAIRS = {
|
|
20859
|
+
'"': '"',
|
|
20860
|
+
"'": "'",
|
|
20861
|
+
"“": "”",
|
|
20862
|
+
"‘": "’",
|
|
20863
|
+
""": """
|
|
20864
|
+
};
|
|
20286
20865
|
TASK_STATUS_VALUES = [
|
|
20287
20866
|
"pending",
|
|
20288
20867
|
"needs_confirmation",
|
|
@@ -20295,7 +20874,50 @@ var init_parse_command = __esm(() => {
|
|
|
20295
20874
|
});
|
|
20296
20875
|
|
|
20297
20876
|
// src/commands/command-policy.ts
|
|
20877
|
+
function isInternalScheduledTurn(metadata) {
|
|
20878
|
+
return Boolean(metadata?.scheduledSessionAlias || metadata?.scheduledSessionDescriptor);
|
|
20879
|
+
}
|
|
20880
|
+
function resolveChannelOwnerIds(config4, channel) {
|
|
20881
|
+
if (!config4 || !channel) {
|
|
20882
|
+
return;
|
|
20883
|
+
}
|
|
20884
|
+
let configured = false;
|
|
20885
|
+
const ids = new Set;
|
|
20886
|
+
if (config4.channel?.type === channel && config4.channel.ownerIds) {
|
|
20887
|
+
configured = true;
|
|
20888
|
+
for (const id of config4.channel.ownerIds)
|
|
20889
|
+
ids.add(id);
|
|
20890
|
+
}
|
|
20891
|
+
for (const entry of config4.channels ?? []) {
|
|
20892
|
+
if ((entry.type === channel || entry.id === channel) && entry.ownerIds) {
|
|
20893
|
+
configured = true;
|
|
20894
|
+
for (const id of entry.ownerIds)
|
|
20895
|
+
ids.add(id);
|
|
20896
|
+
}
|
|
20897
|
+
}
|
|
20898
|
+
return configured ? [...ids] : undefined;
|
|
20899
|
+
}
|
|
20900
|
+
function withEffectiveOwner(metadata, config4) {
|
|
20901
|
+
if (!metadata?.channel || isInternalScheduledTurn(metadata)) {
|
|
20902
|
+
return metadata;
|
|
20903
|
+
}
|
|
20904
|
+
const ownerIds = resolveChannelOwnerIds(config4, metadata.channel);
|
|
20905
|
+
if (ownerIds === undefined) {
|
|
20906
|
+
return metadata;
|
|
20907
|
+
}
|
|
20908
|
+
const isOwner = metadata.isOwner === true || typeof metadata.senderId === "string" && ownerIds.includes(metadata.senderId);
|
|
20909
|
+
return { ...metadata, isOwner };
|
|
20910
|
+
}
|
|
20298
20911
|
function authorizeCommandForChat(command, metadata) {
|
|
20912
|
+
if (metadata?.channel && !isInternalScheduledTurn(metadata) && metadata.chatType !== "direct" && metadata.chatType !== "group") {
|
|
20913
|
+
if (GROUP_PUBLIC_COMMAND_KINDS.has(command.kind)) {
|
|
20914
|
+
return { allowed: true };
|
|
20915
|
+
}
|
|
20916
|
+
return {
|
|
20917
|
+
allowed: false,
|
|
20918
|
+
reason: "chat-type-missing"
|
|
20919
|
+
};
|
|
20920
|
+
}
|
|
20299
20921
|
if (metadata?.chatType !== "group") {
|
|
20300
20922
|
return { allowed: true };
|
|
20301
20923
|
}
|
|
@@ -20310,7 +20932,14 @@ function authorizeCommandForChat(command, metadata) {
|
|
|
20310
20932
|
reason: "group-owner-required"
|
|
20311
20933
|
};
|
|
20312
20934
|
}
|
|
20313
|
-
function renderCommandAccessDenied(command) {
|
|
20935
|
+
function renderCommandAccessDenied(command, reason) {
|
|
20936
|
+
if (reason === "chat-type-missing") {
|
|
20937
|
+
return [
|
|
20938
|
+
`⚠️ ${renderCommandLabel(command)}${t().misc.commandAccessDeniedChatTypeMissingSuffix}`,
|
|
20939
|
+
t().misc.commandAccessDeniedChatTypeMissingHint
|
|
20940
|
+
].join(`
|
|
20941
|
+
`);
|
|
20942
|
+
}
|
|
20314
20943
|
return [
|
|
20315
20944
|
`⚠️ ${renderCommandLabel(command)}${t().misc.commandAccessDeniedSuffix}`,
|
|
20316
20945
|
t().misc.commandAccessDeniedHint
|
|
@@ -20436,13 +21065,18 @@ async function handlePermissionModeSet(context, mode) {
|
|
|
20436
21065
|
return { text: p.noWritableConfig };
|
|
20437
21066
|
}
|
|
20438
21067
|
const previous = cloneAppConfig(context.config);
|
|
21068
|
+
const previousRaw = await context.configStore.getRawValue(["transport", "permissionMode"]);
|
|
20439
21069
|
const updated = await context.configStore.updateTransport({
|
|
20440
21070
|
permissionMode: mode
|
|
20441
21071
|
});
|
|
20442
21072
|
try {
|
|
20443
21073
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20444
21074
|
} catch (error2) {
|
|
20445
|
-
|
|
21075
|
+
if (previousRaw.present) {
|
|
21076
|
+
await context.configStore.setRawValue(["transport", "permissionMode"], previousRaw.value);
|
|
21077
|
+
} else {
|
|
21078
|
+
await context.configStore.unsetRawValue(["transport", "permissionMode"]);
|
|
21079
|
+
}
|
|
20446
21080
|
context.replaceConfig(previous);
|
|
20447
21081
|
throw error2;
|
|
20448
21082
|
}
|
|
@@ -20458,13 +21092,18 @@ async function handlePermissionAutoSet(context, policy) {
|
|
|
20458
21092
|
return { text: p.noWritableConfig };
|
|
20459
21093
|
}
|
|
20460
21094
|
const previous = cloneAppConfig(context.config);
|
|
21095
|
+
const previousRaw = await context.configStore.getRawValue(["transport", "nonInteractivePermissions"]);
|
|
20461
21096
|
const updated = await context.configStore.updateTransport({
|
|
20462
21097
|
nonInteractivePermissions: policy
|
|
20463
21098
|
});
|
|
20464
21099
|
try {
|
|
20465
21100
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20466
21101
|
} catch (error2) {
|
|
20467
|
-
|
|
21102
|
+
if (previousRaw.present) {
|
|
21103
|
+
await context.configStore.setRawValue(["transport", "nonInteractivePermissions"], previousRaw.value);
|
|
21104
|
+
} else {
|
|
21105
|
+
await context.configStore.unsetRawValue(["transport", "nonInteractivePermissions"]);
|
|
21106
|
+
}
|
|
20468
21107
|
context.replaceConfig(previous);
|
|
20469
21108
|
throw error2;
|
|
20470
21109
|
}
|
|
@@ -20514,99 +21153,83 @@ async function handleConfigSet(context, path14, rawValue) {
|
|
|
20514
21153
|
if (!context.config || !context.configStore) {
|
|
20515
21154
|
return { text: c.noWritableConfig };
|
|
20516
21155
|
}
|
|
20517
|
-
const
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
if ("error" in result) {
|
|
20521
|
-
return { text: result.error };
|
|
21156
|
+
const plan = planSupportedConfigUpdate(context.config, path14, rawValue);
|
|
21157
|
+
if ("error" in plan) {
|
|
21158
|
+
return { text: plan.error };
|
|
20522
21159
|
}
|
|
20523
|
-
|
|
21160
|
+
const previousConfig = cloneAppConfig(context.config);
|
|
21161
|
+
const previousRaw = await context.configStore.getRawValue(plan.rawPath);
|
|
21162
|
+
const updated = await context.configStore.setRawValue(plan.rawPath, plan.value);
|
|
20524
21163
|
if (path14 === "transport.permissionMode" || path14 === "transport.nonInteractivePermissions" || path14 === "transport.permissionPolicy") {
|
|
20525
21164
|
try {
|
|
20526
21165
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20527
21166
|
} catch (error2) {
|
|
20528
|
-
|
|
20529
|
-
|
|
21167
|
+
if (previousRaw.present) {
|
|
21168
|
+
await context.configStore.setRawValue(plan.rawPath, previousRaw.value);
|
|
21169
|
+
} else {
|
|
21170
|
+
await context.configStore.unsetRawValue(plan.rawPath);
|
|
21171
|
+
}
|
|
21172
|
+
context.replaceConfig(previousConfig);
|
|
20530
21173
|
throw error2;
|
|
20531
21174
|
}
|
|
20532
21175
|
}
|
|
20533
21176
|
context.replaceConfig(updated);
|
|
20534
|
-
return { text: c.updated(path14,
|
|
21177
|
+
return { text: c.updated(path14, plan.renderedValue) };
|
|
20535
21178
|
}
|
|
20536
|
-
function
|
|
21179
|
+
function planSupportedConfigUpdate(config4, path14, rawValue) {
|
|
20537
21180
|
const c = t().config;
|
|
20538
21181
|
switch (path14) {
|
|
20539
21182
|
case "language": {
|
|
20540
21183
|
if (!isLocale(rawValue))
|
|
20541
21184
|
return { error: c.languageInvalid };
|
|
20542
|
-
|
|
20543
|
-
return { renderedValue: rawValue };
|
|
21185
|
+
return { rawPath: ["language"], value: rawValue, renderedValue: rawValue };
|
|
20544
21186
|
}
|
|
20545
21187
|
case "transport.type": {
|
|
20546
21188
|
const parsed = parseEnum(rawValue, ["acpx-cli", "acpx-bridge"]);
|
|
20547
21189
|
if (!parsed)
|
|
20548
21190
|
return { error: c.transportTypeInvalid };
|
|
20549
|
-
|
|
20550
|
-
return { renderedValue: parsed };
|
|
21191
|
+
return { rawPath: ["transport", "type"], value: parsed, renderedValue: parsed };
|
|
20551
21192
|
}
|
|
20552
21193
|
case "transport.command":
|
|
20553
21194
|
if (!rawValue.trim())
|
|
20554
21195
|
return { error: c.transportCommandEmpty };
|
|
20555
|
-
|
|
20556
|
-
return { renderedValue: rawValue };
|
|
21196
|
+
return { rawPath: ["transport", "command"], value: rawValue, renderedValue: rawValue };
|
|
20557
21197
|
case "transport.sessionInitTimeoutMs": {
|
|
20558
21198
|
const parsed = parsePositiveNumber(rawValue, "transport.sessionInitTimeoutMs");
|
|
20559
21199
|
if ("error" in parsed)
|
|
20560
21200
|
return parsed;
|
|
20561
|
-
|
|
20562
|
-
return { renderedValue: String(parsed.value) };
|
|
21201
|
+
return { rawPath: ["transport", "sessionInitTimeoutMs"], value: parsed.value, renderedValue: String(parsed.value) };
|
|
20563
21202
|
}
|
|
20564
21203
|
case "transport.permissionMode": {
|
|
20565
21204
|
const parsed = parseEnum(rawValue, ["approve-all", "approve-reads", "deny-all"]);
|
|
20566
21205
|
if (!parsed)
|
|
20567
21206
|
return { error: c.transportPermissionModeInvalid };
|
|
20568
|
-
|
|
20569
|
-
return { renderedValue: parsed };
|
|
21207
|
+
return { rawPath: ["transport", "permissionMode"], value: parsed, renderedValue: parsed };
|
|
20570
21208
|
}
|
|
20571
21209
|
case "transport.nonInteractivePermissions": {
|
|
20572
21210
|
const parsed = parseEnum(rawValue, ["deny", "fail"]);
|
|
20573
21211
|
if (!parsed)
|
|
20574
21212
|
return { error: c.transportNonInteractiveInvalid };
|
|
20575
|
-
|
|
20576
|
-
return { renderedValue: parsed };
|
|
21213
|
+
return { rawPath: ["transport", "nonInteractivePermissions"], value: parsed, renderedValue: parsed };
|
|
20577
21214
|
}
|
|
20578
21215
|
case "transport.permissionPolicy":
|
|
20579
21216
|
if (!rawValue.trim())
|
|
20580
21217
|
return { error: c.transportPermissionPolicyEmpty };
|
|
20581
|
-
|
|
20582
|
-
return { renderedValue: rawValue };
|
|
21218
|
+
return { rawPath: ["transport", "permissionPolicy"], value: rawValue, renderedValue: rawValue };
|
|
20583
21219
|
case "logging.level": {
|
|
20584
21220
|
const parsed = parseEnum(rawValue, ["error", "info", "debug"]);
|
|
20585
21221
|
if (!parsed)
|
|
20586
21222
|
return { error: c.loggingLevelInvalid };
|
|
20587
|
-
|
|
20588
|
-
return { renderedValue: parsed };
|
|
20589
|
-
}
|
|
20590
|
-
case "logging.maxSizeBytes": {
|
|
20591
|
-
const parsed = parsePositiveNumber(rawValue, "logging.maxSizeBytes");
|
|
20592
|
-
if ("error" in parsed)
|
|
20593
|
-
return parsed;
|
|
20594
|
-
config4.logging.maxSizeBytes = parsed.value;
|
|
20595
|
-
return { renderedValue: String(parsed.value) };
|
|
20596
|
-
}
|
|
20597
|
-
case "logging.maxFiles": {
|
|
20598
|
-
const parsed = parsePositiveNumber(rawValue, "logging.maxFiles");
|
|
20599
|
-
if ("error" in parsed)
|
|
20600
|
-
return parsed;
|
|
20601
|
-
config4.logging.maxFiles = parsed.value;
|
|
20602
|
-
return { renderedValue: String(parsed.value) };
|
|
21223
|
+
return { rawPath: ["logging", "level"], value: parsed, renderedValue: parsed };
|
|
20603
21224
|
}
|
|
21225
|
+
case "logging.maxSizeBytes":
|
|
21226
|
+
case "logging.maxFiles":
|
|
20604
21227
|
case "logging.retentionDays": {
|
|
20605
|
-
const
|
|
21228
|
+
const field = path14.slice("logging.".length);
|
|
21229
|
+
const parsed = parsePositiveNumber(rawValue, path14);
|
|
20606
21230
|
if ("error" in parsed)
|
|
20607
21231
|
return parsed;
|
|
20608
|
-
|
|
20609
|
-
return { renderedValue: String(parsed.value) };
|
|
21232
|
+
return { rawPath: ["logging", field], value: parsed.value, renderedValue: String(parsed.value) };
|
|
20610
21233
|
}
|
|
20611
21234
|
case "channel.type":
|
|
20612
21235
|
return { error: c.channelTypeDisabled };
|
|
@@ -20614,15 +21237,15 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20614
21237
|
const parsed = parseEnum(rawValue, ["stream", "final", "verbose"]);
|
|
20615
21238
|
if (!parsed)
|
|
20616
21239
|
return { error: c.channelReplyModeInvalid };
|
|
20617
|
-
|
|
20618
|
-
return { renderedValue: parsed };
|
|
21240
|
+
return { rawPath: ["channel", "replyMode"], value: parsed, renderedValue: parsed };
|
|
20619
21241
|
}
|
|
20620
21242
|
case "wechat.replyMode": {
|
|
20621
21243
|
const parsed = parseEnum(rawValue, ["stream", "final", "verbose"]);
|
|
20622
21244
|
if (!parsed)
|
|
20623
21245
|
return { error: c.wechatReplyModeInvalid };
|
|
20624
|
-
config4.channel.replyMode = parsed;
|
|
20625
21246
|
return {
|
|
21247
|
+
rawPath: ["channel", "replyMode"],
|
|
21248
|
+
value: parsed,
|
|
20626
21249
|
renderedValue: c.wechatReplyModeMapped(parsed)
|
|
20627
21250
|
};
|
|
20628
21251
|
}
|
|
@@ -20630,42 +21253,30 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20630
21253
|
const agentMatch = path14.match(/^agents\.([^.]+)\.(driver|command)$/);
|
|
20631
21254
|
if (agentMatch) {
|
|
20632
21255
|
const [, name, field] = agentMatch;
|
|
20633
|
-
if (!name || !field) {
|
|
21256
|
+
if (!name || !field || isPrototypePollutingKey(name)) {
|
|
20634
21257
|
return { error: c.pathNotSupported(path14) };
|
|
20635
21258
|
}
|
|
20636
|
-
|
|
20637
|
-
if (!agent3) {
|
|
21259
|
+
if (!Object.hasOwn(config4.agents, name)) {
|
|
20638
21260
|
return { error: c.agentNotFound(name) };
|
|
20639
21261
|
}
|
|
20640
21262
|
if (!rawValue.trim()) {
|
|
20641
21263
|
return { error: c.fieldEmpty(path14) };
|
|
20642
21264
|
}
|
|
20643
|
-
|
|
20644
|
-
agent3.driver = rawValue;
|
|
20645
|
-
} else {
|
|
20646
|
-
agent3.command = rawValue;
|
|
20647
|
-
}
|
|
20648
|
-
return { renderedValue: rawValue };
|
|
21265
|
+
return { rawPath: ["agents", name, field], value: rawValue, renderedValue: rawValue };
|
|
20649
21266
|
}
|
|
20650
21267
|
const workspaceMatch = path14.match(/^workspaces\.([^.]+)\.(cwd|description)$/);
|
|
20651
21268
|
if (workspaceMatch) {
|
|
20652
21269
|
const [, name, field] = workspaceMatch;
|
|
20653
|
-
if (!name || !field) {
|
|
21270
|
+
if (!name || !field || isPrototypePollutingKey(name)) {
|
|
20654
21271
|
return { error: c.pathNotSupported(path14) };
|
|
20655
21272
|
}
|
|
20656
|
-
|
|
20657
|
-
if (!workspace3) {
|
|
21273
|
+
if (!Object.hasOwn(config4.workspaces, name)) {
|
|
20658
21274
|
return { error: c.workspaceNotFound(name) };
|
|
20659
21275
|
}
|
|
20660
21276
|
if (!rawValue.trim()) {
|
|
20661
21277
|
return { error: c.fieldEmpty(path14) };
|
|
20662
21278
|
}
|
|
20663
|
-
|
|
20664
|
-
workspace3.cwd = rawValue;
|
|
20665
|
-
} else {
|
|
20666
|
-
workspace3.description = rawValue;
|
|
20667
|
-
}
|
|
20668
|
-
return { renderedValue: rawValue };
|
|
21279
|
+
return { rawPath: ["workspaces", name, field], value: rawValue, renderedValue: rawValue };
|
|
20669
21280
|
}
|
|
20670
21281
|
const channelMatch = path14.match(/^channels\.([^.]+)\.replyMode$/);
|
|
20671
21282
|
if (channelMatch) {
|
|
@@ -20681,11 +21292,21 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20681
21292
|
if (!parsed) {
|
|
20682
21293
|
return { error: c.channelRuntimeReplyModeInvalid(id) };
|
|
20683
21294
|
}
|
|
20684
|
-
|
|
20685
|
-
|
|
21295
|
+
return {
|
|
21296
|
+
rawPath: [
|
|
21297
|
+
"channels",
|
|
21298
|
+
{ id, createWith: { id: channel.id, type: channel.type, enabled: channel.enabled } },
|
|
21299
|
+
"replyMode"
|
|
21300
|
+
],
|
|
21301
|
+
value: parsed,
|
|
21302
|
+
renderedValue: parsed
|
|
21303
|
+
};
|
|
20686
21304
|
}
|
|
20687
21305
|
return { error: c.pathNotSupported(path14) };
|
|
20688
21306
|
}
|
|
21307
|
+
function isPrototypePollutingKey(key) {
|
|
21308
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
21309
|
+
}
|
|
20689
21310
|
function parseEnum(value, allowed) {
|
|
20690
21311
|
return allowed.includes(value) ? value : null;
|
|
20691
21312
|
}
|
|
@@ -21227,6 +21848,10 @@ async function handleSessions(context, chatKey) {
|
|
|
21227
21848
|
async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
|
|
21228
21849
|
const channelId = getChannelIdFromChatKey(chatKey);
|
|
21229
21850
|
const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
|
|
21851
|
+
const existing = context.sessions.getResolvedSessionByInternalAlias(internalAlias);
|
|
21852
|
+
if (existing) {
|
|
21853
|
+
return { text: t().session.sessionAlreadyExists(alias, existing.agent, existing.workspace) };
|
|
21854
|
+
}
|
|
21230
21855
|
const session3 = context.lifecycle.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
|
|
21231
21856
|
const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session3.transportSession);
|
|
21232
21857
|
try {
|
|
@@ -21497,7 +22122,9 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
21497
22122
|
}
|
|
21498
22123
|
}
|
|
21499
22124
|
const sharedAliasCount = context.sessions.countAliasesSharingTransport(session3.transportSession, internalAlias);
|
|
22125
|
+
const wasCurrentInThisChat = context.sessions.peekCurrentSessionAlias(chatKey) === internalAlias;
|
|
21500
22126
|
const { wasActive } = await context.sessions.removeSession(internalAlias);
|
|
22127
|
+
const promotedAlias = wasCurrentInThisChat ? context.sessions.peekCurrentSessionAlias(chatKey) || undefined : undefined;
|
|
21501
22128
|
let orchestrationPurgeWarning;
|
|
21502
22129
|
if (context.orchestration) {
|
|
21503
22130
|
try {
|
|
@@ -21535,7 +22162,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
21535
22162
|
const s = t().session;
|
|
21536
22163
|
const lines = [s.sessionRemoved(alias)];
|
|
21537
22164
|
if (wasActive) {
|
|
21538
|
-
lines.push(s.sessionRemovedWasActive);
|
|
22165
|
+
lines.push(promotedAlias ? s.sessionRemovedWasActivePromoted(toDisplaySessionAlias(promotedAlias)) : s.sessionRemovedWasActive);
|
|
21539
22166
|
}
|
|
21540
22167
|
if (!shouldTeardownTransport) {
|
|
21541
22168
|
lines.push(s.sessionTransportShared(session3.transportSession, sharedAliasCount));
|
|
@@ -22363,7 +22990,7 @@ async function handleWorkspaceCreate(context, workspaceName, cwd, options = {})
|
|
|
22363
22990
|
name = allocateWorkspaceName(base, context.config.workspaces);
|
|
22364
22991
|
notice = w.nameSanitized(workspaceName, name);
|
|
22365
22992
|
}
|
|
22366
|
-
const updated = await context.configStore.upsertWorkspace(name,
|
|
22993
|
+
const updated = await context.configStore.upsertWorkspace(name, cwd);
|
|
22367
22994
|
context.replaceConfig(updated);
|
|
22368
22995
|
const savedLine = w.saved(name);
|
|
22369
22996
|
return { text: notice ? `${notice}
|
|
@@ -22479,10 +23106,10 @@ function validateResult(executeAt, messageStartIndex, tokens, now, pastTodayValu
|
|
|
22479
23106
|
if (tokens.slice(messageStartIndex).join(" ").trim().length === 0)
|
|
22480
23107
|
return { ok: false, code: "missing_message" };
|
|
22481
23108
|
const delta = executeAt.getTime() - now.getTime();
|
|
23109
|
+
if (isNaN(delta) || delta > LATER_MAX_DELAY_MS)
|
|
23110
|
+
return { ok: false, code: "out_of_range" };
|
|
22482
23111
|
if (delta < LATER_MIN_DELAY_MS)
|
|
22483
23112
|
return { ok: false, code: "too_soon" };
|
|
22484
|
-
if (delta > LATER_MAX_DELAY_MS)
|
|
22485
|
-
return { ok: false, code: "out_of_range" };
|
|
22486
23113
|
return { ok: true, executeAt, messageStartIndex };
|
|
22487
23114
|
}
|
|
22488
23115
|
var WEEKDAYS, ZH_MIN = "分钟", ZH_HOUR = "小时", ZH_DAY_UNIT = "天", ZH_TODAY = "今天", ZH_TOMORROW = "明天", ZH_DAY_AFTER = "后天", ZH_AFTER = "后", ZH_RELATIVE_RE;
|
|
@@ -22558,16 +23185,17 @@ function laterHelp() {
|
|
|
22558
23185
|
function handleLaterHelp() {
|
|
22559
23186
|
return { text: renderLaterHelp() };
|
|
22560
23187
|
}
|
|
22561
|
-
async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
23188
|
+
async function handleLaterCreate(tokens, tails, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
22562
23189
|
const l = t().later;
|
|
22563
|
-
let
|
|
23190
|
+
let restStart = 0;
|
|
22564
23191
|
const seenFlags = new Set;
|
|
22565
23192
|
let flagMode;
|
|
22566
|
-
while (
|
|
22567
|
-
seenFlags.add(
|
|
22568
|
-
flagMode =
|
|
22569
|
-
|
|
23193
|
+
while (restStart < tokens.length && (tokens[restStart] === "--bind" || tokens[restStart] === "--temp")) {
|
|
23194
|
+
seenFlags.add(tokens[restStart] ?? "");
|
|
23195
|
+
flagMode = tokens[restStart] === "--bind" ? "bound" : "temp";
|
|
23196
|
+
restStart += 1;
|
|
22570
23197
|
}
|
|
23198
|
+
const rest = tokens.slice(restStart);
|
|
22571
23199
|
if (seenFlags.size > 1) {
|
|
22572
23200
|
return { text: l.bindAndTempMutuallyExclusive };
|
|
22573
23201
|
}
|
|
@@ -22588,7 +23216,7 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, def
|
|
|
22588
23216
|
if (!result.ok) {
|
|
22589
23217
|
return { text: renderTimeParseError(result.code, result.value) };
|
|
22590
23218
|
}
|
|
22591
|
-
const message =
|
|
23219
|
+
const message = (tails[restStart + result.messageStartIndex] ?? "").trim();
|
|
22592
23220
|
if (message.startsWith("/")) {
|
|
22593
23221
|
return {
|
|
22594
23222
|
text: [
|
|
@@ -22612,12 +23240,12 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, def
|
|
|
22612
23240
|
});
|
|
22613
23241
|
return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSession.alias)) };
|
|
22614
23242
|
}
|
|
22615
|
-
function handleLaterList(scheduled) {
|
|
22616
|
-
const tasks = scheduled.listPending();
|
|
23243
|
+
function handleLaterList(scheduled, chatKey) {
|
|
23244
|
+
const tasks = scheduled.listPending(chatKey);
|
|
22617
23245
|
return { text: renderLaterList(tasks, (alias) => toDisplaySessionAlias(alias)) };
|
|
22618
23246
|
}
|
|
22619
|
-
async function handleLaterCancel(id, scheduled) {
|
|
22620
|
-
const ok = await scheduled.cancelPending(id);
|
|
23247
|
+
async function handleLaterCancel(id, scheduled, chatKey) {
|
|
23248
|
+
const ok = await scheduled.cancelPending(id, chatKey);
|
|
22621
23249
|
const displayId = id.replace(/^#/, "").toLowerCase();
|
|
22622
23250
|
if (ok) {
|
|
22623
23251
|
return { text: t().later.cancelSuccess(displayId) };
|
|
@@ -23454,7 +24082,7 @@ var init_session_recovery_handler = __esm(() => {
|
|
|
23454
24082
|
// src/recovery/auto-install-optional-dep.ts
|
|
23455
24083
|
import { spawn as spawn5 } from "node:child_process";
|
|
23456
24084
|
import { createWriteStream } from "node:fs";
|
|
23457
|
-
import { mkdir as
|
|
24085
|
+
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
23458
24086
|
import { homedir as homedir6 } from "node:os";
|
|
23459
24087
|
import { join as join14 } from "node:path";
|
|
23460
24088
|
async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
|
|
@@ -23572,7 +24200,7 @@ ${err.message}`, reason: "spawn" });
|
|
|
23572
24200
|
});
|
|
23573
24201
|
}, defaultLogSink = async () => {
|
|
23574
24202
|
const dir = join14(coreHomeDir(homedir6()), "logs");
|
|
23575
|
-
await
|
|
24203
|
+
await mkdir8(dir, { recursive: true });
|
|
23576
24204
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
|
|
23577
24205
|
const path14 = join14(dir, `auto-install-${timestamp}.log`);
|
|
23578
24206
|
const stream = createWriteStream(path14, { flags: "a" });
|
|
@@ -23601,7 +24229,7 @@ import { spawn as spawn6 } from "node:child_process";
|
|
|
23601
24229
|
import { createRequire as createRequire3 } from "node:module";
|
|
23602
24230
|
import { access as access3 } from "node:fs/promises";
|
|
23603
24231
|
import { homedir as homedir7 } from "node:os";
|
|
23604
|
-
import { dirname as
|
|
24232
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
23605
24233
|
function deriveParentPackageName(platformPackage) {
|
|
23606
24234
|
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
23607
24235
|
}
|
|
@@ -23674,7 +24302,7 @@ function defaultResolveFromCwd(name, cwd) {
|
|
|
23674
24302
|
const pkgJson = require2.resolve(`${name}/package.json`, {
|
|
23675
24303
|
paths: [cwd, ...require2.resolve.paths(name) ?? []]
|
|
23676
24304
|
});
|
|
23677
|
-
return
|
|
24305
|
+
return dirname10(pkgJson);
|
|
23678
24306
|
} catch {
|
|
23679
24307
|
return null;
|
|
23680
24308
|
}
|
|
@@ -23809,11 +24437,11 @@ async function handleSessionResetCommand(context, ops, chatKey) {
|
|
|
23809
24437
|
chatKey,
|
|
23810
24438
|
native: wasNative && Boolean(freshAgentSessionId)
|
|
23811
24439
|
});
|
|
23812
|
-
if (
|
|
24440
|
+
if (context.transport.removeSession && context.sessions.countAliasesSharingTransport(previous.transportSession) === 0) {
|
|
23813
24441
|
try {
|
|
23814
24442
|
await context.transport.removeSession(previous);
|
|
23815
24443
|
} catch (error2) {
|
|
23816
|
-
await context.logger.info("session.reset.close_previous_failed", "failed to close previous
|
|
24444
|
+
await context.logger.info("session.reset.close_previous_failed", "failed to close previous session after reset", {
|
|
23817
24445
|
transportSession: previous.transportSession,
|
|
23818
24446
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
23819
24447
|
});
|
|
@@ -23875,9 +24503,20 @@ class CommandRouter {
|
|
|
23875
24503
|
chatKey,
|
|
23876
24504
|
kind: command.kind
|
|
23877
24505
|
});
|
|
24506
|
+
await this.refreshConfigFromStore();
|
|
24507
|
+
perfSpan?.mark("router.config_refreshed");
|
|
24508
|
+
metadata = withEffectiveOwner(metadata, this.config);
|
|
23878
24509
|
const access4 = authorizeCommandForChat(command, metadata);
|
|
23879
24510
|
perfSpan?.mark("router.authorized", { decision: access4.allowed ? "allow" : "deny" });
|
|
23880
24511
|
if (!access4.allowed) {
|
|
24512
|
+
if (access4.reason === "chat-type-missing") {
|
|
24513
|
+
await this.logger.error("channel.chat_type_missing", "channel turn carried no chatType; denying privileged command (channel metadata contract violation)", {
|
|
24514
|
+
chatKey,
|
|
24515
|
+
kind: command.kind,
|
|
24516
|
+
channel: metadata?.channel,
|
|
24517
|
+
senderId: metadata?.senderId
|
|
24518
|
+
});
|
|
24519
|
+
}
|
|
23881
24520
|
await this.logger.info("command.blocked", "blocked command by chat policy", {
|
|
23882
24521
|
chatKey,
|
|
23883
24522
|
kind: command.kind,
|
|
@@ -23885,10 +24524,8 @@ class CommandRouter {
|
|
|
23885
24524
|
channel: metadata?.channel,
|
|
23886
24525
|
senderId: metadata?.senderId
|
|
23887
24526
|
});
|
|
23888
|
-
return { text: renderCommandAccessDenied(command) };
|
|
24527
|
+
return { text: renderCommandAccessDenied(command, access4.reason) };
|
|
23889
24528
|
}
|
|
23890
|
-
await this.refreshConfigFromStore();
|
|
23891
|
-
perfSpan?.mark("router.config_refreshed");
|
|
23892
24529
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
23893
24530
|
switch (command.kind) {
|
|
23894
24531
|
case "invalid":
|
|
@@ -23990,7 +24627,7 @@ class CommandRouter {
|
|
|
23990
24627
|
case "later.list":
|
|
23991
24628
|
if (!this.scheduled)
|
|
23992
24629
|
return { text: t().later.serviceNotEnabled };
|
|
23993
|
-
return handleLaterList(this.scheduled);
|
|
24630
|
+
return handleLaterList(this.scheduled, chatKey);
|
|
23994
24631
|
case "later.create": {
|
|
23995
24632
|
if (!this.scheduled)
|
|
23996
24633
|
return { text: t().later.serviceNotEnabled };
|
|
@@ -23998,12 +24635,12 @@ class CommandRouter {
|
|
|
23998
24635
|
return { text: renderLaterUnsupportedChannel() };
|
|
23999
24636
|
}
|
|
24000
24637
|
const currentSession = await this.sessions.getCurrentSession(chatKey);
|
|
24001
|
-
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);
|
|
24638
|
+
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);
|
|
24002
24639
|
}
|
|
24003
24640
|
case "later.cancel":
|
|
24004
24641
|
if (!this.scheduled)
|
|
24005
24642
|
return { text: t().later.serviceNotEnabled };
|
|
24006
|
-
return await handleLaterCancel(command.id, this.scheduled);
|
|
24643
|
+
return await handleLaterCancel(command.id, this.scheduled, chatKey);
|
|
24007
24644
|
case "prompt": {
|
|
24008
24645
|
const sessionContext = this.createSessionHandlerContext(undefined, perfSpan);
|
|
24009
24646
|
if (metadata?.scheduledSessionDescriptor) {
|
|
@@ -24507,7 +25144,7 @@ var init_console_agent = __esm(() => {
|
|
|
24507
25144
|
});
|
|
24508
25145
|
|
|
24509
25146
|
// src/orchestration/orchestration-server.ts
|
|
24510
|
-
import { rm as rm8 } from "node:fs/promises";
|
|
25147
|
+
import { chmod as chmod4, rm as rm8 } from "node:fs/promises";
|
|
24511
25148
|
import { createServer } from "node:net";
|
|
24512
25149
|
|
|
24513
25150
|
class OrchestrationServer {
|
|
@@ -24554,6 +25191,7 @@ class OrchestrationServer {
|
|
|
24554
25191
|
});
|
|
24555
25192
|
});
|
|
24556
25193
|
await this.listenWithUnixSocketRecovery();
|
|
25194
|
+
await this.hardenUnixSocketPermissions();
|
|
24557
25195
|
this.started = true;
|
|
24558
25196
|
}
|
|
24559
25197
|
async stop() {
|
|
@@ -24742,17 +25380,19 @@ class OrchestrationServer {
|
|
|
24742
25380
|
return task;
|
|
24743
25381
|
}
|
|
24744
25382
|
parseRequestDelegateRpcInput(params) {
|
|
24745
|
-
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId"], "params");
|
|
25383
|
+
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId", "parallel"], "params");
|
|
24746
25384
|
const cwd = requireOptionalString(params, "cwd");
|
|
24747
25385
|
const role = requireOptionalString(params, "role");
|
|
24748
25386
|
const groupId = requireOptionalString(params, "groupId");
|
|
25387
|
+
const parallel = requireOptionalBoolean(params, "parallel");
|
|
24749
25388
|
return {
|
|
24750
25389
|
sourceHandle: requireString(params, "sourceHandle"),
|
|
24751
25390
|
targetAgent: requireString(params, "targetAgent"),
|
|
24752
25391
|
task: requireString(params, "task"),
|
|
24753
25392
|
...cwd !== undefined ? { cwd } : {},
|
|
24754
25393
|
...role !== undefined ? { role } : {},
|
|
24755
|
-
...groupId !== undefined ? { groupId } : {}
|
|
25394
|
+
...groupId !== undefined ? { groupId } : {},
|
|
25395
|
+
...parallel !== undefined ? { parallel } : {}
|
|
24756
25396
|
};
|
|
24757
25397
|
}
|
|
24758
25398
|
parseTaskListFilter(params) {
|
|
@@ -24835,6 +25475,17 @@ class OrchestrationServer {
|
|
|
24835
25475
|
whatIsNeeded: requireString(params, "whatIsNeeded")
|
|
24836
25476
|
};
|
|
24837
25477
|
}
|
|
25478
|
+
async hardenUnixSocketPermissions() {
|
|
25479
|
+
if (this.endpoint.kind !== "unix") {
|
|
25480
|
+
return;
|
|
25481
|
+
}
|
|
25482
|
+
const chmodFile = this.deps.chmodFile ?? chmod4;
|
|
25483
|
+
try {
|
|
25484
|
+
await chmodFile(this.endpoint.path, 384);
|
|
25485
|
+
} catch (error2) {
|
|
25486
|
+
this.deps.onSocketHardenError?.(error2);
|
|
25487
|
+
}
|
|
25488
|
+
}
|
|
24838
25489
|
async cleanupEndpoint() {
|
|
24839
25490
|
if (this.endpoint.kind !== "unix") {
|
|
24840
25491
|
return;
|
|
@@ -28316,18 +28967,39 @@ class ScheduledTaskScheduler {
|
|
|
28316
28967
|
return;
|
|
28317
28968
|
this.ticking = true;
|
|
28318
28969
|
try {
|
|
28319
|
-
|
|
28970
|
+
let dueTasks;
|
|
28971
|
+
try {
|
|
28972
|
+
dueTasks = await this.service.claimDueTasks();
|
|
28973
|
+
} catch (claimError) {
|
|
28974
|
+
await this.logger?.error("scheduled.claim.failed", "claimDueTasks threw; skipping tick", { message: claimError instanceof Error ? claimError.message : String(claimError) });
|
|
28975
|
+
return;
|
|
28976
|
+
}
|
|
28320
28977
|
for (const task of dueTasks) {
|
|
28321
28978
|
try {
|
|
28322
28979
|
await this.dispatchWithTimeout(task);
|
|
28323
|
-
await this.service.markExecuted(task.id);
|
|
28324
28980
|
} catch (error2) {
|
|
28325
28981
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
28326
28982
|
await this.logger?.error("scheduled.dispatch.failed", "failed to dispatch scheduled task", {
|
|
28327
28983
|
taskId: task.id,
|
|
28328
28984
|
message
|
|
28329
28985
|
});
|
|
28330
|
-
|
|
28986
|
+
try {
|
|
28987
|
+
await this.service.markFailed(task.id, error2);
|
|
28988
|
+
} catch (markError) {
|
|
28989
|
+
await this.logger?.error("scheduled.dispatch.mark_failed", "markFailed threw; task state may be stale", {
|
|
28990
|
+
taskId: task.id,
|
|
28991
|
+
message: markError instanceof Error ? markError.message : String(markError)
|
|
28992
|
+
});
|
|
28993
|
+
}
|
|
28994
|
+
continue;
|
|
28995
|
+
}
|
|
28996
|
+
try {
|
|
28997
|
+
await this.service.markExecuted(task.id);
|
|
28998
|
+
} catch (markError) {
|
|
28999
|
+
await this.logger?.error("scheduled.dispatch.mark_executed_failed", "markExecuted threw after a successful dispatch; leaving task state for startup reconciliation", {
|
|
29000
|
+
taskId: task.id,
|
|
29001
|
+
message: markError instanceof Error ? markError.message : String(markError)
|
|
29002
|
+
});
|
|
28331
29003
|
}
|
|
28332
29004
|
}
|
|
28333
29005
|
} finally {
|
|
@@ -28507,12 +29179,12 @@ var init_scheduled_route_create = __esm(() => {
|
|
|
28507
29179
|
|
|
28508
29180
|
// src/scheduled/scheduled-route-manage.ts
|
|
28509
29181
|
async function listScheduledTasksFromRoute(input, deps) {
|
|
28510
|
-
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
28511
|
-
return deps.scheduled.listPending();
|
|
29182
|
+
const route = resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
29183
|
+
return deps.scheduled.listPending(route.chatKey);
|
|
28512
29184
|
}
|
|
28513
29185
|
async function cancelScheduledTaskFromRoute(input, deps) {
|
|
28514
|
-
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
28515
|
-
const cancelled = await deps.scheduled.cancelPending(input.id);
|
|
29186
|
+
const route = resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
29187
|
+
const cancelled = await deps.scheduled.cancelPending(input.id, route.chatKey);
|
|
28516
29188
|
return { id: normalizeId(input.id), cancelled };
|
|
28517
29189
|
}
|
|
28518
29190
|
function resolveOwnedCoordinatorRoute(coordinatorSession, state, label) {
|
|
@@ -28557,25 +29229,37 @@ class SessionService {
|
|
|
28557
29229
|
const seen = new Set;
|
|
28558
29230
|
const resolved = [];
|
|
28559
29231
|
for (const session3 of Object.values(this.state.sessions)) {
|
|
28560
|
-
|
|
29232
|
+
let candidate;
|
|
29233
|
+
try {
|
|
29234
|
+
candidate = this.toResolvedSession(session3);
|
|
29235
|
+
} catch {
|
|
28561
29236
|
continue;
|
|
28562
29237
|
}
|
|
28563
|
-
|
|
28564
|
-
|
|
28565
|
-
|
|
28566
|
-
|
|
29238
|
+
const key = JSON.stringify([
|
|
29239
|
+
candidate.agent,
|
|
29240
|
+
candidate.agentCommand ?? null,
|
|
29241
|
+
candidate.cwd,
|
|
29242
|
+
candidate.transportSession
|
|
29243
|
+
]);
|
|
29244
|
+
if (seen.has(key)) {
|
|
29245
|
+
continue;
|
|
29246
|
+
}
|
|
29247
|
+
seen.add(key);
|
|
29248
|
+
resolved.push(candidate);
|
|
28567
29249
|
}
|
|
28568
29250
|
return resolved;
|
|
28569
29251
|
}
|
|
28570
29252
|
resolveSession(alias, agent3, workspace3, transportSession) {
|
|
28571
29253
|
this.validateSession(alias, agent3, workspace3);
|
|
29254
|
+
const existing = this.state.sessions[alias];
|
|
29255
|
+
const sameAgentExisting = existing && existing.agent === agent3 ? existing : undefined;
|
|
28572
29256
|
return this.toResolvedSession({
|
|
28573
29257
|
alias,
|
|
28574
29258
|
agent: agent3,
|
|
28575
29259
|
workspace: workspace3,
|
|
28576
29260
|
transport_session: transportSession,
|
|
28577
|
-
transport_agent_command:
|
|
28578
|
-
created_at:
|
|
29261
|
+
transport_agent_command: sameAgentExisting?.transport_agent_command,
|
|
29262
|
+
created_at: existing?.created_at ?? new Date().toISOString(),
|
|
28579
29263
|
last_used_at: new Date().toISOString()
|
|
28580
29264
|
});
|
|
28581
29265
|
}
|
|
@@ -28647,10 +29331,13 @@ class SessionService {
|
|
|
28647
29331
|
const previousCurrent = prevCtx?.current_session;
|
|
28648
29332
|
const carriedPrevious = previousCurrent && previousCurrent !== internalAlias ? previousCurrent : prevCtx?.previous_session;
|
|
28649
29333
|
session3.last_used_at = new Date().toISOString();
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
}
|
|
29334
|
+
const nextCtx = { ...prevCtx, current_session: internalAlias };
|
|
29335
|
+
if (carriedPrevious) {
|
|
29336
|
+
nextCtx.previous_session = carriedPrevious;
|
|
29337
|
+
} else {
|
|
29338
|
+
delete nextCtx.previous_session;
|
|
29339
|
+
}
|
|
29340
|
+
this.state.chat_contexts[chatKey] = nextCtx;
|
|
28654
29341
|
await this.persist();
|
|
28655
29342
|
return {
|
|
28656
29343
|
alias: toDisplaySessionAlias(session3.alias),
|
|
@@ -28677,10 +29364,13 @@ class SessionService {
|
|
|
28677
29364
|
}
|
|
28678
29365
|
const currentInternal = ctx?.current_session;
|
|
28679
29366
|
prevSession.last_used_at = new Date().toISOString();
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
}
|
|
29367
|
+
const nextCtx = { ...ctx, current_session: prevInternal };
|
|
29368
|
+
if (currentInternal && currentInternal !== prevInternal) {
|
|
29369
|
+
nextCtx.previous_session = currentInternal;
|
|
29370
|
+
} else {
|
|
29371
|
+
delete nextCtx.previous_session;
|
|
29372
|
+
}
|
|
29373
|
+
this.state.chat_contexts[chatKey] = nextCtx;
|
|
28684
29374
|
await this.persist();
|
|
28685
29375
|
return {
|
|
28686
29376
|
alias: toDisplaySessionAlias(prevSession.alias),
|
|
@@ -28851,13 +29541,26 @@ class SessionService {
|
|
|
28851
29541
|
const wasActive = Object.values(this.state.chat_contexts).some((ctx) => ctx.current_session === alias);
|
|
28852
29542
|
delete this.state.sessions[alias];
|
|
28853
29543
|
for (const [chatKey, ctx] of Object.entries(this.state.chat_contexts)) {
|
|
28854
|
-
if (ctx.current_session === alias) {
|
|
28855
|
-
delete this.state.chat_contexts[chatKey];
|
|
28856
|
-
continue;
|
|
28857
|
-
}
|
|
28858
29544
|
if (ctx.previous_session === alias) {
|
|
28859
29545
|
delete ctx.previous_session;
|
|
28860
29546
|
}
|
|
29547
|
+
if (ctx.current_session === alias) {
|
|
29548
|
+
if (ctx.previous_session) {
|
|
29549
|
+
ctx.current_session = ctx.previous_session;
|
|
29550
|
+
delete ctx.previous_session;
|
|
29551
|
+
} else {
|
|
29552
|
+
ctx.current_session = "";
|
|
29553
|
+
}
|
|
29554
|
+
}
|
|
29555
|
+
if (ctx.background_results && alias in ctx.background_results) {
|
|
29556
|
+
delete ctx.background_results[alias];
|
|
29557
|
+
if (Object.keys(ctx.background_results).length === 0) {
|
|
29558
|
+
delete ctx.background_results;
|
|
29559
|
+
}
|
|
29560
|
+
}
|
|
29561
|
+
if (!ctx.current_session && !ctx.previous_session && !ctx.background_results) {
|
|
29562
|
+
delete this.state.chat_contexts[chatKey];
|
|
29563
|
+
}
|
|
28861
29564
|
}
|
|
28862
29565
|
await this.persist();
|
|
28863
29566
|
return { wasActive };
|
|
@@ -28971,6 +29674,7 @@ class SessionService {
|
|
|
28971
29674
|
throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
|
|
28972
29675
|
}
|
|
28973
29676
|
const existingSession = this.state.sessions[alias];
|
|
29677
|
+
const sameAgentExisting = existingSession && existingSession.agent === agent3 ? existingSession : undefined;
|
|
28974
29678
|
const now = new Date(this.now()).toISOString();
|
|
28975
29679
|
const normalizedTransportAgentCommand = transportAgentCommand?.trim();
|
|
28976
29680
|
const session3 = {
|
|
@@ -28983,9 +29687,9 @@ class SessionService {
|
|
|
28983
29687
|
agent_session_title: native?.title ?? undefined,
|
|
28984
29688
|
agent_session_updated_at: native?.updatedAt,
|
|
28985
29689
|
attached_at: native ? now : undefined,
|
|
28986
|
-
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } :
|
|
28987
|
-
mode_id:
|
|
28988
|
-
reply_mode:
|
|
29690
|
+
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : sameAgentExisting?.transport_agent_command ? { transport_agent_command: sameAgentExisting.transport_agent_command } : {},
|
|
29691
|
+
mode_id: sameAgentExisting?.mode_id,
|
|
29692
|
+
reply_mode: sameAgentExisting?.reply_mode,
|
|
28989
29693
|
created_at: existingSession?.created_at ?? now,
|
|
28990
29694
|
last_used_at: now
|
|
28991
29695
|
};
|
|
@@ -29235,8 +29939,9 @@ async function runConsole(paths, deps) {
|
|
|
29235
29939
|
throw error2;
|
|
29236
29940
|
}
|
|
29237
29941
|
}
|
|
29238
|
-
|
|
29942
|
+
const reapPromise = Promise.resolve(runtime.reapStaleQueueOwners()).catch(() => {});
|
|
29239
29943
|
if (deps.beforeReady) {
|
|
29944
|
+
await reapPromise;
|
|
29240
29945
|
await deps.beforeReady(runtime);
|
|
29241
29946
|
}
|
|
29242
29947
|
if (deps.daemonRuntime) {
|
|
@@ -29250,6 +29955,7 @@ async function runConsole(paths, deps) {
|
|
|
29250
29955
|
deps.daemonRuntime?.heartbeat().catch(() => {});
|
|
29251
29956
|
}, deps.heartbeatIntervalMs ?? 30000);
|
|
29252
29957
|
}
|
|
29958
|
+
await reapPromise;
|
|
29253
29959
|
const channelStartPromise = deps.channels.startAll({
|
|
29254
29960
|
agent: runtime.agent,
|
|
29255
29961
|
abortSignal: shutdownController.signal,
|
|
@@ -29431,15 +30137,15 @@ class AcpxBridgeClient {
|
|
|
29431
30137
|
onEvent
|
|
29432
30138
|
});
|
|
29433
30139
|
try {
|
|
29434
|
-
|
|
30140
|
+
this.writeLine(encodeBridgeRequest({
|
|
29435
30141
|
id,
|
|
29436
30142
|
method,
|
|
29437
30143
|
params
|
|
29438
|
-
}))
|
|
29439
|
-
|
|
29440
|
-
|
|
29441
|
-
|
|
29442
|
-
}
|
|
30144
|
+
}), (error2) => {
|
|
30145
|
+
if (error2 && this.pending.delete(id)) {
|
|
30146
|
+
reject(error2);
|
|
30147
|
+
}
|
|
30148
|
+
});
|
|
29443
30149
|
} catch (error2) {
|
|
29444
30150
|
this.pending.delete(id);
|
|
29445
30151
|
reject(error2);
|
|
@@ -29519,6 +30225,17 @@ class AcpxBridgeClient {
|
|
|
29519
30225
|
}
|
|
29520
30226
|
}
|
|
29521
30227
|
}
|
|
30228
|
+
function buildBridgeSpawnEnv(options = {}) {
|
|
30229
|
+
return {
|
|
30230
|
+
XACPX_LANG: getLocale(),
|
|
30231
|
+
XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
|
|
30232
|
+
XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
|
|
30233
|
+
XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
|
|
30234
|
+
...typeof options.permissionPolicy === "string" && options.permissionPolicy.trim().length > 0 ? { XACPX_BRIDGE_PERMISSION_POLICY: options.permissionPolicy } : {},
|
|
30235
|
+
...typeof options.queueOwnerTtlSeconds === "number" && Number.isFinite(options.queueOwnerTtlSeconds) ? { XACPX_BRIDGE_QUEUE_OWNER_TTL_SECONDS: String(options.queueOwnerTtlSeconds) } : {},
|
|
30236
|
+
...typeof options.sessionInitTimeoutMs === "number" && Number.isFinite(options.sessionInitTimeoutMs) && options.sessionInitTimeoutMs > 0 ? { XACPX_BRIDGE_SESSION_INIT_TIMEOUT_MS: String(options.sessionInitTimeoutMs) } : {}
|
|
30237
|
+
};
|
|
30238
|
+
}
|
|
29522
30239
|
function buildBridgeSpawnSpec(options) {
|
|
29523
30240
|
if (options.execPath.endsWith("bun")) {
|
|
29524
30241
|
return {
|
|
@@ -29541,15 +30258,17 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
29541
30258
|
cwd: options.cwd ?? process.cwd(),
|
|
29542
30259
|
env: {
|
|
29543
30260
|
...process.env,
|
|
29544
|
-
|
|
29545
|
-
XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
|
|
29546
|
-
XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
|
|
29547
|
-
XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
|
|
29548
|
-
...typeof options.queueOwnerTtlSeconds === "number" && Number.isFinite(options.queueOwnerTtlSeconds) ? { XACPX_BRIDGE_QUEUE_OWNER_TTL_SECONDS: String(options.queueOwnerTtlSeconds) } : {}
|
|
30261
|
+
...buildBridgeSpawnEnv(options)
|
|
29549
30262
|
},
|
|
29550
30263
|
stdio: ["pipe", "pipe", "inherit"]
|
|
29551
30264
|
});
|
|
29552
|
-
const client =
|
|
30265
|
+
const client = manageBridgeChild(child);
|
|
30266
|
+
await client.waitUntilReady();
|
|
30267
|
+
return client;
|
|
30268
|
+
}
|
|
30269
|
+
function manageBridgeChild(child) {
|
|
30270
|
+
const client = new AcpxBridgeClient((line, onWriteError) => child.stdin.write(line, onWriteError));
|
|
30271
|
+
child.stdin.on("error", () => {});
|
|
29553
30272
|
const output = createInterface({
|
|
29554
30273
|
input: child.stdout,
|
|
29555
30274
|
crlfDelay: Infinity
|
|
@@ -29575,7 +30294,6 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
29575
30294
|
await terminateProcessTree(child.pid ?? 0, { detachedProcessGroup: false });
|
|
29576
30295
|
}
|
|
29577
30296
|
};
|
|
29578
|
-
await client.waitUntilReady();
|
|
29579
30297
|
return client;
|
|
29580
30298
|
}
|
|
29581
30299
|
function awaitable(executor) {
|
|
@@ -30417,19 +31135,19 @@ var init_streaming_prompt = __esm(() => {
|
|
|
30417
31135
|
|
|
30418
31136
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
30419
31137
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
30420
|
-
import { dirname as
|
|
31138
|
+
import { dirname as dirname11, join as join16 } from "node:path";
|
|
30421
31139
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
30422
31140
|
if (platform === "win32") {
|
|
30423
31141
|
return null;
|
|
30424
31142
|
}
|
|
30425
|
-
return join16(
|
|
31143
|
+
return join16(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
30426
31144
|
}
|
|
30427
|
-
async function ensureNodePtyHelperExecutable(helperPath,
|
|
31145
|
+
async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
|
|
30428
31146
|
if (!helperPath) {
|
|
30429
31147
|
return;
|
|
30430
31148
|
}
|
|
30431
31149
|
try {
|
|
30432
|
-
await
|
|
31150
|
+
await chmod5(helperPath, 493);
|
|
30433
31151
|
} catch (error2) {
|
|
30434
31152
|
if (error2.code === "ENOENT") {
|
|
30435
31153
|
return;
|
|
@@ -30442,7 +31160,7 @@ var init_node_pty_helper = () => {};
|
|
|
30442
31160
|
// src/transport/acpx-queue-owner-launcher.ts
|
|
30443
31161
|
import { createHash as createHash3 } from "node:crypto";
|
|
30444
31162
|
import { spawn as spawn8 } from "node:child_process";
|
|
30445
|
-
import { readFile as
|
|
31163
|
+
import { readFile as readFile13, unlink } from "node:fs/promises";
|
|
30446
31164
|
import { homedir as homedir8 } from "node:os";
|
|
30447
31165
|
import { join as join17 } from "node:path";
|
|
30448
31166
|
function buildXacpxMcpServerSpec(input) {
|
|
@@ -30605,7 +31323,7 @@ async function terminateAcpxQueueOwner(sessionId) {
|
|
|
30605
31323
|
const lockPath = queueLockFilePath(sessionId);
|
|
30606
31324
|
let owner;
|
|
30607
31325
|
try {
|
|
30608
|
-
owner = JSON.parse(await
|
|
31326
|
+
owner = JSON.parse(await readFile13(lockPath, "utf8"));
|
|
30609
31327
|
} catch {
|
|
30610
31328
|
return;
|
|
30611
31329
|
}
|
|
@@ -31264,10 +31982,11 @@ async function reapQueueOwners(acpxCommand, targets, deps = {}) {
|
|
|
31264
31982
|
const timeoutMs = deps.timeoutMs ?? 5000;
|
|
31265
31983
|
const seen = new Set;
|
|
31266
31984
|
const unique = targets.filter((target) => {
|
|
31267
|
-
|
|
31985
|
+
const key = JSON.stringify([target.agent, target.agentCommand ?? null, target.cwd, target.transportSession]);
|
|
31986
|
+
if (seen.has(key)) {
|
|
31268
31987
|
return false;
|
|
31269
31988
|
}
|
|
31270
|
-
seen.add(
|
|
31989
|
+
seen.add(key);
|
|
31271
31990
|
return true;
|
|
31272
31991
|
});
|
|
31273
31992
|
let terminated = 0;
|
|
@@ -31419,10 +32138,21 @@ class MessageChannelRegistry {
|
|
|
31419
32138
|
throw new Error("all channels failed to start");
|
|
31420
32139
|
}
|
|
31421
32140
|
}
|
|
31422
|
-
stopAll() {
|
|
32141
|
+
async stopAll() {
|
|
32142
|
+
let firstError;
|
|
31423
32143
|
for (const channel of this.channels.values()) {
|
|
31424
|
-
|
|
32144
|
+
try {
|
|
32145
|
+
if (channel.stop) {
|
|
32146
|
+
await channel.stop();
|
|
32147
|
+
} else {
|
|
32148
|
+
channel.logout();
|
|
32149
|
+
}
|
|
32150
|
+
} catch (error2) {
|
|
32151
|
+
firstError ??= error2;
|
|
32152
|
+
}
|
|
31425
32153
|
}
|
|
32154
|
+
if (firstError !== undefined)
|
|
32155
|
+
throw firstError;
|
|
31426
32156
|
}
|
|
31427
32157
|
getByChatKey(chatKey) {
|
|
31428
32158
|
return this.channels.get(getChannelIdFromChatKey(chatKey)) ?? null;
|
|
@@ -31670,7 +32400,7 @@ __export(exports_main, {
|
|
|
31670
32400
|
});
|
|
31671
32401
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
31672
32402
|
import { homedir as homedir9 } from "node:os";
|
|
31673
|
-
import { dirname as
|
|
32403
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
31674
32404
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
31675
32405
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
31676
32406
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
@@ -31742,6 +32472,35 @@ async function buildApp(paths, deps = {}) {
|
|
|
31742
32472
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config4.transport.command });
|
|
31743
32473
|
const stateStore = new StateStore(paths.statePath);
|
|
31744
32474
|
const state = await stateStore.load();
|
|
32475
|
+
const stateLoadReport = stateStore.lastLoadReport;
|
|
32476
|
+
if (stateLoadReport) {
|
|
32477
|
+
for (const record3 of stateLoadReport.dropped) {
|
|
32478
|
+
await logger2.error("state.record_quarantined", "dropped malformed state.json record", {
|
|
32479
|
+
statePath: paths.statePath,
|
|
32480
|
+
section: record3.section,
|
|
32481
|
+
key: record3.key,
|
|
32482
|
+
reason: record3.reason
|
|
32483
|
+
});
|
|
32484
|
+
}
|
|
32485
|
+
if (stateLoadReport.corruptPath) {
|
|
32486
|
+
await logger2.error("state.file_corrupt", "state.json was unreadable; renamed aside and starting empty", {
|
|
32487
|
+
statePath: paths.statePath,
|
|
32488
|
+
corruptPath: stateLoadReport.corruptPath
|
|
32489
|
+
});
|
|
32490
|
+
}
|
|
32491
|
+
if (stateLoadReport.quarantinePath) {
|
|
32492
|
+
await logger2.error("state.file_quarantined", "original state.json backed up before dropping records", {
|
|
32493
|
+
statePath: paths.statePath,
|
|
32494
|
+
quarantinePath: stateLoadReport.quarantinePath
|
|
32495
|
+
});
|
|
32496
|
+
}
|
|
32497
|
+
if (stateLoadReport.backupError) {
|
|
32498
|
+
await logger2.error("state.quarantine_backup_failed", "failed to back up the original state.json", {
|
|
32499
|
+
statePath: paths.statePath,
|
|
32500
|
+
message: stateLoadReport.backupError
|
|
32501
|
+
});
|
|
32502
|
+
}
|
|
32503
|
+
}
|
|
31745
32504
|
const stateMutex = new AsyncMutex;
|
|
31746
32505
|
const debouncedStateStore = new DebouncedStateStore({
|
|
31747
32506
|
delegate: stateStore,
|
|
@@ -31761,7 +32520,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
31761
32520
|
bridgeEntryPath: resolveBridgeEntryPath(),
|
|
31762
32521
|
permissionMode: config4.transport.permissionMode,
|
|
31763
32522
|
nonInteractivePermissions: config4.transport.nonInteractivePermissions,
|
|
31764
|
-
...typeof config4.transport.
|
|
32523
|
+
...typeof config4.transport.permissionPolicy === "string" ? { permissionPolicy: config4.transport.permissionPolicy } : {},
|
|
32524
|
+
...typeof config4.transport.queueOwnerTtlSeconds === "number" ? { queueOwnerTtlSeconds: config4.transport.queueOwnerTtlSeconds } : {},
|
|
32525
|
+
...typeof config4.transport.sessionInitTimeoutMs === "number" ? { sessionInitTimeoutMs: config4.transport.sessionInitTimeoutMs } : {}
|
|
31765
32526
|
})))) : deps.createCliTransport?.(acpxCommand) ?? new AcpxCliTransport({ ...config4.transport, command: acpxCommand });
|
|
31766
32527
|
const quota = new QuotaManager({
|
|
31767
32528
|
onInbound: (chatKey) => {
|
|
@@ -32114,6 +32875,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
32114
32875
|
const progressHeartbeatInterval = startProgressHeartbeat(orchestration3, config4, logger2, deps.channel ?? null);
|
|
32115
32876
|
const orchestrationEndpoint = createOrchestrationEndpoint(paths.orchestrationSocketPath ?? resolveOrchestrationSocketPathFromConfigPath(paths.configPath));
|
|
32116
32877
|
const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration3, {
|
|
32878
|
+
onSocketHardenError: (error2) => {
|
|
32879
|
+
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) });
|
|
32880
|
+
},
|
|
32117
32881
|
createScheduledTaskFromRoute: async (input) => await createScheduledTaskFromRoute(input, {
|
|
32118
32882
|
state,
|
|
32119
32883
|
config: config4,
|
|
@@ -32251,7 +33015,7 @@ async function main() {
|
|
|
32251
33015
|
}
|
|
32252
33016
|
}
|
|
32253
33017
|
async function prepareChannelMedia(configPath, config4) {
|
|
32254
|
-
const runtimeDir = join18(
|
|
33018
|
+
const runtimeDir = join18(dirname12(configPath), "runtime");
|
|
32255
33019
|
const mediaRootDir = join18(runtimeDir, "media");
|
|
32256
33020
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
32257
33021
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -32266,7 +33030,7 @@ function resolveRuntimePaths() {
|
|
|
32266
33030
|
throw new Error("Unable to resolve the current user home directory");
|
|
32267
33031
|
}
|
|
32268
33032
|
const configPath = coreEnv("CONFIG") ?? join18(coreHomeDir(home), "config.json");
|
|
32269
|
-
const runtimeDir = join18(
|
|
33033
|
+
const runtimeDir = join18(dirname12(configPath), "runtime");
|
|
32270
33034
|
return {
|
|
32271
33035
|
configPath,
|
|
32272
33036
|
statePath: coreEnv("STATE") ?? join18(coreHomeDir(home), "state.json"),
|
|
@@ -32281,12 +33045,12 @@ function resolveBridgeEntryPath() {
|
|
|
32281
33045
|
return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
32282
33046
|
}
|
|
32283
33047
|
function resolveAppLogPath(configPath) {
|
|
32284
|
-
const rootDir =
|
|
33048
|
+
const rootDir = dirname12(configPath);
|
|
32285
33049
|
const runtimeDir = join18(rootDir, "runtime");
|
|
32286
33050
|
return join18(runtimeDir, "app.log");
|
|
32287
33051
|
}
|
|
32288
33052
|
function resolvePerfLogPath(configPath) {
|
|
32289
|
-
const rootDir =
|
|
33053
|
+
const rootDir = dirname12(configPath);
|
|
32290
33054
|
const runtimeDir = join18(rootDir, "runtime");
|
|
32291
33055
|
return join18(runtimeDir, "perf.log");
|
|
32292
33056
|
}
|
|
@@ -32523,8 +33287,10 @@ var init_config_check = __esm(async () => {
|
|
|
32523
33287
|
});
|
|
32524
33288
|
|
|
32525
33289
|
// src/doctor/checks/daemon-check.ts
|
|
33290
|
+
import { readdir as readdir3, readFile as readFile14, rm as rm10 } from "node:fs/promises";
|
|
32526
33291
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
32527
33292
|
import { homedir as homedir10 } from "node:os";
|
|
33293
|
+
import { join as join19 } from "node:path";
|
|
32528
33294
|
async function checkDaemon(options = {}) {
|
|
32529
33295
|
const home = options.home ?? process.env.HOME ?? homedir10();
|
|
32530
33296
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
@@ -32532,15 +33298,25 @@ async function checkDaemon(options = {}) {
|
|
|
32532
33298
|
home,
|
|
32533
33299
|
...runtimeDir ? { runtimeDir } : {}
|
|
32534
33300
|
});
|
|
33301
|
+
const isProcessRunning = options.isProcessRunning ?? isProcessAlive;
|
|
33302
|
+
const listConsumerLocks = options.listConsumerLocks ?? defaultListConsumerLocks;
|
|
33303
|
+
const readConsumerLock = options.readConsumerLock ?? defaultReadConsumerLock;
|
|
33304
|
+
const removeConsumerLock = options.removeConsumerLock ?? defaultRemoveConsumerLock;
|
|
32535
33305
|
const controller = createDaemonController(paths, {
|
|
32536
33306
|
processExecPath: options.processExecPath ?? process.execPath,
|
|
32537
33307
|
cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
|
|
32538
33308
|
cwd: options.cwd ?? process.cwd(),
|
|
32539
33309
|
env: options.env ?? process.env,
|
|
32540
|
-
isProcessRunning
|
|
33310
|
+
isProcessRunning
|
|
32541
33311
|
});
|
|
32542
33312
|
try {
|
|
32543
33313
|
const status = await controller.getStatus();
|
|
33314
|
+
const staleLockFix = status.state === "stopped" ? await detectStaleConsumerLockFix(paths.runtimeDir, {
|
|
33315
|
+
isProcessRunning,
|
|
33316
|
+
listConsumerLocks,
|
|
33317
|
+
readConsumerLock,
|
|
33318
|
+
removeConsumerLock
|
|
33319
|
+
}) : undefined;
|
|
32544
33320
|
switch (status.state) {
|
|
32545
33321
|
case "running":
|
|
32546
33322
|
return {
|
|
@@ -32562,6 +33338,7 @@ async function checkDaemon(options = {}) {
|
|
|
32562
33338
|
summary: status.stale ? "daemon was stopped and stale runtime files were cleared" : "daemon is not running",
|
|
32563
33339
|
details: status.stale ? ["stale runtime files were cleared"] : undefined,
|
|
32564
33340
|
suggestions: ["run: xacpx start"],
|
|
33341
|
+
...staleLockFix ? { fixes: [staleLockFix] } : {},
|
|
32565
33342
|
metadata: {
|
|
32566
33343
|
paths,
|
|
32567
33344
|
status
|
|
@@ -32608,25 +33385,210 @@ async function checkDaemon(options = {}) {
|
|
|
32608
33385
|
};
|
|
32609
33386
|
}
|
|
32610
33387
|
}
|
|
32611
|
-
function
|
|
33388
|
+
async function detectStaleConsumerLockFix(runtimeDir, deps) {
|
|
33389
|
+
const lockFiles = await deps.listConsumerLocks(runtimeDir);
|
|
33390
|
+
const stalePaths = [];
|
|
33391
|
+
for (const fileName of lockFiles) {
|
|
33392
|
+
if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
|
|
33393
|
+
continue;
|
|
33394
|
+
}
|
|
33395
|
+
const lockPath = join19(runtimeDir, fileName);
|
|
33396
|
+
const lock2 = await deps.readConsumerLock(lockPath);
|
|
33397
|
+
if (lock2 && !deps.isProcessRunning(lock2.pid)) {
|
|
33398
|
+
stalePaths.push(lockPath);
|
|
33399
|
+
}
|
|
33400
|
+
}
|
|
33401
|
+
if (stalePaths.length === 0) {
|
|
33402
|
+
return;
|
|
33403
|
+
}
|
|
33404
|
+
return {
|
|
33405
|
+
id: "daemon.clear-stale-lock",
|
|
33406
|
+
title: "remove stale consumer lock(s)",
|
|
33407
|
+
run: async () => {
|
|
33408
|
+
const removed = [];
|
|
33409
|
+
let skipped = 0;
|
|
33410
|
+
for (const lockPath of stalePaths) {
|
|
33411
|
+
const lock2 = await deps.readConsumerLock(lockPath);
|
|
33412
|
+
if (!lock2 || deps.isProcessRunning(lock2.pid)) {
|
|
33413
|
+
skipped += 1;
|
|
33414
|
+
continue;
|
|
33415
|
+
}
|
|
33416
|
+
await deps.removeConsumerLock(lockPath);
|
|
33417
|
+
removed.push(lockPath);
|
|
33418
|
+
}
|
|
33419
|
+
const skippedNote = skipped > 0 ? `; left ${skipped} no-longer-stale lock(s) alone` : "";
|
|
33420
|
+
return {
|
|
33421
|
+
ok: true,
|
|
33422
|
+
message: removed.length > 0 ? `removed ${removed.length} stale consumer lock(s): ${removed.join(", ")}${skippedNote}` : `no locks removed${skippedNote}`
|
|
33423
|
+
};
|
|
33424
|
+
}
|
|
33425
|
+
};
|
|
33426
|
+
}
|
|
33427
|
+
async function defaultListConsumerLocks(runtimeDir) {
|
|
32612
33428
|
try {
|
|
32613
|
-
|
|
32614
|
-
return true;
|
|
33429
|
+
return await readdir3(runtimeDir);
|
|
32615
33430
|
} catch {
|
|
32616
|
-
return
|
|
33431
|
+
return [];
|
|
33432
|
+
}
|
|
33433
|
+
}
|
|
33434
|
+
async function defaultReadConsumerLock(path15) {
|
|
33435
|
+
try {
|
|
33436
|
+
const raw = await readFile14(path15, "utf8");
|
|
33437
|
+
const parsed = JSON.parse(raw);
|
|
33438
|
+
return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
|
|
33439
|
+
} catch {
|
|
33440
|
+
return null;
|
|
32617
33441
|
}
|
|
32618
33442
|
}
|
|
33443
|
+
async function defaultRemoveConsumerLock(path15) {
|
|
33444
|
+
await rm10(path15, { force: true });
|
|
33445
|
+
}
|
|
32619
33446
|
function resolveCliEntryPath() {
|
|
32620
33447
|
return process.argv[1] ?? fileURLToPath6(import.meta.url);
|
|
32621
33448
|
}
|
|
32622
33449
|
function formatError5(error2) {
|
|
32623
33450
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
32624
33451
|
}
|
|
33452
|
+
var CONSUMER_LOCK_SUFFIX = "-consumer.lock.json";
|
|
32625
33453
|
var init_daemon_check = __esm(() => {
|
|
32626
33454
|
init_create_daemon_controller();
|
|
32627
33455
|
init_daemon_files();
|
|
32628
33456
|
});
|
|
32629
33457
|
|
|
33458
|
+
// src/doctor/checks/logs-check.ts
|
|
33459
|
+
import { stat as stat3, readdir as readdir4 } from "node:fs/promises";
|
|
33460
|
+
import { basename as basename3, join as join20 } from "node:path";
|
|
33461
|
+
import { homedir as homedir11 } from "node:os";
|
|
33462
|
+
async function checkLogs(options = {}) {
|
|
33463
|
+
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
33464
|
+
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
33465
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
33466
|
+
home,
|
|
33467
|
+
...runtimeDir ? { runtimeDir } : {}
|
|
33468
|
+
});
|
|
33469
|
+
const probe = options.probe ?? createLogsFsProbe();
|
|
33470
|
+
const singleFileWarnBytes = options.singleFileWarnBytes ?? DEFAULT_SINGLE_FILE_WARN_BYTES;
|
|
33471
|
+
const totalWarnBytes = options.totalWarnBytes ?? DEFAULT_TOTAL_WARN_BYTES;
|
|
33472
|
+
let entries;
|
|
33473
|
+
try {
|
|
33474
|
+
const dirStat = await probe.stat(paths.runtimeDir);
|
|
33475
|
+
if (!dirStat.isDirectory()) {
|
|
33476
|
+
return skip("runtime log directory could not be read", [
|
|
33477
|
+
`runtimeDir: ${paths.runtimeDir} (exists but is not a directory)`
|
|
33478
|
+
]);
|
|
33479
|
+
}
|
|
33480
|
+
entries = await probe.readdir(paths.runtimeDir);
|
|
33481
|
+
} catch (error2) {
|
|
33482
|
+
if (isMissingPathError(error2)) {
|
|
33483
|
+
return skip("no runtime logs yet", [`runtimeDir: ${paths.runtimeDir} (missing)`]);
|
|
33484
|
+
}
|
|
33485
|
+
return skip("runtime log directory could not be read", [
|
|
33486
|
+
`runtimeDir: ${paths.runtimeDir}`,
|
|
33487
|
+
`error: ${formatError6(error2)}`
|
|
33488
|
+
]);
|
|
33489
|
+
}
|
|
33490
|
+
const baseNames = [basename3(paths.appLog), basename3(paths.stdoutLog), basename3(paths.stderrLog)];
|
|
33491
|
+
const tracked = new Set(baseNames);
|
|
33492
|
+
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
33493
|
+
const files = [];
|
|
33494
|
+
for (const name of matched) {
|
|
33495
|
+
const path15 = join20(paths.runtimeDir, name);
|
|
33496
|
+
try {
|
|
33497
|
+
const fileStat = await probe.stat(path15);
|
|
33498
|
+
if (fileStat.isDirectory()) {
|
|
33499
|
+
continue;
|
|
33500
|
+
}
|
|
33501
|
+
files.push({ name, path: path15, size: fileStat.size });
|
|
33502
|
+
} catch {
|
|
33503
|
+
continue;
|
|
33504
|
+
}
|
|
33505
|
+
}
|
|
33506
|
+
const total = files.reduce((sum, file) => sum + file.size, 0);
|
|
33507
|
+
const largestSingle = files.reduce((max, file) => Math.max(max, file.size), 0);
|
|
33508
|
+
const overSingle = files.some((file) => file.size > singleFileWarnBytes);
|
|
33509
|
+
const overTotal = total > totalWarnBytes;
|
|
33510
|
+
const sorted = [...files].sort((a, b) => b.size - a.size);
|
|
33511
|
+
const details = [
|
|
33512
|
+
...sorted.map((file) => `${file.name}: ${formatBytes(file.size)}`),
|
|
33513
|
+
`total: ${formatBytes(total)}`
|
|
33514
|
+
];
|
|
33515
|
+
if (overSingle || overTotal) {
|
|
33516
|
+
const reason = overSingle ? `largest single log is ${formatBytes(largestSingle)}` : `total is ${formatBytes(total)}`;
|
|
33517
|
+
return {
|
|
33518
|
+
id: "logs",
|
|
33519
|
+
label: "Logs",
|
|
33520
|
+
severity: "warn",
|
|
33521
|
+
summary: `log growth high: ${reason} (total ${formatBytes(total)})`,
|
|
33522
|
+
details,
|
|
33523
|
+
suggestions: [
|
|
33524
|
+
"logs are large; check disk space and that log rotation is configured (logging.maxSizeBytes / maxFiles)"
|
|
33525
|
+
]
|
|
33526
|
+
};
|
|
33527
|
+
}
|
|
33528
|
+
return {
|
|
33529
|
+
id: "logs",
|
|
33530
|
+
label: "Logs",
|
|
33531
|
+
severity: "pass",
|
|
33532
|
+
summary: `logs total ${formatBytes(total)}`,
|
|
33533
|
+
details
|
|
33534
|
+
};
|
|
33535
|
+
}
|
|
33536
|
+
function skip(summary, details) {
|
|
33537
|
+
return {
|
|
33538
|
+
id: "logs",
|
|
33539
|
+
label: "Logs",
|
|
33540
|
+
severity: "skip",
|
|
33541
|
+
summary,
|
|
33542
|
+
details
|
|
33543
|
+
};
|
|
33544
|
+
}
|
|
33545
|
+
function isTrackedLogName(name, baseNames) {
|
|
33546
|
+
if (baseNames.has(name)) {
|
|
33547
|
+
return true;
|
|
33548
|
+
}
|
|
33549
|
+
for (const base of baseNames) {
|
|
33550
|
+
const prefix = `${base}.`;
|
|
33551
|
+
if (name.startsWith(prefix)) {
|
|
33552
|
+
const suffix = name.slice(prefix.length);
|
|
33553
|
+
if (/^\d+$/.test(suffix) && Number(suffix) > 0) {
|
|
33554
|
+
return true;
|
|
33555
|
+
}
|
|
33556
|
+
}
|
|
33557
|
+
}
|
|
33558
|
+
return false;
|
|
33559
|
+
}
|
|
33560
|
+
function formatBytes(bytes) {
|
|
33561
|
+
if (bytes < 1024) {
|
|
33562
|
+
return `${bytes} B`;
|
|
33563
|
+
}
|
|
33564
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
33565
|
+
let value = bytes / 1024;
|
|
33566
|
+
let unitIndex = 0;
|
|
33567
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
33568
|
+
value /= 1024;
|
|
33569
|
+
unitIndex += 1;
|
|
33570
|
+
}
|
|
33571
|
+
return `${value.toFixed(1)} ${units[unitIndex]}`;
|
|
33572
|
+
}
|
|
33573
|
+
function createLogsFsProbe() {
|
|
33574
|
+
return {
|
|
33575
|
+
stat: async (path15) => await stat3(path15),
|
|
33576
|
+
readdir: async (path15) => await readdir4(path15)
|
|
33577
|
+
};
|
|
33578
|
+
}
|
|
33579
|
+
function formatError6(error2) {
|
|
33580
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
33581
|
+
}
|
|
33582
|
+
function isMissingPathError(error2) {
|
|
33583
|
+
return typeof error2 === "object" && error2 !== null && "code" in error2 && (error2.code === "ENOENT" || error2.code === "ENOTDIR");
|
|
33584
|
+
}
|
|
33585
|
+
var DEFAULT_SINGLE_FILE_WARN_BYTES, DEFAULT_TOTAL_WARN_BYTES;
|
|
33586
|
+
var init_logs_check = __esm(() => {
|
|
33587
|
+
init_daemon_files();
|
|
33588
|
+
DEFAULT_SINGLE_FILE_WARN_BYTES = 50 * 1024 * 1024;
|
|
33589
|
+
DEFAULT_TOTAL_WARN_BYTES = 200 * 1024 * 1024;
|
|
33590
|
+
});
|
|
33591
|
+
|
|
32630
33592
|
// src/doctor/checks/orchestration-health.ts
|
|
32631
33593
|
async function checkOrchestrationHealth(options) {
|
|
32632
33594
|
const state = await options.loadState();
|
|
@@ -32679,13 +33641,187 @@ var init_orchestration_health = __esm(() => {
|
|
|
32679
33641
|
init_i18n();
|
|
32680
33642
|
});
|
|
32681
33643
|
|
|
33644
|
+
// src/doctor/checks/orchestration-socket-check.ts
|
|
33645
|
+
import { homedir as homedir12 } from "node:os";
|
|
33646
|
+
async function checkOrchestrationSocket(options = {}) {
|
|
33647
|
+
const home = options.home ?? process.env.HOME ?? homedir12();
|
|
33648
|
+
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
33649
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
33650
|
+
home,
|
|
33651
|
+
...runtimeDir ? { runtimeDir } : {}
|
|
33652
|
+
});
|
|
33653
|
+
const getDaemonStatus = options.getDaemonStatus ?? ((p) => defaultGetDaemonStatus(p, options));
|
|
33654
|
+
const probe = options.canConnectToEndpoint ?? canConnectToEndpoint;
|
|
33655
|
+
const resolveEndpoint = options.resolveOrchestrationEndpoint ?? ((dir) => resolveOrchestrationEndpoint(dir));
|
|
33656
|
+
let status;
|
|
33657
|
+
try {
|
|
33658
|
+
status = await getDaemonStatus(paths);
|
|
33659
|
+
} catch (error2) {
|
|
33660
|
+
return {
|
|
33661
|
+
id: "orchestration-socket",
|
|
33662
|
+
label: "Orchestration IPC",
|
|
33663
|
+
severity: "skip",
|
|
33664
|
+
summary: "daemon status could not be read",
|
|
33665
|
+
details: [`runtime dir: ${paths.runtimeDir}`, `error: ${formatError7(error2)}`]
|
|
33666
|
+
};
|
|
33667
|
+
}
|
|
33668
|
+
if (status.state === "stopped") {
|
|
33669
|
+
return {
|
|
33670
|
+
id: "orchestration-socket",
|
|
33671
|
+
label: "Orchestration IPC",
|
|
33672
|
+
severity: "skip",
|
|
33673
|
+
summary: "daemon stopped"
|
|
33674
|
+
};
|
|
33675
|
+
}
|
|
33676
|
+
let endpoint;
|
|
33677
|
+
let reachable;
|
|
33678
|
+
try {
|
|
33679
|
+
endpoint = resolveEndpoint(paths.runtimeDir);
|
|
33680
|
+
reachable = await probe(endpoint.path);
|
|
33681
|
+
} catch (error2) {
|
|
33682
|
+
return {
|
|
33683
|
+
id: "orchestration-socket",
|
|
33684
|
+
label: "Orchestration IPC",
|
|
33685
|
+
severity: "skip",
|
|
33686
|
+
summary: "orchestration IPC liveness could not be probed",
|
|
33687
|
+
details: [`runtime dir: ${paths.runtimeDir}`, `error: ${formatError7(error2)}`]
|
|
33688
|
+
};
|
|
33689
|
+
}
|
|
33690
|
+
if (reachable) {
|
|
33691
|
+
return {
|
|
33692
|
+
id: "orchestration-socket",
|
|
33693
|
+
label: "Orchestration IPC",
|
|
33694
|
+
severity: "pass",
|
|
33695
|
+
summary: "orchestration IPC is accepting connections",
|
|
33696
|
+
details: [`endpoint: ${endpoint.path}`]
|
|
33697
|
+
};
|
|
33698
|
+
}
|
|
33699
|
+
return {
|
|
33700
|
+
id: "orchestration-socket",
|
|
33701
|
+
label: "Orchestration IPC",
|
|
33702
|
+
severity: "fail",
|
|
33703
|
+
summary: "daemon is running but orchestration IPC is not accepting connections",
|
|
33704
|
+
details: [`endpoint: ${endpoint.path}`],
|
|
33705
|
+
suggestions: ["run: xacpx restart"]
|
|
33706
|
+
};
|
|
33707
|
+
}
|
|
33708
|
+
async function defaultGetDaemonStatus(paths, options) {
|
|
33709
|
+
const controller = createDaemonController(paths, {
|
|
33710
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
33711
|
+
cliEntryPath: options.cliEntryPath ?? process.argv[1] ?? "",
|
|
33712
|
+
cwd: options.cwd ?? process.cwd(),
|
|
33713
|
+
env: options.env ?? process.env,
|
|
33714
|
+
isProcessRunning: options.isProcessRunning ?? isProcessAlive
|
|
33715
|
+
});
|
|
33716
|
+
return await controller.getStatus();
|
|
33717
|
+
}
|
|
33718
|
+
function formatError7(error2) {
|
|
33719
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
33720
|
+
}
|
|
33721
|
+
var init_orchestration_socket_check = __esm(() => {
|
|
33722
|
+
init_create_daemon_controller();
|
|
33723
|
+
init_daemon_files();
|
|
33724
|
+
init_endpoint_probe();
|
|
33725
|
+
init_orchestration_ipc();
|
|
33726
|
+
});
|
|
33727
|
+
|
|
33728
|
+
// src/doctor/checks/plugin-check.ts
|
|
33729
|
+
async function checkPlugins(options = {}) {
|
|
33730
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
33731
|
+
let config4;
|
|
33732
|
+
try {
|
|
33733
|
+
config4 = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
33734
|
+
} catch (error2) {
|
|
33735
|
+
return {
|
|
33736
|
+
id: "plugins",
|
|
33737
|
+
label: "Plugins",
|
|
33738
|
+
severity: "skip",
|
|
33739
|
+
summary: "plugin check skipped because configuration could not be loaded",
|
|
33740
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError8(error2)}`],
|
|
33741
|
+
suggestions: ["fix the Config check first, then run: xacpx doctor"]
|
|
33742
|
+
};
|
|
33743
|
+
}
|
|
33744
|
+
if (!hasPluginSurface(config4)) {
|
|
33745
|
+
return {
|
|
33746
|
+
id: "plugins",
|
|
33747
|
+
label: "Plugins",
|
|
33748
|
+
severity: "skip",
|
|
33749
|
+
summary: "no plugins configured"
|
|
33750
|
+
};
|
|
33751
|
+
}
|
|
33752
|
+
const pluginHome = (options.resolvePluginHome ?? resolvePluginHome)({ home: options.home });
|
|
33753
|
+
const inspect = options.inspectPlugins ?? inspectPlugins;
|
|
33754
|
+
let issues;
|
|
33755
|
+
try {
|
|
33756
|
+
issues = await inspect({
|
|
33757
|
+
config: config4,
|
|
33758
|
+
pluginHome,
|
|
33759
|
+
currentXacpxVersion: options.currentXacpxVersion ?? XACPX_CORE_VERSION
|
|
33760
|
+
});
|
|
33761
|
+
} catch (error2) {
|
|
33762
|
+
return {
|
|
33763
|
+
id: "plugins",
|
|
33764
|
+
label: "Plugins",
|
|
33765
|
+
severity: "fail",
|
|
33766
|
+
summary: "plugin health check failed",
|
|
33767
|
+
details: [`plugin home: ${pluginHome}`, `error: ${formatError8(error2)}`]
|
|
33768
|
+
};
|
|
33769
|
+
}
|
|
33770
|
+
const errorCount = issues.filter((issue2) => issue2.level === "error").length;
|
|
33771
|
+
const warnCount = issues.filter((issue2) => issue2.level === "warn").length;
|
|
33772
|
+
const severity = errorCount > 0 ? "fail" : warnCount > 0 ? "warn" : "pass";
|
|
33773
|
+
const problemCount = errorCount + warnCount;
|
|
33774
|
+
return {
|
|
33775
|
+
id: "plugins",
|
|
33776
|
+
label: "Plugins",
|
|
33777
|
+
severity,
|
|
33778
|
+
summary: problemCount > 0 ? `${problemCount} plugin issue(s)` : "all plugins healthy",
|
|
33779
|
+
details: issues.filter((issue2) => issue2.level !== "ok").map(formatIssueDetail),
|
|
33780
|
+
suggestions: collectSuggestions(issues),
|
|
33781
|
+
metadata: { pluginHome, errorCount, warnCount }
|
|
33782
|
+
};
|
|
33783
|
+
}
|
|
33784
|
+
function hasPluginSurface(config4) {
|
|
33785
|
+
if ((config4.plugins ?? []).length > 0) {
|
|
33786
|
+
return true;
|
|
33787
|
+
}
|
|
33788
|
+
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
33789
|
+
return (config4.channels ?? []).some((channel) => channel.enabled !== false && !builtInChannelTypes.has(channel.type));
|
|
33790
|
+
}
|
|
33791
|
+
function formatIssueDetail(issue2) {
|
|
33792
|
+
return issue2.plugin ? `${issue2.plugin}: ${issue2.message}` : issue2.message;
|
|
33793
|
+
}
|
|
33794
|
+
function collectSuggestions(issues) {
|
|
33795
|
+
const suggestions = [];
|
|
33796
|
+
const seen = new Set;
|
|
33797
|
+
for (const issue2 of issues) {
|
|
33798
|
+
const suggestion = issue2.suggestion;
|
|
33799
|
+
if (suggestion && !seen.has(suggestion)) {
|
|
33800
|
+
seen.add(suggestion);
|
|
33801
|
+
suggestions.push(`run: ${suggestion}`);
|
|
33802
|
+
}
|
|
33803
|
+
}
|
|
33804
|
+
return suggestions;
|
|
33805
|
+
}
|
|
33806
|
+
function formatError8(error2) {
|
|
33807
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
33808
|
+
}
|
|
33809
|
+
var init_plugin_check = __esm(async () => {
|
|
33810
|
+
init_load_config();
|
|
33811
|
+
init_channel_scope();
|
|
33812
|
+
init_plugin_doctor();
|
|
33813
|
+
init_plugin_home();
|
|
33814
|
+
init_version();
|
|
33815
|
+
await init_main();
|
|
33816
|
+
});
|
|
33817
|
+
|
|
32682
33818
|
// src/doctor/checks/runtime-check.ts
|
|
32683
33819
|
import { constants } from "node:fs";
|
|
32684
|
-
import { access as access4, stat as
|
|
32685
|
-
import { dirname as
|
|
32686
|
-
import { homedir as
|
|
33820
|
+
import { access as access4, stat as stat4 } from "node:fs/promises";
|
|
33821
|
+
import { dirname as dirname13 } from "node:path";
|
|
33822
|
+
import { homedir as homedir13 } from "node:os";
|
|
32687
33823
|
async function checkRuntime(options = {}) {
|
|
32688
|
-
const home = options.home ?? process.env.HOME ??
|
|
33824
|
+
const home = options.home ?? process.env.HOME ?? homedir13();
|
|
32689
33825
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
32690
33826
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
32691
33827
|
home,
|
|
@@ -32711,6 +33847,20 @@ async function checkRuntime(options = {}) {
|
|
|
32711
33847
|
details: checks3.map((check) => check.detail)
|
|
32712
33848
|
};
|
|
32713
33849
|
}
|
|
33850
|
+
const privacy = await inspectRuntimeDirPrivacy(paths.runtimeDir, probe, platform);
|
|
33851
|
+
if (privacy.needsRepair) {
|
|
33852
|
+
return {
|
|
33853
|
+
id: "runtime",
|
|
33854
|
+
label: "Runtime",
|
|
33855
|
+
severity: "warn",
|
|
33856
|
+
summary: "daemon runtime dir should be private (mode 0700)",
|
|
33857
|
+
details: [...checks3.map((check) => check.detail), privacy.detail],
|
|
33858
|
+
fixes: [createEnsurePrivateDirFix(paths.runtimeDir, options.ensurePrivateRuntimeDir)],
|
|
33859
|
+
metadata: {
|
|
33860
|
+
paths
|
|
33861
|
+
}
|
|
33862
|
+
};
|
|
33863
|
+
}
|
|
32714
33864
|
return {
|
|
32715
33865
|
id: "runtime",
|
|
32716
33866
|
label: "Runtime",
|
|
@@ -32722,9 +33872,50 @@ async function checkRuntime(options = {}) {
|
|
|
32722
33872
|
}
|
|
32723
33873
|
};
|
|
32724
33874
|
}
|
|
33875
|
+
async function inspectRuntimeDirPrivacy(runtimeDir, probe, platform) {
|
|
33876
|
+
if (platform === "win32") {
|
|
33877
|
+
return { needsRepair: false, detail: "" };
|
|
33878
|
+
}
|
|
33879
|
+
try {
|
|
33880
|
+
const stats = await probe.stat(runtimeDir);
|
|
33881
|
+
if (typeof stats.mode !== "number") {
|
|
33882
|
+
return { needsRepair: false, detail: "" };
|
|
33883
|
+
}
|
|
33884
|
+
const mode = stats.mode & 511;
|
|
33885
|
+
if (mode === PRIVATE_DIR_MODE) {
|
|
33886
|
+
return { needsRepair: false, detail: "" };
|
|
33887
|
+
}
|
|
33888
|
+
return {
|
|
33889
|
+
needsRepair: true,
|
|
33890
|
+
detail: `runtimeDir: ${runtimeDir} (mode ${formatMode(mode)} is not 0700; group/other access should be removed)`
|
|
33891
|
+
};
|
|
33892
|
+
} catch (error2) {
|
|
33893
|
+
if (isMissingPathError2(error2)) {
|
|
33894
|
+
return {
|
|
33895
|
+
needsRepair: true,
|
|
33896
|
+
detail: `runtimeDir: ${runtimeDir} (missing; will be created with mode 0700)`
|
|
33897
|
+
};
|
|
33898
|
+
}
|
|
33899
|
+
return { needsRepair: false, detail: "" };
|
|
33900
|
+
}
|
|
33901
|
+
}
|
|
33902
|
+
function createEnsurePrivateDirFix(runtimeDir, ensureImpl) {
|
|
33903
|
+
const ensure = ensureImpl ?? ((dir) => ensurePrivateRuntimeDir(dir));
|
|
33904
|
+
return {
|
|
33905
|
+
id: "runtime.ensure-private-dir",
|
|
33906
|
+
title: "create/repair runtime dir with mode 0700",
|
|
33907
|
+
run: async () => {
|
|
33908
|
+
await ensure(runtimeDir);
|
|
33909
|
+
return { ok: true, message: `runtime dir ${runtimeDir} created/repaired with mode 0700` };
|
|
33910
|
+
}
|
|
33911
|
+
};
|
|
33912
|
+
}
|
|
33913
|
+
function formatMode(mode) {
|
|
33914
|
+
return `0${(mode & 511).toString(8)}`;
|
|
33915
|
+
}
|
|
32725
33916
|
function createRuntimeFsProbe() {
|
|
32726
33917
|
return {
|
|
32727
|
-
stat: async (path15) => await
|
|
33918
|
+
stat: async (path15) => await stat4(path15),
|
|
32728
33919
|
access: async (path15, mode) => await access4(path15, mode)
|
|
32729
33920
|
};
|
|
32730
33921
|
}
|
|
@@ -32743,10 +33934,10 @@ async function checkDirectoryCreatable(label, path15, probe, platform) {
|
|
|
32743
33934
|
detail: `${label}: ${path15} (writable)`
|
|
32744
33935
|
};
|
|
32745
33936
|
} catch (error2) {
|
|
32746
|
-
if (!
|
|
33937
|
+
if (!isMissingPathError2(error2)) {
|
|
32747
33938
|
return {
|
|
32748
33939
|
ok: false,
|
|
32749
|
-
detail: `${label}: ${path15} (unusable: ${
|
|
33940
|
+
detail: `${label}: ${path15} (unusable: ${formatError9(error2)})`
|
|
32750
33941
|
};
|
|
32751
33942
|
}
|
|
32752
33943
|
const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
|
|
@@ -32777,13 +33968,13 @@ async function checkFileCreatable(label, path15, probe, platform) {
|
|
|
32777
33968
|
detail: `${label}: ${path15} (writable)`
|
|
32778
33969
|
};
|
|
32779
33970
|
} catch (error2) {
|
|
32780
|
-
if (!
|
|
33971
|
+
if (!isMissingPathError2(error2)) {
|
|
32781
33972
|
return {
|
|
32782
33973
|
ok: false,
|
|
32783
|
-
detail: `${label}: ${path15} (unusable: ${
|
|
33974
|
+
detail: `${label}: ${path15} (unusable: ${formatError9(error2)})`
|
|
32784
33975
|
};
|
|
32785
33976
|
}
|
|
32786
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
33977
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname13(path15), probe, platform);
|
|
32787
33978
|
if (!parentCheck.ok) {
|
|
32788
33979
|
return {
|
|
32789
33980
|
ok: false,
|
|
@@ -32812,14 +34003,14 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
|
32812
34003
|
creatableFrom: path15
|
|
32813
34004
|
};
|
|
32814
34005
|
} catch (error2) {
|
|
32815
|
-
if (!
|
|
34006
|
+
if (!isMissingPathError2(error2)) {
|
|
32816
34007
|
return {
|
|
32817
34008
|
ok: false,
|
|
32818
34009
|
creatableFrom: path15,
|
|
32819
34010
|
blockingPath: path15
|
|
32820
34011
|
};
|
|
32821
34012
|
}
|
|
32822
|
-
const parent =
|
|
34013
|
+
const parent = dirname13(path15);
|
|
32823
34014
|
if (parent === path15) {
|
|
32824
34015
|
return {
|
|
32825
34016
|
ok: false,
|
|
@@ -32840,21 +34031,22 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
|
32840
34031
|
function directoryAccessMode(platform) {
|
|
32841
34032
|
return platform === "win32" ? constants.W_OK : DIRECTORY_USABLE;
|
|
32842
34033
|
}
|
|
32843
|
-
function
|
|
34034
|
+
function isMissingPathError2(error2) {
|
|
32844
34035
|
return isErrnoError(error2) && (error2.code === "ENOENT" || error2.code === "ENOTDIR");
|
|
32845
34036
|
}
|
|
32846
34037
|
function isErrnoError(error2) {
|
|
32847
34038
|
return typeof error2 === "object" && error2 !== null && "code" in error2;
|
|
32848
34039
|
}
|
|
32849
|
-
function
|
|
34040
|
+
function formatError9(error2) {
|
|
32850
34041
|
if (error2 instanceof Error) {
|
|
32851
34042
|
return error2.message;
|
|
32852
34043
|
}
|
|
32853
34044
|
return String(error2);
|
|
32854
34045
|
}
|
|
32855
|
-
var DIRECTORY_USABLE;
|
|
34046
|
+
var DIRECTORY_USABLE, PRIVATE_DIR_MODE = 448;
|
|
32856
34047
|
var init_runtime_check = __esm(() => {
|
|
32857
34048
|
init_daemon_files();
|
|
34049
|
+
init_private_runtime_dir();
|
|
32858
34050
|
DIRECTORY_USABLE = constants.W_OK | constants.X_OK;
|
|
32859
34051
|
});
|
|
32860
34052
|
|
|
@@ -32966,7 +34158,7 @@ async function checkSmoke(options = {}, deps = {}) {
|
|
|
32966
34158
|
label: "Smoke",
|
|
32967
34159
|
severity: "fail",
|
|
32968
34160
|
summary: "smoke transport probe failed",
|
|
32969
|
-
details: [`config path: ${runtimePaths.configPath}`, `error: ${
|
|
34161
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError10(error2)}`]
|
|
32970
34162
|
};
|
|
32971
34163
|
}
|
|
32972
34164
|
}
|
|
@@ -33093,7 +34285,7 @@ function buildDetails3(options) {
|
|
|
33093
34285
|
}
|
|
33094
34286
|
return details;
|
|
33095
34287
|
}
|
|
33096
|
-
function
|
|
34288
|
+
function formatError10(error2) {
|
|
33097
34289
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33098
34290
|
}
|
|
33099
34291
|
var SMOKE_PROMPT = "Reply with exactly: ok";
|
|
@@ -33118,7 +34310,7 @@ async function checkWechat(options = {}) {
|
|
|
33118
34310
|
} catch (error2) {
|
|
33119
34311
|
return {
|
|
33120
34312
|
accountId,
|
|
33121
|
-
error:
|
|
34313
|
+
error: formatError11(error2)
|
|
33122
34314
|
};
|
|
33123
34315
|
}
|
|
33124
34316
|
});
|
|
@@ -33159,7 +34351,7 @@ function buildVerboseDetails(loggedIn, verbose, accounts) {
|
|
|
33159
34351
|
}
|
|
33160
34352
|
return details;
|
|
33161
34353
|
}
|
|
33162
|
-
function
|
|
34354
|
+
function formatError11(error2) {
|
|
33163
34355
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33164
34356
|
}
|
|
33165
34357
|
var init_wechat_check = __esm(() => {
|
|
@@ -33168,43 +34360,60 @@ var init_wechat_check = __esm(() => {
|
|
|
33168
34360
|
|
|
33169
34361
|
// src/doctor/render-doctor.ts
|
|
33170
34362
|
function renderDoctor(report, options = {}) {
|
|
33171
|
-
|
|
34363
|
+
const fixMode = options.fix === true;
|
|
34364
|
+
return options.verbose ? renderVerboseDoctor(report, fixMode) : renderDefaultDoctor(report, fixMode);
|
|
33172
34365
|
}
|
|
33173
|
-
function renderDefaultDoctor(report) {
|
|
34366
|
+
function renderDefaultDoctor(report, fixMode) {
|
|
33174
34367
|
const lines = [];
|
|
33175
34368
|
for (const check of report.checks) {
|
|
33176
|
-
lines.push(renderCheckLine(check));
|
|
34369
|
+
lines.push(renderCheckLine(check, fixMode));
|
|
33177
34370
|
}
|
|
34371
|
+
appendRepairs(lines, report, fixMode);
|
|
33178
34372
|
lines.push(renderSummaryLine(report.checks));
|
|
33179
|
-
|
|
33180
|
-
if (suggestions.length > 0) {
|
|
33181
|
-
lines.push("Next steps:");
|
|
33182
|
-
for (const suggestion of suggestions) {
|
|
33183
|
-
lines.push(`- ${suggestion}`);
|
|
33184
|
-
}
|
|
33185
|
-
}
|
|
34373
|
+
appendNextSteps(lines, report.checks);
|
|
33186
34374
|
return lines;
|
|
33187
34375
|
}
|
|
33188
|
-
function renderVerboseDoctor(report) {
|
|
34376
|
+
function renderVerboseDoctor(report, fixMode) {
|
|
33189
34377
|
const lines = [];
|
|
33190
34378
|
for (const check of report.checks) {
|
|
33191
|
-
lines.push(renderCheckLine(check));
|
|
34379
|
+
lines.push(renderCheckLine(check, fixMode));
|
|
33192
34380
|
for (const detail of check.details ?? []) {
|
|
33193
34381
|
lines.push(` detail: ${detail}`);
|
|
33194
34382
|
}
|
|
33195
34383
|
}
|
|
34384
|
+
appendRepairs(lines, report, fixMode);
|
|
33196
34385
|
lines.push(renderSummaryLine(report.checks));
|
|
33197
|
-
|
|
34386
|
+
appendNextSteps(lines, report.checks);
|
|
34387
|
+
return lines;
|
|
34388
|
+
}
|
|
34389
|
+
function appendNextSteps(lines, checks3) {
|
|
34390
|
+
const suggestions = collectSuggestions2(checks3);
|
|
33198
34391
|
if (suggestions.length > 0) {
|
|
33199
34392
|
lines.push("Next steps:");
|
|
33200
34393
|
for (const suggestion of suggestions) {
|
|
33201
34394
|
lines.push(`- ${suggestion}`);
|
|
33202
34395
|
}
|
|
33203
34396
|
}
|
|
33204
|
-
return lines;
|
|
33205
34397
|
}
|
|
33206
|
-
function
|
|
33207
|
-
|
|
34398
|
+
function appendRepairs(lines, report, fixMode) {
|
|
34399
|
+
if (!fixMode) {
|
|
34400
|
+
return;
|
|
34401
|
+
}
|
|
34402
|
+
const repairs = report.repairs ?? [];
|
|
34403
|
+
if (repairs.length === 0) {
|
|
34404
|
+
return;
|
|
34405
|
+
}
|
|
34406
|
+
lines.push("Repairs:");
|
|
34407
|
+
for (const repair of repairs) {
|
|
34408
|
+
lines.push(`- ${repair.title}: ${repair.status} (${repair.message})`);
|
|
34409
|
+
}
|
|
34410
|
+
}
|
|
34411
|
+
function renderCheckLine(check, fixMode) {
|
|
34412
|
+
const base = `${SEVERITY_LABELS[check.severity]} ${check.label}: ${check.summary}`;
|
|
34413
|
+
if (!fixMode && (check.fixes?.length ?? 0) > 0) {
|
|
34414
|
+
return `${base} (fixable — run: xacpx doctor --fix)`;
|
|
34415
|
+
}
|
|
34416
|
+
return base;
|
|
33208
34417
|
}
|
|
33209
34418
|
function renderSummaryLine(checks3) {
|
|
33210
34419
|
const counts = summarizeChecks(checks3);
|
|
@@ -33216,7 +34425,7 @@ function summarizeChecks(checks3) {
|
|
|
33216
34425
|
return counts;
|
|
33217
34426
|
}, { pass: 0, warn: 0, fail: 0, skip: 0 });
|
|
33218
34427
|
}
|
|
33219
|
-
function
|
|
34428
|
+
function collectSuggestions2(checks3) {
|
|
33220
34429
|
const seen = new Set;
|
|
33221
34430
|
const suggestions = [];
|
|
33222
34431
|
for (const check of checks3) {
|
|
@@ -33241,47 +34450,112 @@ var init_render_doctor = __esm(() => {
|
|
|
33241
34450
|
});
|
|
33242
34451
|
|
|
33243
34452
|
// src/doctor/doctor.ts
|
|
33244
|
-
import { homedir as
|
|
33245
|
-
import { join as
|
|
34453
|
+
import { homedir as homedir14 } from "node:os";
|
|
34454
|
+
import { join as join21 } from "node:path";
|
|
33246
34455
|
async function runDoctor(options = {}, deps = {}) {
|
|
33247
|
-
const home = deps.home ?? process.env.HOME ??
|
|
34456
|
+
const home = deps.home ?? process.env.HOME ?? homedir14();
|
|
33248
34457
|
const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
|
|
33249
34458
|
const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
|
|
34459
|
+
const runners = [
|
|
34460
|
+
{
|
|
34461
|
+
id: "config",
|
|
34462
|
+
run: () => (deps.checkConfig ?? checkConfig)({
|
|
34463
|
+
loadConfig: sharedLoadConfig,
|
|
34464
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34465
|
+
})
|
|
34466
|
+
},
|
|
34467
|
+
{
|
|
34468
|
+
id: "runtime",
|
|
34469
|
+
run: () => (deps.checkRuntime ?? checkRuntime)({
|
|
34470
|
+
home,
|
|
34471
|
+
configPath: runtimePaths.configPath
|
|
34472
|
+
})
|
|
34473
|
+
},
|
|
34474
|
+
{
|
|
34475
|
+
id: "logs",
|
|
34476
|
+
run: () => (deps.checkLogs ?? checkLogs)({
|
|
34477
|
+
home,
|
|
34478
|
+
configPath: runtimePaths.configPath
|
|
34479
|
+
})
|
|
34480
|
+
},
|
|
34481
|
+
{
|
|
34482
|
+
id: "daemon",
|
|
34483
|
+
run: () => (deps.checkDaemon ?? checkDaemon)({
|
|
34484
|
+
home,
|
|
34485
|
+
configPath: runtimePaths.configPath
|
|
34486
|
+
})
|
|
34487
|
+
},
|
|
34488
|
+
{
|
|
34489
|
+
id: "wechat",
|
|
34490
|
+
run: () => (deps.checkWechat ?? checkWechat)({
|
|
34491
|
+
verbose: options.verbose
|
|
34492
|
+
})
|
|
34493
|
+
},
|
|
34494
|
+
{
|
|
34495
|
+
id: "acpx",
|
|
34496
|
+
run: () => (deps.checkAcpx ?? checkAcpx)({
|
|
34497
|
+
verbose: options.verbose,
|
|
34498
|
+
loadConfig: sharedLoadConfig,
|
|
34499
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34500
|
+
})
|
|
34501
|
+
},
|
|
34502
|
+
{
|
|
34503
|
+
id: "bridge",
|
|
34504
|
+
run: () => (deps.checkBridge ?? checkBridge)({
|
|
34505
|
+
verbose: options.verbose,
|
|
34506
|
+
loadConfig: sharedLoadConfig,
|
|
34507
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34508
|
+
})
|
|
34509
|
+
},
|
|
34510
|
+
{
|
|
34511
|
+
id: "plugins",
|
|
34512
|
+
run: () => (deps.checkPlugins ?? checkPlugins)({
|
|
34513
|
+
home,
|
|
34514
|
+
loadConfig: sharedLoadConfig,
|
|
34515
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34516
|
+
})
|
|
34517
|
+
},
|
|
34518
|
+
{
|
|
34519
|
+
id: "orchestration",
|
|
34520
|
+
run: () => (deps.checkOrchestrationHealth ?? (() => defaultCheckOrchestrationHealth({
|
|
34521
|
+
runtimePaths,
|
|
34522
|
+
loadConfig: sharedLoadConfig,
|
|
34523
|
+
isDaemonRunning: deps.isDaemonRunning ?? (() => defaultIsDaemonRunning(home, runtimePaths, deps.getDaemonStatus))
|
|
34524
|
+
})))()
|
|
34525
|
+
},
|
|
34526
|
+
{
|
|
34527
|
+
id: "orchestration-socket",
|
|
34528
|
+
run: () => (deps.checkOrchestrationSocket ?? checkOrchestrationSocket)({
|
|
34529
|
+
home,
|
|
34530
|
+
configPath: runtimePaths.configPath
|
|
34531
|
+
})
|
|
34532
|
+
},
|
|
34533
|
+
{
|
|
34534
|
+
id: "smoke",
|
|
34535
|
+
run: () => options.smoke === true ? (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
|
|
34536
|
+
resolveRuntimePaths: () => runtimePaths,
|
|
34537
|
+
loadConfig: sharedLoadConfig
|
|
34538
|
+
})))(options) : Promise.resolve(createSmokeSkipResult("smoke probe not requested"))
|
|
34539
|
+
}
|
|
34540
|
+
];
|
|
34541
|
+
const runnersById = new Map(runners.map((runner) => [runner.id, runner.run]));
|
|
33250
34542
|
const checks3 = [];
|
|
33251
|
-
|
|
33252
|
-
|
|
33253
|
-
|
|
33254
|
-
}));
|
|
33255
|
-
checks3.push(await (deps.checkRuntime ?? checkRuntime)({
|
|
33256
|
-
home,
|
|
33257
|
-
configPath: runtimePaths.configPath
|
|
33258
|
-
}));
|
|
33259
|
-
checks3.push(await (deps.checkDaemon ?? checkDaemon)({
|
|
33260
|
-
home,
|
|
33261
|
-
configPath: runtimePaths.configPath
|
|
33262
|
-
}));
|
|
33263
|
-
checks3.push(await (deps.checkWechat ?? checkWechat)({
|
|
33264
|
-
verbose: options.verbose
|
|
33265
|
-
}));
|
|
33266
|
-
checks3.push(await (deps.checkAcpx ?? checkAcpx)({
|
|
33267
|
-
verbose: options.verbose,
|
|
33268
|
-
loadConfig: sharedLoadConfig,
|
|
33269
|
-
resolveRuntimePaths: () => runtimePaths
|
|
33270
|
-
}));
|
|
33271
|
-
checks3.push(await (deps.checkBridge ?? checkBridge)({
|
|
33272
|
-
verbose: options.verbose,
|
|
33273
|
-
loadConfig: sharedLoadConfig,
|
|
33274
|
-
resolveRuntimePaths: () => runtimePaths
|
|
33275
|
-
}));
|
|
33276
|
-
checks3.push(await (deps.checkOrchestrationHealth ?? (() => defaultCheckOrchestrationHealth({
|
|
33277
|
-
runtimePaths,
|
|
33278
|
-
loadConfig: sharedLoadConfig
|
|
33279
|
-
})))());
|
|
33280
|
-
checks3.push(options.smoke === true ? await (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
|
|
33281
|
-
resolveRuntimePaths: () => runtimePaths,
|
|
33282
|
-
loadConfig: sharedLoadConfig
|
|
33283
|
-
})))(options) : createSmokeSkipResult("smoke probe not requested"));
|
|
34543
|
+
for (const runner of runners) {
|
|
34544
|
+
checks3.push(await runner.run());
|
|
34545
|
+
}
|
|
33284
34546
|
const report = { checks: checks3 };
|
|
34547
|
+
if (options.fix === true) {
|
|
34548
|
+
const { repairs, repairedCheckIds } = await applyRepairs(checks3);
|
|
34549
|
+
report.repairs = repairs;
|
|
34550
|
+
for (const checkId of repairedCheckIds) {
|
|
34551
|
+
const index = checks3.findIndex((check) => check.id === checkId);
|
|
34552
|
+
const rerun = runnersById.get(checkId);
|
|
34553
|
+
if (index === -1 || !rerun) {
|
|
34554
|
+
continue;
|
|
34555
|
+
}
|
|
34556
|
+
checks3[index] = await rerun();
|
|
34557
|
+
}
|
|
34558
|
+
}
|
|
33285
34559
|
const output = (deps.renderDoctor ?? renderDoctor)(report, options);
|
|
33286
34560
|
return {
|
|
33287
34561
|
report,
|
|
@@ -33289,6 +34563,41 @@ async function runDoctor(options = {}, deps = {}) {
|
|
|
33289
34563
|
exitCode: checks3.some((check) => check.severity === "fail") ? 1 : 0
|
|
33290
34564
|
};
|
|
33291
34565
|
}
|
|
34566
|
+
async function applyRepairs(checks3) {
|
|
34567
|
+
const repairs = [];
|
|
34568
|
+
const repairedCheckIds = [];
|
|
34569
|
+
for (const check of checks3) {
|
|
34570
|
+
for (const fix of check.fixes ?? []) {
|
|
34571
|
+
if (fix.withheld !== undefined) {
|
|
34572
|
+
repairs.push({
|
|
34573
|
+
checkId: check.id,
|
|
34574
|
+
fixId: fix.id,
|
|
34575
|
+
title: fix.title,
|
|
34576
|
+
status: "skipped",
|
|
34577
|
+
message: fix.withheld
|
|
34578
|
+
});
|
|
34579
|
+
continue;
|
|
34580
|
+
}
|
|
34581
|
+
let outcome;
|
|
34582
|
+
try {
|
|
34583
|
+
outcome = await fix.run();
|
|
34584
|
+
} catch (error2) {
|
|
34585
|
+
outcome = { ok: false, message: formatError12(error2) };
|
|
34586
|
+
}
|
|
34587
|
+
repairs.push({
|
|
34588
|
+
checkId: check.id,
|
|
34589
|
+
fixId: fix.id,
|
|
34590
|
+
title: fix.title,
|
|
34591
|
+
status: outcome.ok ? "applied" : "failed",
|
|
34592
|
+
message: outcome.message
|
|
34593
|
+
});
|
|
34594
|
+
if (outcome.ok && !repairedCheckIds.includes(check.id)) {
|
|
34595
|
+
repairedCheckIds.push(check.id);
|
|
34596
|
+
}
|
|
34597
|
+
}
|
|
34598
|
+
}
|
|
34599
|
+
return { repairs, repairedCheckIds };
|
|
34600
|
+
}
|
|
33292
34601
|
function resolveDoctorRuntimePaths(home, resolver) {
|
|
33293
34602
|
if (resolver) {
|
|
33294
34603
|
return resolver();
|
|
@@ -33297,8 +34606,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
|
|
|
33297
34606
|
return resolveRuntimePaths();
|
|
33298
34607
|
}
|
|
33299
34608
|
return {
|
|
33300
|
-
configPath:
|
|
33301
|
-
statePath:
|
|
34609
|
+
configPath: join21(coreHomeDir(home), "config.json"),
|
|
34610
|
+
statePath: join21(coreHomeDir(home), "state.json")
|
|
33302
34611
|
};
|
|
33303
34612
|
}
|
|
33304
34613
|
function depsUseExplicitRuntimeOverrides() {
|
|
@@ -33338,36 +34647,116 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33338
34647
|
label: "Orchestration",
|
|
33339
34648
|
severity: "skip",
|
|
33340
34649
|
summary: "orchestration check skipped because configuration could not be loaded",
|
|
33341
|
-
details: [`config path: ${deps.runtimePaths.configPath}`, `error: ${
|
|
34650
|
+
details: [`config path: ${deps.runtimePaths.configPath}`, `error: ${formatError12(error2)}`],
|
|
33342
34651
|
suggestions: ["fix the Config check first, then run: xacpx doctor"]
|
|
33343
34652
|
};
|
|
33344
34653
|
}
|
|
33345
34654
|
try {
|
|
33346
34655
|
const store = new StateStore(deps.runtimePaths.statePath);
|
|
33347
|
-
|
|
33348
|
-
|
|
34656
|
+
const inspection = await store.inspect();
|
|
34657
|
+
const result = await checkOrchestrationHealth({
|
|
34658
|
+
loadState: async () => inspection.state,
|
|
33349
34659
|
now: () => new Date,
|
|
33350
34660
|
heartbeatThresholdSeconds: config4.orchestration.progressHeartbeatSeconds
|
|
33351
34661
|
});
|
|
34662
|
+
const daemonRunning = inspection.report ? await deps.isDaemonRunning() : false;
|
|
34663
|
+
return applyStateInspectionReport(result, inspection.report, deps.runtimePaths.statePath, daemonRunning, deps.isDaemonRunning);
|
|
33352
34664
|
} catch (error2) {
|
|
33353
34665
|
return {
|
|
33354
34666
|
id: "orchestration",
|
|
33355
34667
|
label: "Orchestration",
|
|
33356
34668
|
severity: "fail",
|
|
33357
34669
|
summary: "orchestration health check failed",
|
|
33358
|
-
details: [`state path: ${deps.runtimePaths.statePath}`, `error: ${
|
|
34670
|
+
details: [`state path: ${deps.runtimePaths.statePath}`, `error: ${formatError12(error2)}`]
|
|
33359
34671
|
};
|
|
33360
34672
|
}
|
|
33361
34673
|
}
|
|
33362
|
-
function
|
|
34674
|
+
function applyStateInspectionReport(result, report, statePath, daemonRunning, isDaemonRunning) {
|
|
34675
|
+
if (!report) {
|
|
34676
|
+
return result;
|
|
34677
|
+
}
|
|
34678
|
+
const fileCorrupt = report.dropped.some((record3) => record3.section === "file");
|
|
34679
|
+
const details = [
|
|
34680
|
+
...result.details ?? [],
|
|
34681
|
+
`state path: ${statePath}`,
|
|
34682
|
+
...report.dropped.map((record3) => record3.section === "file" ? `state.json is unreadable: ${record3.reason}` : `invalid state record ${record3.section}["${record3.key}"]: ${record3.reason}`)
|
|
34683
|
+
];
|
|
34684
|
+
return {
|
|
34685
|
+
...result,
|
|
34686
|
+
severity: result.severity === "fail" ? "fail" : "warn",
|
|
34687
|
+
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}`,
|
|
34688
|
+
details,
|
|
34689
|
+
suggestions: [
|
|
34690
|
+
...result.suggestions ?? [],
|
|
34691
|
+
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"
|
|
34692
|
+
],
|
|
34693
|
+
fixes: [createStateQuarantineFix(statePath, daemonRunning, isDaemonRunning)]
|
|
34694
|
+
};
|
|
34695
|
+
}
|
|
34696
|
+
function createStateQuarantineFix(statePath, daemonRunning, isDaemonRunning) {
|
|
34697
|
+
return {
|
|
34698
|
+
id: "state.quarantine",
|
|
34699
|
+
title: "quarantine invalid state.json records",
|
|
34700
|
+
...daemonRunning ? { withheld: "stop the daemon first: xacpx stop" } : {},
|
|
34701
|
+
run: async () => {
|
|
34702
|
+
if (await isDaemonRunning()) {
|
|
34703
|
+
return {
|
|
34704
|
+
ok: false,
|
|
34705
|
+
message: "a daemon started since detection; stop it first: xacpx stop"
|
|
34706
|
+
};
|
|
34707
|
+
}
|
|
34708
|
+
const store = new StateStore(statePath);
|
|
34709
|
+
await store.load();
|
|
34710
|
+
const report = store.lastLoadReport;
|
|
34711
|
+
if (!report) {
|
|
34712
|
+
return { ok: true, message: "state.json was already valid; nothing to quarantine" };
|
|
34713
|
+
}
|
|
34714
|
+
if (report.corruptPath) {
|
|
34715
|
+
return { ok: true, message: `state.json was unreadable; renamed to ${report.corruptPath} and reset` };
|
|
34716
|
+
}
|
|
34717
|
+
const backup = report.quarantinePath ? ` (original backed up to ${report.quarantinePath})` : "";
|
|
34718
|
+
return {
|
|
34719
|
+
ok: true,
|
|
34720
|
+
message: `quarantined ${report.dropped.length} invalid state.json record(s)${backup}`
|
|
34721
|
+
};
|
|
34722
|
+
}
|
|
34723
|
+
};
|
|
34724
|
+
}
|
|
34725
|
+
async function defaultIsDaemonRunning(home, runtimePaths, getDaemonStatus = () => defaultGetDaemonStatus2(home, runtimePaths)) {
|
|
34726
|
+
try {
|
|
34727
|
+
const status = await getDaemonStatus();
|
|
34728
|
+
return status.state === "running" || status.state === "indeterminate";
|
|
34729
|
+
} catch {
|
|
34730
|
+
return true;
|
|
34731
|
+
}
|
|
34732
|
+
}
|
|
34733
|
+
async function defaultGetDaemonStatus2(home, runtimePaths) {
|
|
34734
|
+
const paths = resolveDaemonPaths({
|
|
34735
|
+
home,
|
|
34736
|
+
runtimeDir: resolveRuntimeDirFromConfigPath(runtimePaths.configPath)
|
|
34737
|
+
});
|
|
34738
|
+
const controller = createDaemonController(paths, {
|
|
34739
|
+
processExecPath: process.execPath,
|
|
34740
|
+
cliEntryPath: process.argv[1] ?? "",
|
|
34741
|
+
cwd: process.cwd(),
|
|
34742
|
+
env: process.env,
|
|
34743
|
+
isProcessRunning: isProcessAlive
|
|
34744
|
+
});
|
|
34745
|
+
return await controller.getStatus();
|
|
34746
|
+
}
|
|
34747
|
+
function formatError12(error2) {
|
|
33363
34748
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33364
34749
|
}
|
|
33365
34750
|
var init_doctor = __esm(async () => {
|
|
33366
34751
|
init_core_home();
|
|
33367
34752
|
init_load_config();
|
|
34753
|
+
init_create_daemon_controller();
|
|
34754
|
+
init_daemon_files();
|
|
33368
34755
|
init_state_store();
|
|
33369
34756
|
init_daemon_check();
|
|
34757
|
+
init_logs_check();
|
|
33370
34758
|
init_orchestration_health();
|
|
34759
|
+
init_orchestration_socket_check();
|
|
33371
34760
|
init_runtime_check();
|
|
33372
34761
|
init_wechat_check();
|
|
33373
34762
|
init_render_doctor();
|
|
@@ -33376,6 +34765,7 @@ var init_doctor = __esm(async () => {
|
|
|
33376
34765
|
init_acpx_check(),
|
|
33377
34766
|
init_bridge_check(),
|
|
33378
34767
|
init_config_check(),
|
|
34768
|
+
init_plugin_check(),
|
|
33379
34769
|
init_smoke_check()
|
|
33380
34770
|
]);
|
|
33381
34771
|
});
|
|
@@ -33400,8 +34790,8 @@ var init_doctor2 = __esm(async () => {
|
|
|
33400
34790
|
// src/cli.ts
|
|
33401
34791
|
init_core_home();
|
|
33402
34792
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
33403
|
-
import { homedir as
|
|
33404
|
-
import { dirname as
|
|
34793
|
+
import { homedir as homedir15 } from "node:os";
|
|
34794
|
+
import { dirname as dirname14, join as join22, sep } from "node:path";
|
|
33405
34795
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
33406
34796
|
|
|
33407
34797
|
// src/runtime/migrate-core-home.ts
|
|
@@ -33485,8 +34875,8 @@ init_daemon_files();
|
|
|
33485
34875
|
|
|
33486
34876
|
// src/daemon/daemon-runtime.ts
|
|
33487
34877
|
init_daemon_status();
|
|
33488
|
-
|
|
33489
|
-
import {
|
|
34878
|
+
init_private_runtime_dir();
|
|
34879
|
+
import { rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
33490
34880
|
|
|
33491
34881
|
class DaemonRuntime {
|
|
33492
34882
|
paths;
|
|
@@ -33512,8 +34902,8 @@ class DaemonRuntime {
|
|
|
33512
34902
|
stdout_log: this.paths.stdoutLog,
|
|
33513
34903
|
stderr_log: this.paths.stderrLog
|
|
33514
34904
|
};
|
|
33515
|
-
await
|
|
33516
|
-
await
|
|
34905
|
+
await ensurePrivateRuntimeDir(this.paths.runtimeDir);
|
|
34906
|
+
await writeFile2(this.paths.pidFile, `${this.options.pid}
|
|
33517
34907
|
`);
|
|
33518
34908
|
await this.statusStore.save(this.currentStatus);
|
|
33519
34909
|
}
|
|
@@ -46188,7 +47578,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
46188
47578
|
});
|
|
46189
47579
|
tools.push({
|
|
46190
47580
|
name: "scheduled_list",
|
|
46191
|
-
description: "List pending one-shot scheduled tasks
|
|
47581
|
+
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.",
|
|
46192
47582
|
inputSchema: exports_external.object({}).strict(),
|
|
46193
47583
|
handler: async () => await asToolResult(async () => {
|
|
46194
47584
|
const tasks = await transport.scheduledList({ coordinatorSession });
|
|
@@ -46206,7 +47596,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
46206
47596
|
});
|
|
46207
47597
|
tools.push({
|
|
46208
47598
|
name: "scheduled_cancel",
|
|
46209
|
-
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.",
|
|
47599
|
+
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.",
|
|
46210
47600
|
inputSchema: exports_external.object({
|
|
46211
47601
|
id: exports_external.string().min(1).describe("The scheduled task id, e.g. 'k8f2' (a leading # is allowed).")
|
|
46212
47602
|
}).strict(),
|
|
@@ -47416,7 +48806,10 @@ async function maybeRunFirstUseOnboarding(input) {
|
|
|
47416
48806
|
const agentExisted = Boolean(input.config.agents[agentName]);
|
|
47417
48807
|
input.config.workspaces[workspaceName] = { cwd };
|
|
47418
48808
|
input.config.agents[agentName] = template;
|
|
47419
|
-
await input.
|
|
48809
|
+
await input.saveFirstRunConfig({
|
|
48810
|
+
workspace: { name: workspaceName, cwd },
|
|
48811
|
+
agent: { name: agentName, config: template }
|
|
48812
|
+
});
|
|
47420
48813
|
const alias = `${workspaceName}:${agentName}`;
|
|
47421
48814
|
input.deps.print(t().misc.onboardingCreatedWorkspace(workspaceName, alias));
|
|
47422
48815
|
return {
|
|
@@ -47439,8 +48832,8 @@ function resolveTemplateChoice(answer, names) {
|
|
|
47439
48832
|
// src/cli-update.ts
|
|
47440
48833
|
init_plugin_home();
|
|
47441
48834
|
import { spawn as spawn4 } from "node:child_process";
|
|
47442
|
-
import { readFile as
|
|
47443
|
-
import { dirname as
|
|
48835
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
48836
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
47444
48837
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
47445
48838
|
|
|
47446
48839
|
// src/plugins/package-manager.ts
|
|
@@ -47448,9 +48841,14 @@ init_plugin_home();
|
|
|
47448
48841
|
import { spawn as spawn3 } from "node:child_process";
|
|
47449
48842
|
import { rm as rm4 } from "node:fs/promises";
|
|
47450
48843
|
import { join as join7 } from "node:path";
|
|
48844
|
+
function shellSpawnPlan(args) {
|
|
48845
|
+
const shell = process.platform === "win32";
|
|
48846
|
+
return { shell, args: shell ? args.map((arg) => `"${arg}"`) : args };
|
|
48847
|
+
}
|
|
47451
48848
|
async function defaultRunCommand(command, args, options) {
|
|
47452
48849
|
await new Promise((resolve, reject) => {
|
|
47453
|
-
const
|
|
48850
|
+
const plan = shellSpawnPlan(args);
|
|
48851
|
+
const child = spawn3(command, plan.args, { cwd: options.cwd, stdio: "inherit", shell: plan.shell });
|
|
47454
48852
|
child.on("error", reject);
|
|
47455
48853
|
child.on("exit", (code) => {
|
|
47456
48854
|
if (code === 0)
|
|
@@ -47462,7 +48860,8 @@ async function defaultRunCommand(command, args, options) {
|
|
|
47462
48860
|
}
|
|
47463
48861
|
async function silentRun(command, args, options) {
|
|
47464
48862
|
await new Promise((resolve, reject) => {
|
|
47465
|
-
const
|
|
48863
|
+
const plan = shellSpawnPlan(args);
|
|
48864
|
+
const child = spawn3(command, plan.args, { cwd: options.cwd, stdio: "ignore", shell: plan.shell });
|
|
47466
48865
|
child.on("error", reject);
|
|
47467
48866
|
child.on("exit", (code) => {
|
|
47468
48867
|
if (code === 0)
|
|
@@ -47638,7 +49037,7 @@ async function handleUpdateCli(args, deps) {
|
|
|
47638
49037
|
}
|
|
47639
49038
|
if (selected.targets.some((target) => target.kind === "plugin")) {
|
|
47640
49039
|
config4.plugins = updatedPlugins;
|
|
47641
|
-
await deps.
|
|
49040
|
+
await deps.savePlugins(updatedPlugins);
|
|
47642
49041
|
}
|
|
47643
49042
|
return 0;
|
|
47644
49043
|
}
|
|
@@ -47748,9 +49147,10 @@ function compareSemver2(a, b) {
|
|
|
47748
49147
|
return 0;
|
|
47749
49148
|
return left.prerelease ? -1 : 1;
|
|
47750
49149
|
}
|
|
49150
|
+
var spawnUsesShell = () => process.platform === "win32";
|
|
47751
49151
|
async function runCapture(command, args) {
|
|
47752
49152
|
return await new Promise((resolve, reject) => {
|
|
47753
|
-
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
49153
|
+
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: spawnUsesShell() });
|
|
47754
49154
|
let stdout2 = "";
|
|
47755
49155
|
let stderr = "";
|
|
47756
49156
|
child.stdout.setEncoding("utf8");
|
|
@@ -47767,7 +49167,7 @@ async function runCapture(command, args) {
|
|
|
47767
49167
|
}
|
|
47768
49168
|
async function runInherit(command, args) {
|
|
47769
49169
|
await new Promise((resolve, reject) => {
|
|
47770
|
-
const child = spawn4(command, args, { stdio: "inherit" });
|
|
49170
|
+
const child = spawn4(command, args, { stdio: "inherit", shell: spawnUsesShell() });
|
|
47771
49171
|
child.on("error", reject);
|
|
47772
49172
|
child.on("exit", (code) => {
|
|
47773
49173
|
if (code === 0)
|
|
@@ -47779,10 +49179,10 @@ async function runInherit(command, args) {
|
|
|
47779
49179
|
}
|
|
47780
49180
|
async function readPackageName() {
|
|
47781
49181
|
try {
|
|
47782
|
-
const here =
|
|
49182
|
+
const here = dirname8(fileURLToPath3(import.meta.url));
|
|
47783
49183
|
for (const candidate of [join11(here, "..", "package.json"), join11(here, "..", "..", "package.json")]) {
|
|
47784
49184
|
try {
|
|
47785
|
-
const parsed = JSON.parse(await
|
|
49185
|
+
const parsed = JSON.parse(await readFile9(candidate, "utf8"));
|
|
47786
49186
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
47787
49187
|
return parsed.name.trim();
|
|
47788
49188
|
} catch {}
|
|
@@ -48024,7 +49424,7 @@ async function addChannel(type, rawArgs, deps) {
|
|
|
48024
49424
|
return 1;
|
|
48025
49425
|
}
|
|
48026
49426
|
config4.channels = [...config4.channels ?? [], candidate];
|
|
48027
|
-
await deps.
|
|
49427
|
+
await deps.saveChannels(config4.channels);
|
|
48028
49428
|
deps.print(t().channelCli.channelAdded(type));
|
|
48029
49429
|
for (const line of provider.renderSummary(candidate))
|
|
48030
49430
|
deps.print(line);
|
|
@@ -48125,7 +49525,7 @@ async function removeChannel(type, rawArgs, deps) {
|
|
|
48125
49525
|
return 1;
|
|
48126
49526
|
}
|
|
48127
49527
|
config4.channels = config4.channels.filter((entry) => entry.id !== channel.id);
|
|
48128
|
-
await deps.
|
|
49528
|
+
await deps.saveChannels(config4.channels);
|
|
48129
49529
|
deps.print(t().channelCli.channelRemoved(channel.id));
|
|
48130
49530
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48131
49531
|
}
|
|
@@ -48147,7 +49547,7 @@ async function setChannelEnabled(type, enabled, rawArgs, deps) {
|
|
|
48147
49547
|
return 1;
|
|
48148
49548
|
}
|
|
48149
49549
|
channel.enabled = enabled;
|
|
48150
|
-
await deps.
|
|
49550
|
+
await deps.saveChannels(config4.channels);
|
|
48151
49551
|
deps.print(t().channelCli.channelEnabledToggled(channel.id, enabled));
|
|
48152
49552
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48153
49553
|
}
|
|
@@ -48169,7 +49569,7 @@ async function setChannelReplyMode(type, mode, rawArgs, deps) {
|
|
|
48169
49569
|
return 1;
|
|
48170
49570
|
}
|
|
48171
49571
|
channel.replyMode = mode;
|
|
48172
|
-
await deps.
|
|
49572
|
+
await deps.saveChannels(config4.channels);
|
|
48173
49573
|
deps.print(t().channelCli.channelReplyModeSet(channel.id, mode));
|
|
48174
49574
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48175
49575
|
}
|
|
@@ -48315,7 +49715,7 @@ async function addChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48315
49715
|
deps.print(validation.map((issue2) => issue2.message).join(";"));
|
|
48316
49716
|
return 1;
|
|
48317
49717
|
}
|
|
48318
|
-
await deps.
|
|
49718
|
+
await deps.saveChannels(config4.channels);
|
|
48319
49719
|
deps.print(t().channelCli.channelAccountAdded(type, accountId));
|
|
48320
49720
|
if (reEnabledChannel)
|
|
48321
49721
|
deps.print(t().channelCli.channelReEnabled(type));
|
|
@@ -48355,7 +49755,7 @@ async function removeChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48355
49755
|
return 1;
|
|
48356
49756
|
}
|
|
48357
49757
|
config4.channels = config4.channels.filter((channel) => channel.id !== existing.id);
|
|
48358
|
-
await deps.
|
|
49758
|
+
await deps.saveChannels(config4.channels);
|
|
48359
49759
|
deps.print(t().channelCli.channelAccountRemovedWithChannel(type, accountId));
|
|
48360
49760
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48361
49761
|
}
|
|
@@ -48372,7 +49772,7 @@ async function removeChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48372
49772
|
deps.print(t().channelCli.channelAccountDefaultSwitched(options.defaultAccount));
|
|
48373
49773
|
}
|
|
48374
49774
|
existing.options = options;
|
|
48375
|
-
await deps.
|
|
49775
|
+
await deps.saveChannels(config4.channels);
|
|
48376
49776
|
deps.print(t().channelCli.channelAccountRemoved(type, accountId));
|
|
48377
49777
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48378
49778
|
}
|
|
@@ -48426,7 +49826,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
48426
49826
|
return 1;
|
|
48427
49827
|
}
|
|
48428
49828
|
existing.options = options;
|
|
48429
|
-
await deps.
|
|
49829
|
+
await deps.saveChannels(config4.channels);
|
|
48430
49830
|
deps.print(t().channelCli.channelAccountEnabledToggled(type, accountId, enabled));
|
|
48431
49831
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48432
49832
|
}
|
|
@@ -48434,128 +49834,39 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
48434
49834
|
// src/plugins/plugin-cli.ts
|
|
48435
49835
|
init_core_home();
|
|
48436
49836
|
init_plugin_home();
|
|
48437
|
-
import { readFile as
|
|
49837
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
48438
49838
|
import { isAbsolute, join as join13, resolve } from "node:path";
|
|
48439
49839
|
init_plugin_loader();
|
|
48440
49840
|
init_validate_plugin();
|
|
48441
|
-
|
|
48442
|
-
// src/plugins/plugin-doctor.ts
|
|
48443
|
-
init_channel_scope();
|
|
48444
|
-
init_plugin_loader();
|
|
48445
|
-
init_validate_plugin();
|
|
49841
|
+
init_plugin_doctor();
|
|
48446
49842
|
init_known_plugins();
|
|
48447
|
-
|
|
48448
|
-
|
|
48449
|
-
function
|
|
48450
|
-
|
|
48451
|
-
|
|
48452
|
-
|
|
48453
|
-
|
|
48454
|
-
|
|
48455
|
-
const parsed = JSON.parse(raw);
|
|
48456
|
-
const out = {};
|
|
48457
|
-
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
48458
|
-
if (typeof value === "string")
|
|
48459
|
-
out[name] = value;
|
|
48460
|
-
}
|
|
48461
|
-
return out;
|
|
48462
|
-
} catch (error2) {
|
|
48463
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
48464
|
-
throw new Error(`failed to read plugin home package.json: ${message}`);
|
|
48465
|
-
}
|
|
49843
|
+
init_plugin_renames();
|
|
49844
|
+
init_i18n();
|
|
49845
|
+
function findPluginSpecViolation(spec, platform) {
|
|
49846
|
+
if (spec.includes('"'))
|
|
49847
|
+
return "double-quote";
|
|
49848
|
+
if (platform === "win32" && spec.includes("%"))
|
|
49849
|
+
return "percent-on-windows";
|
|
49850
|
+
return null;
|
|
48466
49851
|
}
|
|
48467
|
-
|
|
48468
|
-
const
|
|
48469
|
-
|
|
48470
|
-
try {
|
|
48471
|
-
dependencies = await readDependencyEntries(input.pluginHome);
|
|
48472
|
-
} catch (error2) {
|
|
48473
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
48474
|
-
return [{ level: "error", message }];
|
|
48475
|
-
}
|
|
48476
|
-
const importPlugin = input.importPlugin ?? importPluginFromHome;
|
|
48477
|
-
const allConfigured = input.config.plugins;
|
|
48478
|
-
const filterByName = input.pluginName ?? null;
|
|
48479
|
-
if (filterByName && !allConfigured.some((plugin) => plugin.name === filterByName)) {
|
|
48480
|
-
return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
|
|
48481
|
-
}
|
|
48482
|
-
const pushIfRelevant = (issue2) => {
|
|
48483
|
-
if (!filterByName || issue2.plugin === filterByName)
|
|
48484
|
-
issues.push(issue2);
|
|
48485
|
-
};
|
|
48486
|
-
const channelProviders = new Map;
|
|
48487
|
-
for (const configPlugin of allConfigured) {
|
|
48488
|
-
if (!(configPlugin.name in dependencies)) {
|
|
48489
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `package not installed in plugin home; run xacpx plugin add ${configPlugin.name}` });
|
|
49852
|
+
function invalidSpecMessage(specs, platform) {
|
|
49853
|
+
for (const spec of specs) {
|
|
49854
|
+
if (!spec)
|
|
48490
49855
|
continue;
|
|
48491
|
-
|
|
48492
|
-
|
|
48493
|
-
|
|
48494
|
-
|
|
48495
|
-
|
|
48496
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
48497
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `failed to import plugin: ${message}` });
|
|
48498
|
-
continue;
|
|
48499
|
-
}
|
|
48500
|
-
try {
|
|
48501
|
-
const plugin = validateWeacpxPlugin(moduleValue, configPlugin.name, {
|
|
48502
|
-
...input.currentXacpxVersion !== undefined ? { currentXacpxVersion: input.currentXacpxVersion } : {}
|
|
48503
|
-
});
|
|
48504
|
-
const channels = plugin.channels ?? [];
|
|
48505
|
-
const channelTypes = channels.map((channel) => channel.type);
|
|
48506
|
-
for (const type of channelTypes) {
|
|
48507
|
-
const existing = channelProviders.get(type);
|
|
48508
|
-
if (existing) {
|
|
48509
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `channel type ${type} is already provided by ${existing.plugin}` });
|
|
48510
|
-
} else {
|
|
48511
|
-
channelProviders.set(type, { plugin: configPlugin.name, enabled: configPlugin.enabled });
|
|
48512
|
-
}
|
|
48513
|
-
}
|
|
48514
|
-
pushIfRelevant({
|
|
48515
|
-
level: configPlugin.enabled ? "ok" : "warn",
|
|
48516
|
-
plugin: configPlugin.name,
|
|
48517
|
-
message: configPlugin.enabled ? `plugin is installed and valid; channels: ${channelTypes.length > 0 ? channelTypes.join(", ") : "none"}` : `plugin is installed and valid but disabled; run xacpx plugin enable ${configPlugin.name}`
|
|
48518
|
-
});
|
|
48519
|
-
} catch (error2) {
|
|
48520
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
48521
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message });
|
|
48522
|
-
}
|
|
49856
|
+
const violation = findPluginSpecViolation(spec, platform);
|
|
49857
|
+
if (violation === "double-quote")
|
|
49858
|
+
return t().pluginCli.pluginSpecHasDoubleQuote(spec);
|
|
49859
|
+
if (violation === "percent-on-windows")
|
|
49860
|
+
return t().pluginCli.pluginSpecHasPercentOnWindows(spec);
|
|
48523
49861
|
}
|
|
48524
|
-
|
|
48525
|
-
for (const channel of input.config.channels) {
|
|
48526
|
-
if (builtInChannelTypes.has(channel.type))
|
|
48527
|
-
continue;
|
|
48528
|
-
const provider = channelProviders.get(channel.type);
|
|
48529
|
-
if (!provider) {
|
|
48530
|
-
if (!filterByName) {
|
|
48531
|
-
issues.push({
|
|
48532
|
-
level: "error",
|
|
48533
|
-
message: `channel ${channel.type} is configured but no enabled plugin provides it; run xacpx plugin add ${suggestedPluginPackageForChannel(channel.type)} or another plugin that provides type "${channel.type}"`
|
|
48534
|
-
});
|
|
48535
|
-
}
|
|
48536
|
-
continue;
|
|
48537
|
-
}
|
|
48538
|
-
if (!provider.enabled) {
|
|
48539
|
-
pushIfRelevant({
|
|
48540
|
-
level: "error",
|
|
48541
|
-
plugin: provider.plugin,
|
|
48542
|
-
message: `channel ${channel.type} is configured but provider plugin is disabled; run xacpx plugin enable ${provider.plugin}`
|
|
48543
|
-
});
|
|
48544
|
-
}
|
|
48545
|
-
}
|
|
48546
|
-
return issues;
|
|
49862
|
+
return null;
|
|
48547
49863
|
}
|
|
48548
|
-
|
|
48549
|
-
// src/plugins/plugin-cli.ts
|
|
48550
|
-
init_known_plugins();
|
|
48551
|
-
init_plugin_renames();
|
|
48552
|
-
init_i18n();
|
|
48553
49864
|
function looksLikePath(spec) {
|
|
48554
49865
|
return spec === "." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || spec.startsWith(".\\") || spec.startsWith("..\\") || spec.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(spec) || isAbsolute(spec);
|
|
48555
49866
|
}
|
|
48556
49867
|
async function readDependencyEntries2(pluginHome) {
|
|
48557
49868
|
try {
|
|
48558
|
-
const raw = await
|
|
49869
|
+
const raw = await readFile11(join13(pluginHome, "package.json"), "utf8");
|
|
48559
49870
|
const parsed = JSON.parse(raw);
|
|
48560
49871
|
const out = {};
|
|
48561
49872
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -48581,7 +49892,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
|
|
|
48581
49892
|
return name;
|
|
48582
49893
|
}
|
|
48583
49894
|
try {
|
|
48584
|
-
const raw = await
|
|
49895
|
+
const raw = await readFile11(join13(installSpec, "package.json"), "utf8");
|
|
48585
49896
|
const parsed = JSON.parse(raw);
|
|
48586
49897
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
48587
49898
|
return parsed.name.trim();
|
|
@@ -48724,6 +50035,11 @@ async function addPlugin(packageSpec, rawArgs, deps) {
|
|
|
48724
50035
|
deps.print(t().pluginCli.unrecognizedArgs(flags.rest.join(" ")));
|
|
48725
50036
|
return 1;
|
|
48726
50037
|
}
|
|
50038
|
+
const invalidSpec = invalidSpecMessage([packageSpec, flags.version], deps.platform ?? process.platform);
|
|
50039
|
+
if (invalidSpec) {
|
|
50040
|
+
deps.print(invalidSpec);
|
|
50041
|
+
return 1;
|
|
50042
|
+
}
|
|
48727
50043
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
48728
50044
|
await ensurePluginHome(pluginHome);
|
|
48729
50045
|
const installSpec = looksLikePath(packageSpec) && !isAbsolute(packageSpec) ? resolve(process.cwd(), packageSpec) : packageSpec;
|
|
@@ -48761,7 +50077,7 @@ async function addPlugin(packageSpec, rawArgs, deps) {
|
|
|
48761
50077
|
} else {
|
|
48762
50078
|
config4.plugins = [...config4.plugins, next];
|
|
48763
50079
|
}
|
|
48764
|
-
await deps.
|
|
50080
|
+
await deps.savePlugins(config4.plugins);
|
|
48765
50081
|
deps.print(t().pluginCli.pluginInstalled(recordedName));
|
|
48766
50082
|
if (summary.channels.length > 0) {
|
|
48767
50083
|
deps.print(t().pluginCli.providesChannels(summary.channels.join(", ")));
|
|
@@ -48787,7 +50103,7 @@ async function removePlugin(packageName, rawArgs, deps) {
|
|
|
48787
50103
|
}
|
|
48788
50104
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
48789
50105
|
const validate = deps.validateInstalledPlugin ?? ((name) => validateInstalledPluginDefault(name, pluginHome));
|
|
48790
|
-
const guard = await dependencyGuard(
|
|
50106
|
+
const guard = await dependencyGuard(existing.name, config4, validate);
|
|
48791
50107
|
if (!guard.allow) {
|
|
48792
50108
|
if (guard.reason)
|
|
48793
50109
|
deps.print(guard.reason);
|
|
@@ -48797,14 +50113,14 @@ async function removePlugin(packageName, rawArgs, deps) {
|
|
|
48797
50113
|
await removePluginPackage({ packageName: name, pluginHome });
|
|
48798
50114
|
});
|
|
48799
50115
|
try {
|
|
48800
|
-
await remove(
|
|
50116
|
+
await remove(existing.name);
|
|
48801
50117
|
} catch (error2) {
|
|
48802
|
-
deps.print(t().pluginCli.pluginUninstallFailed(
|
|
50118
|
+
deps.print(t().pluginCli.pluginUninstallFailed(existing.name, describeError(error2)));
|
|
48803
50119
|
return 1;
|
|
48804
50120
|
}
|
|
48805
|
-
config4.plugins = config4.plugins.filter((entry) => entry.name !==
|
|
48806
|
-
await deps.
|
|
48807
|
-
deps.print(t().pluginCli.pluginRemoved(
|
|
50121
|
+
config4.plugins = config4.plugins.filter((entry) => entry.name !== existing.name);
|
|
50122
|
+
await deps.savePlugins(config4.plugins);
|
|
50123
|
+
deps.print(t().pluginCli.pluginRemoved(existing.name));
|
|
48808
50124
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48809
50125
|
}
|
|
48810
50126
|
async function updatePlugins(args, deps) {
|
|
@@ -48824,6 +50140,11 @@ async function updatePlugins(args, deps) {
|
|
|
48824
50140
|
deps.print("--all cannot be combined with --version");
|
|
48825
50141
|
return 1;
|
|
48826
50142
|
}
|
|
50143
|
+
const invalidSpec = invalidSpecMessage([flags.version], deps.platform ?? process.platform);
|
|
50144
|
+
if (invalidSpec) {
|
|
50145
|
+
deps.print(invalidSpec);
|
|
50146
|
+
return 1;
|
|
50147
|
+
}
|
|
48827
50148
|
const config4 = await deps.loadConfig();
|
|
48828
50149
|
ensurePluginsArray(config4);
|
|
48829
50150
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
@@ -48880,7 +50201,7 @@ async function updatePlugins(args, deps) {
|
|
|
48880
50201
|
deps.print(t().pluginCli.providesChannels(summary.channels.join(", ")));
|
|
48881
50202
|
}
|
|
48882
50203
|
}
|
|
48883
|
-
await deps.
|
|
50204
|
+
await deps.savePlugins(config4.plugins);
|
|
48884
50205
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48885
50206
|
}
|
|
48886
50207
|
async function setPluginEnabled(packageName, enabled, rawArgs, deps) {
|
|
@@ -48911,7 +50232,7 @@ async function setPluginEnabled(packageName, enabled, rawArgs, deps) {
|
|
|
48911
50232
|
}
|
|
48912
50233
|
}
|
|
48913
50234
|
existing.enabled = enabled;
|
|
48914
|
-
await deps.
|
|
50235
|
+
await deps.savePlugins(config4.plugins);
|
|
48915
50236
|
deps.print(t().pluginCli.pluginEnabledToggled(packageName, enabled));
|
|
48916
50237
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48917
50238
|
}
|
|
@@ -49429,7 +50750,9 @@ async function defaultUpdate(args, input) {
|
|
|
49429
50750
|
const store = await createCliConfigStore();
|
|
49430
50751
|
const deps = {
|
|
49431
50752
|
loadConfig: async () => await store.load(),
|
|
49432
|
-
|
|
50753
|
+
savePlugins: async (plugins) => {
|
|
50754
|
+
await store.replacePlugins(plugins);
|
|
50755
|
+
},
|
|
49433
50756
|
readCurrentVersion: readVersion,
|
|
49434
50757
|
print: input.print,
|
|
49435
50758
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
@@ -49438,6 +50761,27 @@ async function defaultUpdate(args, input) {
|
|
|
49438
50761
|
};
|
|
49439
50762
|
return await handleUpdateCli(args, deps);
|
|
49440
50763
|
}
|
|
50764
|
+
function warnStateLoadReport(store, writeStderr = (text) => process.stderr.write(text)) {
|
|
50765
|
+
const report = store.lastLoadReport;
|
|
50766
|
+
if (!report)
|
|
50767
|
+
return;
|
|
50768
|
+
for (const record3 of report.dropped) {
|
|
50769
|
+
writeStderr(`[xacpx] state.record_quarantined section=${record3.section}${record3.key ? ` key=${record3.key}` : ""} reason=${record3.reason}
|
|
50770
|
+
`);
|
|
50771
|
+
}
|
|
50772
|
+
if (report.corruptPath) {
|
|
50773
|
+
writeStderr(`[xacpx] state.file_corrupt unreadable state.json renamed to ${report.corruptPath}
|
|
50774
|
+
`);
|
|
50775
|
+
}
|
|
50776
|
+
if (report.quarantinePath) {
|
|
50777
|
+
writeStderr(`[xacpx] state.file_quarantined original state.json backed up to ${report.quarantinePath}
|
|
50778
|
+
`);
|
|
50779
|
+
}
|
|
50780
|
+
if (report.backupError) {
|
|
50781
|
+
writeStderr(`[xacpx] state.quarantine_backup_failed ${report.backupError}
|
|
50782
|
+
`);
|
|
50783
|
+
}
|
|
50784
|
+
}
|
|
49441
50785
|
async function runOnboardingBeforeStart(input) {
|
|
49442
50786
|
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
49443
50787
|
await ensureConfigExists(runtimePaths.configPath);
|
|
@@ -49445,10 +50789,14 @@ async function runOnboardingBeforeStart(input) {
|
|
|
49445
50789
|
const stateStore = new StateStore(runtimePaths.statePath);
|
|
49446
50790
|
const config4 = await configStore.load();
|
|
49447
50791
|
const state = await stateStore.load();
|
|
50792
|
+
warnStateLoadReport(stateStore);
|
|
49448
50793
|
const result = await maybeRunFirstUseOnboarding({
|
|
49449
50794
|
config: config4,
|
|
49450
50795
|
state,
|
|
49451
|
-
|
|
50796
|
+
saveFirstRunConfig: async ({ workspace: workspace3, agent: agent3 }) => {
|
|
50797
|
+
await configStore.upsertWorkspace(workspace3.name, workspace3.cwd);
|
|
50798
|
+
await configStore.upsertAgent(agent3.name, agent3.config);
|
|
50799
|
+
},
|
|
49452
50800
|
deps: {
|
|
49453
50801
|
print: input.print,
|
|
49454
50802
|
cwd: input.cwd,
|
|
@@ -49659,7 +51007,7 @@ async function handleLaterCli(args, deps) {
|
|
|
49659
51007
|
}
|
|
49660
51008
|
async function laterList(print) {
|
|
49661
51009
|
const scheduled = await createCliScheduledTaskService();
|
|
49662
|
-
print(renderLaterList(scheduled.
|
|
51010
|
+
print(renderLaterList(scheduled.listPendingAllChats(), (alias) => toDisplaySessionAlias(alias)));
|
|
49663
51011
|
return 0;
|
|
49664
51012
|
}
|
|
49665
51013
|
async function laterCancel(rawId, print) {
|
|
@@ -49669,7 +51017,7 @@ async function laterCancel(rawId, print) {
|
|
|
49669
51017
|
return 1;
|
|
49670
51018
|
}
|
|
49671
51019
|
const scheduled = await createCliScheduledTaskService();
|
|
49672
|
-
const ok = await scheduled.
|
|
51020
|
+
const ok = await scheduled.cancelPendingAnyChat(id);
|
|
49673
51021
|
if (!ok) {
|
|
49674
51022
|
print(t().cli.laterNotFound(id));
|
|
49675
51023
|
print(t().cli.laterNotFoundHint);
|
|
@@ -49682,10 +51030,11 @@ async function createCliScheduledTaskService() {
|
|
|
49682
51030
|
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
49683
51031
|
const stateStore = new StateStore(runtimePaths.statePath);
|
|
49684
51032
|
const state = await stateStore.load();
|
|
51033
|
+
warnStateLoadReport(stateStore);
|
|
49685
51034
|
return new ScheduledTaskService(state, stateStore);
|
|
49686
51035
|
}
|
|
49687
51036
|
function resolveConfigPathForCurrentEnv() {
|
|
49688
|
-
return coreEnv("CONFIG") ??
|
|
51037
|
+
return coreEnv("CONFIG") ?? join22(coreHomeDir(requireHome2()), "config.json");
|
|
49689
51038
|
}
|
|
49690
51039
|
function resolveDaemonPathsForCurrentConfig() {
|
|
49691
51040
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
@@ -49784,14 +51133,12 @@ async function createFirstRunSession(runtime, plan) {
|
|
|
49784
51133
|
}
|
|
49785
51134
|
async function rollbackFirstRunConfig(runtime, plan) {
|
|
49786
51135
|
try {
|
|
49787
|
-
|
|
49788
|
-
|
|
49789
|
-
delete config4.workspaces[plan.workspace];
|
|
51136
|
+
if (!plan.rollback.workspaceExisted) {
|
|
51137
|
+
await runtime.configStore.removeWorkspace(plan.workspace);
|
|
49790
51138
|
}
|
|
49791
|
-
if (!plan.rollback.agentExisted
|
|
49792
|
-
|
|
51139
|
+
if (!plan.rollback.agentExisted) {
|
|
51140
|
+
await runtime.configStore.removeAgent(plan.agent);
|
|
49793
51141
|
}
|
|
49794
|
-
await runtime.configStore.save(config4);
|
|
49795
51142
|
} catch (error2) {
|
|
49796
51143
|
await runtime.logger.error("onboarding.rollback_failed", "failed to roll back first-run config", {
|
|
49797
51144
|
alias: plan.alias,
|
|
@@ -49823,7 +51170,9 @@ async function defaultMcpStdio(args, deps = {}) {
|
|
|
49823
51170
|
await ensureConfigExists(runtimePaths.configPath);
|
|
49824
51171
|
const config4 = await loadConfig(runtimePaths.configPath);
|
|
49825
51172
|
availableAgents = Object.keys(config4.agents);
|
|
49826
|
-
const
|
|
51173
|
+
const stateStore = new StateStore(runtimePaths.statePath);
|
|
51174
|
+
const state = await stateStore.load();
|
|
51175
|
+
warnStateLoadReport(stateStore, deps.stderr ?? ((text) => process.stderr.write(text)));
|
|
49827
51176
|
const resolveIdentity = createMcpStdioIdentityResolver({
|
|
49828
51177
|
parsedCoordinatorSession,
|
|
49829
51178
|
sourceHandle,
|
|
@@ -49887,7 +51236,9 @@ async function createChannelCliDeps(input) {
|
|
|
49887
51236
|
const controller = input.controller ?? createDefaultController();
|
|
49888
51237
|
const base = {
|
|
49889
51238
|
loadConfig: async () => await store.load(),
|
|
49890
|
-
|
|
51239
|
+
saveChannels: async (channels) => {
|
|
51240
|
+
await store.replaceChannels(channels);
|
|
51241
|
+
},
|
|
49891
51242
|
print: input.print,
|
|
49892
51243
|
stderr: input.stderr ?? ((text) => process.stderr.write(text)),
|
|
49893
51244
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
@@ -49910,7 +51261,9 @@ async function createPluginCliDeps(input) {
|
|
|
49910
51261
|
const controller = input.controller ?? createDefaultController();
|
|
49911
51262
|
const base = {
|
|
49912
51263
|
loadConfig: async () => await store.load(),
|
|
49913
|
-
|
|
51264
|
+
savePlugins: async (plugins) => {
|
|
51265
|
+
await store.replacePlugins(plugins);
|
|
51266
|
+
},
|
|
49914
51267
|
print: input.print,
|
|
49915
51268
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
49916
51269
|
promptText: input.promptText ?? defaultPromptText,
|
|
@@ -50028,7 +51381,7 @@ function decodeFirstRunOnboarding(raw) {
|
|
|
50028
51381
|
return null;
|
|
50029
51382
|
}
|
|
50030
51383
|
function requireHome2() {
|
|
50031
|
-
const home = process.env.HOME ??
|
|
51384
|
+
const home = process.env.HOME ?? homedir15();
|
|
50032
51385
|
if (!home) {
|
|
50033
51386
|
throw new Error("Unable to resolve the current user home directory");
|
|
50034
51387
|
}
|
|
@@ -50052,7 +51405,7 @@ function safeDaemonLogPaths() {
|
|
|
50052
51405
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
50053
51406
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
50054
51407
|
return {
|
|
50055
|
-
appLog:
|
|
51408
|
+
appLog: join22(dirname14(configPath), "runtime", "app.log"),
|
|
50056
51409
|
stderrLog: paths.stderrLog
|
|
50057
51410
|
};
|
|
50058
51411
|
} catch {
|
|
@@ -50076,6 +51429,9 @@ function parseDoctorArgs(args) {
|
|
|
50076
51429
|
case "--smoke":
|
|
50077
51430
|
options.smoke = true;
|
|
50078
51431
|
break;
|
|
51432
|
+
case "--fix":
|
|
51433
|
+
options.fix = true;
|
|
51434
|
+
break;
|
|
50079
51435
|
case "--agent": {
|
|
50080
51436
|
const value = args[index + 1];
|
|
50081
51437
|
if (!value || value.startsWith("--")) {
|