@ganglion/xacpx 0.9.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/bridge-main.js +170 -78
- package/dist/channels/types.d.ts +11 -0
- package/dist/channels/weixin-channel.d.ts +8 -0
- package/dist/cli.js +1236 -501
- 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
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
return
|
|
4844
|
+
assertSafeConfigKey(name);
|
|
4845
|
+
return await this.patchRaw((raw) => {
|
|
4846
|
+
deleteRecordEntry(raw, "agents", name);
|
|
4847
|
+
});
|
|
4848
|
+
}
|
|
4849
|
+
async updateTransport(patch) {
|
|
4850
|
+
return await this.patchRaw((raw) => {
|
|
4851
|
+
applyRecordPatch(ensureRecordAt(raw, "transport"), patch);
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4854
|
+
async updateChannel(patch) {
|
|
4855
|
+
return await this.patchRaw((raw) => {
|
|
4856
|
+
applyRecordPatch(ensureRecordAt(raw, "channel"), patch);
|
|
4857
|
+
});
|
|
4858
|
+
}
|
|
4859
|
+
async replacePlugins(plugins) {
|
|
4860
|
+
return await this.patchRaw((raw) => {
|
|
4861
|
+
raw.plugins = clonePlain(plugins);
|
|
4862
|
+
});
|
|
4863
|
+
}
|
|
4864
|
+
async replaceChannels(channels) {
|
|
4865
|
+
return await this.patchRaw((raw) => {
|
|
4866
|
+
raw.channels = clonePlain(channels);
|
|
4867
|
+
});
|
|
4868
|
+
}
|
|
4869
|
+
async patchRaw(mutate) {
|
|
4870
|
+
return await withPrivateFileLock(this.path, async (writeLocked) => {
|
|
4871
|
+
const { raw, existed } = await this.readRaw();
|
|
4872
|
+
const doc = existed ? raw : { transport: {}, agents: {}, workspaces: {} };
|
|
4873
|
+
mutate(doc);
|
|
4874
|
+
const parsed = parseConfig({ transport: {}, agents: {}, workspaces: {}, ...doc });
|
|
4875
|
+
await writeLocked(serializeRawConfig(doc));
|
|
4876
|
+
return parsed;
|
|
4877
|
+
});
|
|
4813
4878
|
}
|
|
4879
|
+
async readRaw() {
|
|
4880
|
+
let text;
|
|
4881
|
+
try {
|
|
4882
|
+
text = await readFile2(this.path, "utf8");
|
|
4883
|
+
} catch (error) {
|
|
4884
|
+
if (isMissingFileError(error)) {
|
|
4885
|
+
return { raw: {}, existed: false };
|
|
4886
|
+
}
|
|
4887
|
+
throw error;
|
|
4888
|
+
}
|
|
4889
|
+
let parsed;
|
|
4890
|
+
try {
|
|
4891
|
+
parsed = JSON.parse(text);
|
|
4892
|
+
} catch (error) {
|
|
4893
|
+
throw new Error(`refusing to modify ${this.path}: it is not valid JSON (${error instanceof Error ? error.message : String(error)}); fix the file and retry`);
|
|
4894
|
+
}
|
|
4895
|
+
if (!isPlainRecord(parsed)) {
|
|
4896
|
+
throw new Error(`refusing to modify ${this.path}: the top level must be a JSON object`);
|
|
4897
|
+
}
|
|
4898
|
+
return { raw: parsed, existed: true };
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
function serializeRawConfig(raw) {
|
|
4902
|
+
return `${JSON.stringify(raw, null, 2)}
|
|
4903
|
+
`;
|
|
4904
|
+
}
|
|
4905
|
+
function readRawConfigValue(root, path2) {
|
|
4906
|
+
const lastKey = requireObjectKeyTail(path2);
|
|
4907
|
+
let container = root;
|
|
4908
|
+
for (const segment of path2.slice(0, -1)) {
|
|
4909
|
+
const next = descendForRead(container, segment);
|
|
4910
|
+
if (!next.ok) {
|
|
4911
|
+
return { present: false };
|
|
4912
|
+
}
|
|
4913
|
+
container = next.value;
|
|
4914
|
+
}
|
|
4915
|
+
if (!isPlainRecord(container) || !Object.hasOwn(container, lastKey)) {
|
|
4916
|
+
return { present: false };
|
|
4917
|
+
}
|
|
4918
|
+
return { present: true, value: container[lastKey] };
|
|
4919
|
+
}
|
|
4920
|
+
function setRawConfigValue(root, path2, value) {
|
|
4921
|
+
const lastKey = requireObjectKeyTail(path2);
|
|
4922
|
+
let container = root;
|
|
4923
|
+
for (let index = 0;index < path2.length - 1; index += 1) {
|
|
4924
|
+
const segment = path2[index];
|
|
4925
|
+
const nextSegment = path2[index + 1];
|
|
4926
|
+
container = descendForWrite(container, segment, typeof nextSegment !== "string", path2);
|
|
4927
|
+
}
|
|
4928
|
+
if (!isPlainRecord(container)) {
|
|
4929
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" does not address an object`);
|
|
4930
|
+
}
|
|
4931
|
+
container[lastKey] = value;
|
|
4932
|
+
}
|
|
4933
|
+
function unsetRawConfigValue(root, path2) {
|
|
4934
|
+
const lastKey = requireObjectKeyTail(path2);
|
|
4935
|
+
let container = root;
|
|
4936
|
+
for (const segment of path2.slice(0, -1)) {
|
|
4937
|
+
const next = descendForRead(container, segment);
|
|
4938
|
+
if (!next.ok) {
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
container = next.value;
|
|
4942
|
+
}
|
|
4943
|
+
if (isPlainRecord(container)) {
|
|
4944
|
+
delete container[lastKey];
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
function descendForRead(container, segment) {
|
|
4948
|
+
if (typeof segment === "string") {
|
|
4949
|
+
assertSafeConfigKey(segment);
|
|
4950
|
+
if (!isPlainRecord(container) || !Object.hasOwn(container, segment)) {
|
|
4951
|
+
return { ok: false };
|
|
4952
|
+
}
|
|
4953
|
+
return { ok: true, value: container[segment] };
|
|
4954
|
+
}
|
|
4955
|
+
if (!Array.isArray(container)) {
|
|
4956
|
+
return { ok: false };
|
|
4957
|
+
}
|
|
4958
|
+
const entry = container.find((item) => isPlainRecord(item) && item.id === segment.id);
|
|
4959
|
+
return entry === undefined ? { ok: false } : { ok: true, value: entry };
|
|
4960
|
+
}
|
|
4961
|
+
function descendForWrite(container, segment, nextIsArrayEntry, path2) {
|
|
4962
|
+
if (typeof segment === "string") {
|
|
4963
|
+
assertSafeConfigKey(segment);
|
|
4964
|
+
if (!isPlainRecord(container)) {
|
|
4965
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" does not address an object at "${segment}"`);
|
|
4966
|
+
}
|
|
4967
|
+
let existing = container[segment];
|
|
4968
|
+
if (existing === undefined) {
|
|
4969
|
+
existing = nextIsArrayEntry ? [] : {};
|
|
4970
|
+
container[segment] = existing;
|
|
4971
|
+
}
|
|
4972
|
+
if (!isPlainRecord(existing) && !Array.isArray(existing)) {
|
|
4973
|
+
throw new Error(`refusing to overwrite config key "${segment}" (path "${describeRawConfigPath(path2)}"): it is not an object`);
|
|
4974
|
+
}
|
|
4975
|
+
return existing;
|
|
4976
|
+
}
|
|
4977
|
+
if (!Array.isArray(container)) {
|
|
4978
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" expects an array before [id=${segment.id}]`);
|
|
4979
|
+
}
|
|
4980
|
+
const entry = container.find((item) => isPlainRecord(item) && item.id === segment.id);
|
|
4981
|
+
if (entry !== undefined) {
|
|
4982
|
+
return entry;
|
|
4983
|
+
}
|
|
4984
|
+
if (!segment.createWith) {
|
|
4985
|
+
throw new Error(`config path "${describeRawConfigPath(path2)}" has no entry with id "${segment.id}"`);
|
|
4986
|
+
}
|
|
4987
|
+
const created = clonePlain(segment.createWith);
|
|
4988
|
+
container.push(created);
|
|
4989
|
+
return created;
|
|
4990
|
+
}
|
|
4991
|
+
function requireObjectKeyTail(path2) {
|
|
4992
|
+
const last = path2[path2.length - 1];
|
|
4993
|
+
if (typeof last !== "string") {
|
|
4994
|
+
throw new Error("raw config path must end with an object key");
|
|
4995
|
+
}
|
|
4996
|
+
assertSafeConfigKey(last);
|
|
4997
|
+
return last;
|
|
4998
|
+
}
|
|
4999
|
+
function assertSafeConfigKey(key) {
|
|
5000
|
+
if (PROTOTYPE_POLLUTING_KEYS.has(key)) {
|
|
5001
|
+
throw new Error(`refusing to use unsafe config key "${key}"`);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
function describeRawConfigPath(path2) {
|
|
5005
|
+
return path2.map((segment) => typeof segment === "string" ? segment : `[id=${segment.id}]`).join(".");
|
|
5006
|
+
}
|
|
5007
|
+
function ensureRecordAt(raw, key) {
|
|
5008
|
+
const existing = raw[key];
|
|
5009
|
+
if (existing === undefined) {
|
|
5010
|
+
const created = {};
|
|
5011
|
+
raw[key] = created;
|
|
5012
|
+
return created;
|
|
5013
|
+
}
|
|
5014
|
+
if (!isPlainRecord(existing)) {
|
|
5015
|
+
throw new Error(`refusing to overwrite config key "${key}": it is not a JSON object`);
|
|
5016
|
+
}
|
|
5017
|
+
return existing;
|
|
5018
|
+
}
|
|
5019
|
+
function deleteRecordEntry(raw, section, name) {
|
|
5020
|
+
const record = raw[section];
|
|
5021
|
+
if (isPlainRecord(record)) {
|
|
5022
|
+
delete record[name];
|
|
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";
|
|
4814
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;
|
|
@@ -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,7 +5691,7 @@ 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;
|
|
@@ -12124,10 +12371,24 @@ function createEmptyState() {
|
|
|
12124
12371
|
var init_types = () => {};
|
|
12125
12372
|
|
|
12126
12373
|
// src/state/state-store.ts
|
|
12127
|
-
import { readFile as
|
|
12374
|
+
import { readFile as readFile6, rename, writeFile as writeFile3 } from "node:fs/promises";
|
|
12128
12375
|
function isRecord2(value) {
|
|
12129
12376
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12130
12377
|
}
|
|
12378
|
+
function sectionRecord(value, section, dropped) {
|
|
12379
|
+
if (value === undefined) {
|
|
12380
|
+
return {};
|
|
12381
|
+
}
|
|
12382
|
+
if (!isRecord2(value)) {
|
|
12383
|
+
dropped.push({
|
|
12384
|
+
section,
|
|
12385
|
+
key: "",
|
|
12386
|
+
reason: `field "${section}" is not an object; reset to empty`
|
|
12387
|
+
});
|
|
12388
|
+
return {};
|
|
12389
|
+
}
|
|
12390
|
+
return value;
|
|
12391
|
+
}
|
|
12131
12392
|
function isString(value) {
|
|
12132
12393
|
return typeof value === "string";
|
|
12133
12394
|
}
|
|
@@ -12232,73 +12493,82 @@ function isHumanQuestionPackageRecord(value) {
|
|
|
12232
12493
|
const messages = value.messages;
|
|
12233
12494
|
return isString(value.packageId) && isString(value.coordinatorSession) && (value.status === "active" || value.status === "closed") && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.closedAt) && Array.isArray(initialTaskIds) && initialTaskIds.every(isString) && Array.isArray(openTaskIds) && openTaskIds.every(isString) && Array.isArray(resolvedTaskIds) && resolvedTaskIds.every(isString) && Array.isArray(messages) && messages.every(isHumanQuestionPackageMessageRecord) && isOptionalString(value.awaitingReplyMessageId);
|
|
12234
12495
|
}
|
|
12235
|
-
function parseOrchestrationState(raw,
|
|
12496
|
+
function parseOrchestrationState(raw, dropped) {
|
|
12236
12497
|
if (raw === undefined) {
|
|
12237
12498
|
return createEmptyOrchestrationState();
|
|
12238
12499
|
}
|
|
12239
12500
|
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"`);
|
|
12501
|
+
dropped.push({
|
|
12502
|
+
section: "orchestration",
|
|
12503
|
+
key: "",
|
|
12504
|
+
reason: 'field "orchestration" is not an object; reset to empty'
|
|
12505
|
+
});
|
|
12506
|
+
return createEmptyOrchestrationState();
|
|
12269
12507
|
}
|
|
12508
|
+
const tasks = sectionRecord(raw.tasks, "orchestration.tasks", dropped);
|
|
12509
|
+
const workerBindings = sectionRecord(raw.workerBindings, "orchestration.workerBindings", dropped);
|
|
12510
|
+
const groups = sectionRecord(raw.groups, "orchestration.groups", dropped);
|
|
12511
|
+
const humanQuestionPackages = sectionRecord(raw.humanQuestionPackages, "orchestration.humanQuestionPackages", dropped);
|
|
12512
|
+
const coordinatorQuestionState = sectionRecord(raw.coordinatorQuestionState, "orchestration.coordinatorQuestionState", dropped);
|
|
12513
|
+
const coordinatorRoutes = sectionRecord(raw.coordinatorRoutes, "orchestration.coordinatorRoutes", dropped);
|
|
12514
|
+
const externalCoordinators = sectionRecord(raw.externalCoordinators, "orchestration.externalCoordinators", dropped);
|
|
12270
12515
|
const parsedTasks = {};
|
|
12271
|
-
for (const [taskId, task] of Object.entries(tasks
|
|
12516
|
+
for (const [taskId, task] of Object.entries(tasks)) {
|
|
12272
12517
|
if (!isTaskRecord(task)) {
|
|
12273
|
-
|
|
12518
|
+
dropped.push({
|
|
12519
|
+
section: "orchestration.tasks",
|
|
12520
|
+
key: taskId,
|
|
12521
|
+
reason: "malformed orchestration task record"
|
|
12522
|
+
});
|
|
12523
|
+
continue;
|
|
12274
12524
|
}
|
|
12275
12525
|
parsedTasks[taskId] = task;
|
|
12276
12526
|
}
|
|
12277
12527
|
const parsedWorkerBindings = {};
|
|
12278
|
-
for (const [workerSession, binding] of Object.entries(workerBindings
|
|
12528
|
+
for (const [workerSession, binding] of Object.entries(workerBindings)) {
|
|
12279
12529
|
if (!isWorkerBindingRecord(binding)) {
|
|
12280
|
-
|
|
12530
|
+
dropped.push({
|
|
12531
|
+
section: "orchestration.workerBindings",
|
|
12532
|
+
key: workerSession,
|
|
12533
|
+
reason: "malformed orchestration worker binding record"
|
|
12534
|
+
});
|
|
12535
|
+
continue;
|
|
12281
12536
|
}
|
|
12282
12537
|
parsedWorkerBindings[workerSession] = binding;
|
|
12283
12538
|
}
|
|
12284
12539
|
const parsedGroups = {};
|
|
12285
|
-
for (const [groupId, group] of Object.entries(groups
|
|
12540
|
+
for (const [groupId, group] of Object.entries(groups)) {
|
|
12286
12541
|
if (!isGroupRecord(group)) {
|
|
12287
|
-
|
|
12542
|
+
dropped.push({
|
|
12543
|
+
section: "orchestration.groups",
|
|
12544
|
+
key: groupId,
|
|
12545
|
+
reason: "malformed orchestration group record"
|
|
12546
|
+
});
|
|
12547
|
+
continue;
|
|
12288
12548
|
}
|
|
12289
12549
|
parsedGroups[groupId] = group;
|
|
12290
12550
|
}
|
|
12291
12551
|
const parsedHumanQuestionPackages = {};
|
|
12292
|
-
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages
|
|
12552
|
+
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages)) {
|
|
12293
12553
|
if (!isHumanQuestionPackageRecord(packageRecord)) {
|
|
12294
|
-
|
|
12554
|
+
dropped.push({
|
|
12555
|
+
section: "orchestration.humanQuestionPackages",
|
|
12556
|
+
key: packageId,
|
|
12557
|
+
reason: "malformed human question package record"
|
|
12558
|
+
});
|
|
12559
|
+
continue;
|
|
12295
12560
|
}
|
|
12296
12561
|
parsedHumanQuestionPackages[packageId] = packageRecord;
|
|
12297
12562
|
}
|
|
12298
12563
|
const parsedCoordinatorQuestionState = {};
|
|
12299
|
-
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState
|
|
12564
|
+
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState)) {
|
|
12300
12565
|
if (!isCoordinatorQuestionStateRecord(questionState)) {
|
|
12301
|
-
|
|
12566
|
+
dropped.push({
|
|
12567
|
+
section: "orchestration.coordinatorQuestionState",
|
|
12568
|
+
key: coordinatorSession,
|
|
12569
|
+
reason: "malformed coordinator question state record"
|
|
12570
|
+
});
|
|
12571
|
+
continue;
|
|
12302
12572
|
}
|
|
12303
12573
|
parsedCoordinatorQuestionState[coordinatorSession] = {
|
|
12304
12574
|
activePackageId: questionState.activePackageId,
|
|
@@ -12306,19 +12576,34 @@ function parseOrchestrationState(raw, path3) {
|
|
|
12306
12576
|
};
|
|
12307
12577
|
}
|
|
12308
12578
|
const parsedCoordinatorRoutes = {};
|
|
12309
|
-
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes
|
|
12579
|
+
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes)) {
|
|
12310
12580
|
if (!isCoordinatorRouteContextRecord(route)) {
|
|
12311
|
-
|
|
12581
|
+
dropped.push({
|
|
12582
|
+
section: "orchestration.coordinatorRoutes",
|
|
12583
|
+
key: coordinatorSession,
|
|
12584
|
+
reason: "malformed coordinator route record"
|
|
12585
|
+
});
|
|
12586
|
+
continue;
|
|
12312
12587
|
}
|
|
12313
12588
|
parsedCoordinatorRoutes[coordinatorSession] = route;
|
|
12314
12589
|
}
|
|
12315
12590
|
const parsedExternalCoordinators = {};
|
|
12316
|
-
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators
|
|
12591
|
+
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators)) {
|
|
12317
12592
|
if (!isExternalCoordinatorRecord(externalCoordinator)) {
|
|
12318
|
-
|
|
12593
|
+
dropped.push({
|
|
12594
|
+
section: "orchestration.externalCoordinators",
|
|
12595
|
+
key: coordinatorSession,
|
|
12596
|
+
reason: "malformed external coordinator record"
|
|
12597
|
+
});
|
|
12598
|
+
continue;
|
|
12319
12599
|
}
|
|
12320
12600
|
if (externalCoordinator.coordinatorSession !== coordinatorSession) {
|
|
12321
|
-
|
|
12601
|
+
dropped.push({
|
|
12602
|
+
section: "orchestration.externalCoordinators",
|
|
12603
|
+
key: coordinatorSession,
|
|
12604
|
+
reason: `coordinatorSession "${externalCoordinator.coordinatorSession}" does not match map key`
|
|
12605
|
+
});
|
|
12606
|
+
continue;
|
|
12322
12607
|
}
|
|
12323
12608
|
parsedExternalCoordinators[coordinatorSession] = externalCoordinator;
|
|
12324
12609
|
}
|
|
@@ -12344,11 +12629,12 @@ function isSessionRecord(value) {
|
|
|
12344
12629
|
}
|
|
12345
12630
|
return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isSessionSource(value.source) && isOptionalString(value.agent_session_id) && isOptionalString(value.agent_session_title) && isOptionalString(value.agent_session_updated_at) && isOptionalString(value.attached_at) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode2(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
|
|
12346
12631
|
}
|
|
12347
|
-
function parseSessions(raw,
|
|
12632
|
+
function parseSessions(raw, dropped) {
|
|
12348
12633
|
const sessions = {};
|
|
12349
12634
|
for (const [alias, value] of Object.entries(raw)) {
|
|
12350
12635
|
if (!isSessionRecord(value)) {
|
|
12351
|
-
|
|
12636
|
+
dropped.push({ section: "sessions", key: alias, reason: "malformed session record" });
|
|
12637
|
+
continue;
|
|
12352
12638
|
}
|
|
12353
12639
|
sessions[alias] = value;
|
|
12354
12640
|
}
|
|
@@ -12357,11 +12643,12 @@ function parseSessions(raw, path3) {
|
|
|
12357
12643
|
function isChatContextRecord(value) {
|
|
12358
12644
|
return isRecord2(value) && isString(value.current_session);
|
|
12359
12645
|
}
|
|
12360
|
-
function parseChatContexts(raw,
|
|
12646
|
+
function parseChatContexts(raw, dropped) {
|
|
12361
12647
|
const chatContexts = {};
|
|
12362
12648
|
for (const [chatKey, value] of Object.entries(raw)) {
|
|
12363
12649
|
if (!isChatContextRecord(value)) {
|
|
12364
|
-
|
|
12650
|
+
dropped.push({ section: "chat_contexts", key: chatKey, reason: "malformed chat context record" });
|
|
12651
|
+
continue;
|
|
12365
12652
|
}
|
|
12366
12653
|
chatContexts[chatKey] = value;
|
|
12367
12654
|
}
|
|
@@ -12402,92 +12689,177 @@ function isScheduledTaskRecord(value) {
|
|
|
12402
12689
|
return false;
|
|
12403
12690
|
return isString(value.id) && isString(value.chat_key) && isString(value.session_alias) && isString(value.execute_at) && isString(value.message) && isScheduledTaskStatus(value.status) && isString(value.created_at) && isOptionalString(value.account_id) && isOptionalString(value.reply_context_token) && isOptionalString(value.source_label) && isOptionalString(value.triggered_at) && isOptionalString(value.executed_at) && isOptionalString(value.cancelled_at) && isOptionalString(value.missed_at) && isOptionalString(value.failed_at) && isOptionalString(value.last_error) && isOptionalScheduledSessionMode(value.session_mode) && isOptionalString(value.agent) && isOptionalString(value.workspace);
|
|
12404
12691
|
}
|
|
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
|
-
}
|
|
12692
|
+
function parseScheduledTasks(raw, dropped) {
|
|
12693
|
+
const source = sectionRecord(raw, "scheduled_tasks", dropped);
|
|
12411
12694
|
const tasks = {};
|
|
12412
|
-
for (const [id, value] of Object.entries(
|
|
12695
|
+
for (const [id, value] of Object.entries(source)) {
|
|
12413
12696
|
if (!isScheduledTaskRecord(value) || value.id !== id) {
|
|
12414
|
-
|
|
12697
|
+
dropped.push({ section: "scheduled_tasks", key: id, reason: "malformed scheduled task record" });
|
|
12698
|
+
continue;
|
|
12415
12699
|
}
|
|
12416
12700
|
tasks[id] = value;
|
|
12417
12701
|
}
|
|
12418
12702
|
return tasks;
|
|
12419
12703
|
}
|
|
12420
|
-
function parseState(raw, path3) {
|
|
12704
|
+
function parseState(raw, path3, dropped = []) {
|
|
12421
12705
|
if (!isRecord2(raw)) {
|
|
12422
12706
|
throw new Error(`state file "${path3}" must contain a JSON object`);
|
|
12423
12707
|
}
|
|
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);
|
|
12708
|
+
const parsedSessions = parseSessions(sectionRecord(raw.sessions, "sessions", dropped), dropped);
|
|
12709
|
+
const orchestration3 = parseOrchestrationState(raw.orchestration, dropped);
|
|
12710
|
+
repairExternalCoordinatorIdentityCollisions(parsedSessions, orchestration3, dropped);
|
|
12435
12711
|
return {
|
|
12436
12712
|
sessions: parsedSessions,
|
|
12437
|
-
chat_contexts: parseChatContexts(
|
|
12713
|
+
chat_contexts: parseChatContexts(sectionRecord(raw.chat_contexts, "chat_contexts", dropped), dropped),
|
|
12438
12714
|
native_session_lists: parseNativeSessionLists(raw.native_session_lists),
|
|
12439
12715
|
orchestration: orchestration3,
|
|
12440
|
-
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks,
|
|
12716
|
+
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks, dropped)
|
|
12441
12717
|
};
|
|
12442
12718
|
}
|
|
12443
|
-
function
|
|
12719
|
+
function repairExternalCoordinatorIdentityCollisions(sessions, orchestration3, dropped) {
|
|
12444
12720
|
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`);
|
|
12721
|
+
const conflict = findExternalCoordinatorConflict(coordinatorSession, sessions, orchestration3);
|
|
12722
|
+
if (!conflict) {
|
|
12723
|
+
continue;
|
|
12453
12724
|
}
|
|
12725
|
+
delete orchestration3.externalCoordinators[coordinatorSession];
|
|
12726
|
+
dropped.push({
|
|
12727
|
+
section: "orchestration.externalCoordinators",
|
|
12728
|
+
key: coordinatorSession,
|
|
12729
|
+
reason: `conflicts with ${conflict}; dropped (re-registered on next coordinator connect)`
|
|
12730
|
+
});
|
|
12454
12731
|
}
|
|
12455
12732
|
}
|
|
12733
|
+
function findExternalCoordinatorConflict(coordinatorSession, sessions, orchestration3) {
|
|
12734
|
+
if (Object.values(sessions).some((session3) => session3.transport_session === coordinatorSession)) {
|
|
12735
|
+
return "a logical session";
|
|
12736
|
+
}
|
|
12737
|
+
if (orchestration3.workerBindings[coordinatorSession]) {
|
|
12738
|
+
return "a worker binding";
|
|
12739
|
+
}
|
|
12740
|
+
if (Object.values(orchestration3.tasks).some((task) => task.workerSession === coordinatorSession && (!isTerminalTaskStatus(task.status) || task.reviewPending !== undefined))) {
|
|
12741
|
+
return "an active task worker session";
|
|
12742
|
+
}
|
|
12743
|
+
return null;
|
|
12744
|
+
}
|
|
12456
12745
|
function isTerminalTaskStatus(status) {
|
|
12457
12746
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
12458
12747
|
}
|
|
12459
12748
|
|
|
12460
12749
|
class StateStore {
|
|
12461
12750
|
path;
|
|
12462
|
-
|
|
12751
|
+
options;
|
|
12752
|
+
loadReport = null;
|
|
12753
|
+
constructor(path3, options = {}) {
|
|
12463
12754
|
this.path = path3;
|
|
12755
|
+
this.options = options;
|
|
12756
|
+
}
|
|
12757
|
+
get lastLoadReport() {
|
|
12758
|
+
return this.loadReport;
|
|
12464
12759
|
}
|
|
12465
12760
|
async load() {
|
|
12761
|
+
this.loadReport = null;
|
|
12762
|
+
const read = await this.readAndParse();
|
|
12763
|
+
if (read.kind === "absent") {
|
|
12764
|
+
return createEmptyState();
|
|
12765
|
+
}
|
|
12766
|
+
if (read.kind === "corrupt") {
|
|
12767
|
+
return await this.recoverFromCorruptFile(read.reason);
|
|
12768
|
+
}
|
|
12769
|
+
if (read.dropped.length === 0) {
|
|
12770
|
+
return read.state;
|
|
12771
|
+
}
|
|
12772
|
+
const report = { dropped: read.dropped };
|
|
12773
|
+
const quarantinePath = `${this.path}.quarantine-${this.fileTimestamp()}`;
|
|
12466
12774
|
try {
|
|
12467
|
-
const
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
}
|
|
12479
|
-
|
|
12775
|
+
const written = await (this.options.writeBackup ?? defaultWriteBackup)(quarantinePath, read.content);
|
|
12776
|
+
report.quarantinePath = typeof written === "string" ? written : quarantinePath;
|
|
12777
|
+
} catch (error2) {
|
|
12778
|
+
report.backupError = error2 instanceof Error ? error2.message : String(error2);
|
|
12779
|
+
}
|
|
12780
|
+
this.loadReport = report;
|
|
12781
|
+
return read.state;
|
|
12782
|
+
}
|
|
12783
|
+
async inspect() {
|
|
12784
|
+
const read = await this.readAndParse();
|
|
12785
|
+
if (read.kind === "absent") {
|
|
12786
|
+
return { state: createEmptyState(), report: null };
|
|
12787
|
+
}
|
|
12788
|
+
if (read.kind === "corrupt") {
|
|
12789
|
+
return {
|
|
12790
|
+
state: createEmptyState(),
|
|
12791
|
+
report: { dropped: [{ section: "file", key: this.path, reason: read.reason }] }
|
|
12792
|
+
};
|
|
12793
|
+
}
|
|
12794
|
+
return {
|
|
12795
|
+
state: read.state,
|
|
12796
|
+
report: read.dropped.length > 0 ? { dropped: read.dropped } : null
|
|
12797
|
+
};
|
|
12798
|
+
}
|
|
12799
|
+
async readAndParse() {
|
|
12800
|
+
let content;
|
|
12801
|
+
try {
|
|
12802
|
+
content = await readFile6(this.path, "utf8");
|
|
12480
12803
|
} catch (error2) {
|
|
12481
12804
|
if (error2.code === "ENOENT") {
|
|
12482
|
-
return
|
|
12805
|
+
return { kind: "absent" };
|
|
12483
12806
|
}
|
|
12484
12807
|
throw error2;
|
|
12485
12808
|
}
|
|
12809
|
+
if (content.trim() === "") {
|
|
12810
|
+
return { kind: "absent" };
|
|
12811
|
+
}
|
|
12812
|
+
let parsed;
|
|
12813
|
+
try {
|
|
12814
|
+
parsed = JSON.parse(content);
|
|
12815
|
+
} catch (error2) {
|
|
12816
|
+
return {
|
|
12817
|
+
kind: "corrupt",
|
|
12818
|
+
reason: `invalid JSON: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
12819
|
+
};
|
|
12820
|
+
}
|
|
12821
|
+
if (!isRecord2(parsed)) {
|
|
12822
|
+
return { kind: "corrupt", reason: "top-level value is not an object" };
|
|
12823
|
+
}
|
|
12824
|
+
const dropped = [];
|
|
12825
|
+
return { kind: "parsed", state: parseState(parsed, this.path, dropped), dropped, content };
|
|
12486
12826
|
}
|
|
12487
12827
|
async save(state) {
|
|
12488
|
-
await writePrivateFileAtomic(this.path, JSON.stringify(state, null, 2));
|
|
12828
|
+
await writePrivateFileAtomic(this.path, JSON.stringify({ version: STATE_FILE_VERSION, ...state }, null, 2));
|
|
12829
|
+
}
|
|
12830
|
+
fileTimestamp() {
|
|
12831
|
+
const now = this.options.now?.() ?? new Date;
|
|
12832
|
+
return now.toISOString().replace(/[:.]/g, "-");
|
|
12833
|
+
}
|
|
12834
|
+
async recoverFromCorruptFile(reason) {
|
|
12835
|
+
const corruptPath = `${this.path}.corrupt-${this.fileTimestamp()}`;
|
|
12836
|
+
const report = {
|
|
12837
|
+
dropped: [{ section: "file", key: this.path, reason }]
|
|
12838
|
+
};
|
|
12839
|
+
try {
|
|
12840
|
+
await rename(this.path, corruptPath);
|
|
12841
|
+
report.corruptPath = corruptPath;
|
|
12842
|
+
} catch (error2) {
|
|
12843
|
+
report.backupError = error2 instanceof Error ? error2.message : String(error2);
|
|
12844
|
+
}
|
|
12845
|
+
this.loadReport = report;
|
|
12846
|
+
return createEmptyState();
|
|
12847
|
+
}
|
|
12848
|
+
}
|
|
12849
|
+
async function defaultWriteBackup(targetPath, content) {
|
|
12850
|
+
for (let attempt = 0;; attempt += 1) {
|
|
12851
|
+
const candidate = attempt === 0 ? targetPath : `${targetPath}-${attempt}`;
|
|
12852
|
+
try {
|
|
12853
|
+
await writeFile3(candidate, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
12854
|
+
return candidate;
|
|
12855
|
+
} catch (error2) {
|
|
12856
|
+
if (error2.code !== "EEXIST" || attempt >= 9) {
|
|
12857
|
+
throw error2;
|
|
12858
|
+
}
|
|
12859
|
+
}
|
|
12489
12860
|
}
|
|
12490
12861
|
}
|
|
12862
|
+
var STATE_FILE_VERSION = 1;
|
|
12491
12863
|
var init_state_store = __esm(() => {
|
|
12492
12864
|
init_private_file();
|
|
12493
12865
|
init_types();
|
|
@@ -12726,14 +13098,23 @@ class ScheduledTaskService {
|
|
|
12726
13098
|
return task;
|
|
12727
13099
|
});
|
|
12728
13100
|
}
|
|
12729
|
-
listPending() {
|
|
13101
|
+
listPending(chatKey) {
|
|
13102
|
+
return this.listPendingAllChats().filter((task) => task.chat_key === chatKey);
|
|
13103
|
+
}
|
|
13104
|
+
listPendingAllChats() {
|
|
12730
13105
|
return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
|
|
12731
13106
|
}
|
|
12732
|
-
async cancelPending(inputId) {
|
|
13107
|
+
async cancelPending(inputId, chatKey) {
|
|
13108
|
+
return await this.cancelPendingWhere(inputId, (task) => task.chat_key === chatKey);
|
|
13109
|
+
}
|
|
13110
|
+
async cancelPendingAnyChat(inputId) {
|
|
13111
|
+
return await this.cancelPendingWhere(inputId, () => true);
|
|
13112
|
+
}
|
|
13113
|
+
async cancelPendingWhere(inputId, allowed) {
|
|
12733
13114
|
return await this.mutate(async () => {
|
|
12734
13115
|
const id = normalizeId(inputId);
|
|
12735
13116
|
const task = this.state.scheduled_tasks[id];
|
|
12736
|
-
if (!task || task.status !== "pending")
|
|
13117
|
+
if (!task || task.status !== "pending" || !allowed(task))
|
|
12737
13118
|
return false;
|
|
12738
13119
|
task.status = "cancelled";
|
|
12739
13120
|
task.cancelled_at = this.now().toISOString();
|
|
@@ -12765,7 +13146,7 @@ class ScheduledTaskService {
|
|
|
12765
13146
|
async claimDueTasks() {
|
|
12766
13147
|
return await this.mutate(async () => {
|
|
12767
13148
|
const nowMs = this.now().getTime();
|
|
12768
|
-
const due = this.
|
|
13149
|
+
const due = this.listPendingAllChats().filter((task) => Date.parse(task.execute_at) <= nowMs);
|
|
12769
13150
|
if (due.length === 0)
|
|
12770
13151
|
return [];
|
|
12771
13152
|
const at = this.now().toISOString();
|
|
@@ -12774,7 +13155,16 @@ class ScheduledTaskService {
|
|
|
12774
13155
|
task.triggered_at = at;
|
|
12775
13156
|
this.claimedInThisSession.add(task.id);
|
|
12776
13157
|
}
|
|
12777
|
-
|
|
13158
|
+
try {
|
|
13159
|
+
await this.save();
|
|
13160
|
+
} catch (error2) {
|
|
13161
|
+
for (const task of due) {
|
|
13162
|
+
task.status = "pending";
|
|
13163
|
+
delete task.triggered_at;
|
|
13164
|
+
this.claimedInThisSession.delete(task.id);
|
|
13165
|
+
}
|
|
13166
|
+
throw error2;
|
|
13167
|
+
}
|
|
12778
13168
|
return due.map((task) => ({ ...task }));
|
|
12779
13169
|
});
|
|
12780
13170
|
}
|
|
@@ -12832,20 +13222,20 @@ var init_scheduled_service = () => {};
|
|
|
12832
13222
|
|
|
12833
13223
|
// src/plugins/plugin-home.ts
|
|
12834
13224
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
12835
|
-
import { copyFile, mkdir as
|
|
13225
|
+
import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
12836
13226
|
import { homedir as homedir3 } from "node:os";
|
|
12837
|
-
import { dirname as
|
|
13227
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
12838
13228
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12839
13229
|
function resolveCoreRoot() {
|
|
12840
13230
|
try {
|
|
12841
|
-
let dir =
|
|
13231
|
+
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
12842
13232
|
for (let depth = 0;depth < 12; depth++) {
|
|
12843
13233
|
try {
|
|
12844
13234
|
const pkg = JSON.parse(readFileSync2(join6(dir, "package.json"), "utf-8"));
|
|
12845
13235
|
if (pkg.name && CORE_ROOT_NAMES.includes(pkg.name))
|
|
12846
13236
|
return dir;
|
|
12847
13237
|
} catch {}
|
|
12848
|
-
const parent =
|
|
13238
|
+
const parent = dirname4(dir);
|
|
12849
13239
|
if (parent === dir)
|
|
12850
13240
|
break;
|
|
12851
13241
|
dir = parent;
|
|
@@ -12863,7 +13253,7 @@ async function ensureCoreResolution(pluginHome) {
|
|
|
12863
13253
|
for (const name of SHIM_SPECIFIERS) {
|
|
12864
13254
|
const targetDir = join6(pluginHome, "node_modules", name);
|
|
12865
13255
|
const dstJs = join6(targetDir, "plugin-api.js");
|
|
12866
|
-
await
|
|
13256
|
+
await mkdir4(targetDir, { recursive: true });
|
|
12867
13257
|
try {
|
|
12868
13258
|
await copyFile(srcJs, dstJs);
|
|
12869
13259
|
} catch (error2) {
|
|
@@ -12906,7 +13296,7 @@ async function normalizePluginHomeManifest(pluginHome) {
|
|
|
12906
13296
|
const manifestPath = join6(pluginHome, "package.json");
|
|
12907
13297
|
let raw;
|
|
12908
13298
|
try {
|
|
12909
|
-
raw = await
|
|
13299
|
+
raw = await readFile7(manifestPath, "utf8");
|
|
12910
13300
|
} catch {
|
|
12911
13301
|
return false;
|
|
12912
13302
|
}
|
|
@@ -12924,7 +13314,7 @@ async function normalizePluginHomeManifest(pluginHome) {
|
|
|
12924
13314
|
return true;
|
|
12925
13315
|
}
|
|
12926
13316
|
async function ensurePluginHome(pluginHome) {
|
|
12927
|
-
await
|
|
13317
|
+
await mkdir4(pluginHome, { recursive: true, mode: 448 });
|
|
12928
13318
|
await writeFile4(join6(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
|
|
12929
13319
|
`, { flag: "wx" }).catch((error2) => {
|
|
12930
13320
|
if (error2.code !== "EEXIST")
|
|
@@ -15540,7 +15930,7 @@ function createConversationExecutor() {
|
|
|
15540
15930
|
var DEFAULT_SESSION_KEY = "__chat__";
|
|
15541
15931
|
|
|
15542
15932
|
// src/channels/media-store.ts
|
|
15543
|
-
import { access as access2, mkdir as
|
|
15933
|
+
import { access as access2, mkdir as mkdir5, readdir, rm as rm5, stat, writeFile as writeFile5 } from "node:fs/promises";
|
|
15544
15934
|
import path7 from "node:path";
|
|
15545
15935
|
|
|
15546
15936
|
class RuntimeMediaStore {
|
|
@@ -15558,7 +15948,7 @@ class RuntimeMediaStore {
|
|
|
15558
15948
|
const safeMessageId = safePathSegment(input.messageId || "message");
|
|
15559
15949
|
const baseFileName = sanitizeMediaFileName(input.fileName ?? "attachment", input.mimeType);
|
|
15560
15950
|
const dir = path7.join(this.rootDir, input.channelId, safeChatKey, safeMessageId);
|
|
15561
|
-
await
|
|
15951
|
+
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
15562
15952
|
const resolvedRoot = path7.resolve(this.rootDir);
|
|
15563
15953
|
const resolvedFile = path7.resolve(path7.join(dir, await uniqueFileName(dir, baseFileName)));
|
|
15564
15954
|
if (!isPathInside(resolvedFile, resolvedRoot)) {
|
|
@@ -16947,15 +17337,6 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
16947
17337
|
await drainPendingFinalForJx(ctx);
|
|
16948
17338
|
return { handled: true };
|
|
16949
17339
|
}
|
|
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
17340
|
default:
|
|
16960
17341
|
return { handled: false };
|
|
16961
17342
|
}
|
|
@@ -16968,7 +17349,6 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
16968
17349
|
}
|
|
16969
17350
|
}
|
|
16970
17351
|
var init_slash_commands = __esm(() => {
|
|
16971
|
-
init_accounts();
|
|
16972
17352
|
init_logger();
|
|
16973
17353
|
init_i18n();
|
|
16974
17354
|
init_final_heads_up();
|
|
@@ -16984,14 +17364,14 @@ function normalizeMediaArray(media) {
|
|
|
16984
17364
|
}
|
|
16985
17365
|
|
|
16986
17366
|
// 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
|
|
17367
|
+
import { readdir as readdir2, rename as rename2, rm as rm6, stat as stat2 } from "node:fs/promises";
|
|
17368
|
+
import { basename, dirname as dirname5, join as join8 } from "node:path";
|
|
16989
17369
|
async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
16990
17370
|
let currentSize = 0;
|
|
16991
17371
|
try {
|
|
16992
17372
|
currentSize = (await stat2(filePath)).size;
|
|
16993
17373
|
} catch (error2) {
|
|
16994
|
-
if (!
|
|
17374
|
+
if (!isMissingFileError3(error2)) {
|
|
16995
17375
|
throw error2;
|
|
16996
17376
|
}
|
|
16997
17377
|
}
|
|
@@ -17009,24 +17389,24 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
|
17009
17389
|
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
17010
17390
|
const source = `${filePath}.${index}`;
|
|
17011
17391
|
try {
|
|
17012
|
-
await
|
|
17392
|
+
await rename2(source, `${filePath}.${index + 1}`);
|
|
17013
17393
|
} catch (error2) {
|
|
17014
|
-
if (!
|
|
17394
|
+
if (!isMissingFileError3(error2)) {
|
|
17015
17395
|
throw error2;
|
|
17016
17396
|
}
|
|
17017
17397
|
}
|
|
17018
17398
|
}
|
|
17019
|
-
await
|
|
17399
|
+
await rename2(filePath, `${filePath}.1`);
|
|
17020
17400
|
}
|
|
17021
17401
|
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
17022
|
-
const parentDir =
|
|
17402
|
+
const parentDir = dirname5(filePath);
|
|
17023
17403
|
const prefix = `${basename(filePath)}.`;
|
|
17024
17404
|
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
17025
17405
|
let files = [];
|
|
17026
17406
|
try {
|
|
17027
17407
|
files = await readdir2(parentDir);
|
|
17028
17408
|
} catch (error2) {
|
|
17029
|
-
if (
|
|
17409
|
+
if (isMissingFileError3(error2)) {
|
|
17030
17410
|
return;
|
|
17031
17411
|
}
|
|
17032
17412
|
throw error2;
|
|
@@ -17036,23 +17416,31 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
|
17036
17416
|
continue;
|
|
17037
17417
|
}
|
|
17038
17418
|
const candidate = join8(parentDir, file);
|
|
17039
|
-
|
|
17419
|
+
let details;
|
|
17420
|
+
try {
|
|
17421
|
+
details = await stat2(candidate);
|
|
17422
|
+
} catch (error2) {
|
|
17423
|
+
if (isMissingFileError3(error2)) {
|
|
17424
|
+
continue;
|
|
17425
|
+
}
|
|
17426
|
+
throw error2;
|
|
17427
|
+
}
|
|
17040
17428
|
if (details.mtime.getTime() < cutoff) {
|
|
17041
17429
|
await rm6(candidate, { force: true });
|
|
17042
17430
|
}
|
|
17043
17431
|
}
|
|
17044
17432
|
}
|
|
17045
|
-
function
|
|
17433
|
+
function isMissingFileError3(error2) {
|
|
17046
17434
|
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
17047
17435
|
}
|
|
17048
17436
|
var init_rotating_file_writer = () => {};
|
|
17049
17437
|
|
|
17050
17438
|
// src/perf/perf-log-writer.ts
|
|
17051
17439
|
import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
|
|
17052
|
-
import { dirname as
|
|
17440
|
+
import { dirname as dirname6 } from "node:path";
|
|
17053
17441
|
function createPerfLogWriter(options) {
|
|
17054
17442
|
const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
|
|
17055
|
-
const
|
|
17443
|
+
const mkdir6 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
|
|
17056
17444
|
return;
|
|
17057
17445
|
}));
|
|
17058
17446
|
const now = options.now ?? (() => new Date);
|
|
@@ -17099,7 +17487,7 @@ function createPerfLogWriter(options) {
|
|
|
17099
17487
|
return;
|
|
17100
17488
|
const data = batch.join("");
|
|
17101
17489
|
try {
|
|
17102
|
-
await
|
|
17490
|
+
await mkdir6(dirname6(options.filePath), { recursive: true, mode: 448 });
|
|
17103
17491
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
|
|
17104
17492
|
await append(options.filePath, data);
|
|
17105
17493
|
consecutiveFailures = 0;
|
|
@@ -17630,13 +18018,39 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
17630
18018
|
deps.errLog(`weixin.final.dropped reason=backgrounded_no_store kind=text chatKey=${to}`);
|
|
17631
18019
|
} else {
|
|
17632
18020
|
const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
|
|
18021
|
+
const sendAllParkedNotice = async (count) => {
|
|
18022
|
+
try {
|
|
18023
|
+
await sendMessageWeixin({
|
|
18024
|
+
to,
|
|
18025
|
+
text: t().misc.finalAllParked(count),
|
|
18026
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
18027
|
+
});
|
|
18028
|
+
} catch (noticeErr) {
|
|
18029
|
+
deps.errLog(`weixin.final.parked_notice_failed chatKey=${to} err=${String(noticeErr)}`);
|
|
18030
|
+
}
|
|
18031
|
+
};
|
|
18032
|
+
const buildPendingChunk = (text, seq, total) => {
|
|
18033
|
+
const entry = { text, seq, total };
|
|
18034
|
+
if (contextToken !== undefined)
|
|
18035
|
+
entry.contextToken = contextToken;
|
|
18036
|
+
if (deps.accountId !== undefined)
|
|
18037
|
+
entry.accountId = deps.accountId;
|
|
18038
|
+
return entry;
|
|
18039
|
+
};
|
|
17633
18040
|
if (rawChunks.length > 0) {
|
|
17634
18041
|
const total = rawChunks.length;
|
|
17635
18042
|
if (total === 1) {
|
|
17636
18043
|
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
17637
18044
|
if (!reserved) {
|
|
17638
|
-
|
|
17639
|
-
|
|
18045
|
+
if (deps.enqueuePendingFinal) {
|
|
18046
|
+
deps.enqueuePendingFinal(to, [buildPendingChunk(rawChunks[0], 1, 1)]);
|
|
18047
|
+
finalChunksPending = 1;
|
|
18048
|
+
deps.errLog(`weixin.final.parked reason=quota_exhausted kind=text chatKey=${to}`);
|
|
18049
|
+
await sendAllParkedNotice(1);
|
|
18050
|
+
} else {
|
|
18051
|
+
finalDropped = true;
|
|
18052
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
|
|
18053
|
+
}
|
|
17640
18054
|
} else {
|
|
17641
18055
|
await sendMessageWeixin({
|
|
17642
18056
|
to,
|
|
@@ -17693,16 +18107,11 @@ ${buildFinalHeadsUp({
|
|
|
17693
18107
|
const restToPark = prefixed.slice(sent);
|
|
17694
18108
|
finalChunksPending = restToPark.length;
|
|
17695
18109
|
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
|
-
});
|
|
18110
|
+
const pending = restToPark.map((text, idx) => buildPendingChunk(text, sent + idx + 1, total));
|
|
17705
18111
|
deps.enqueuePendingFinal(to, pending);
|
|
18112
|
+
if (sent === 0) {
|
|
18113
|
+
await sendAllParkedNotice(restToPark.length);
|
|
18114
|
+
}
|
|
17706
18115
|
}
|
|
17707
18116
|
}
|
|
17708
18117
|
}
|
|
@@ -17898,7 +18307,7 @@ function shouldFetchTypingConfig(textBody) {
|
|
|
17898
18307
|
const command = parseSlashCommand(textBody);
|
|
17899
18308
|
if (!command)
|
|
17900
18309
|
return true;
|
|
17901
|
-
return !["/cancel", "/stop", "/jx", "/echo", "/toggle-debug"
|
|
18310
|
+
return !["/cancel", "/stop", "/jx", "/echo", "/toggle-debug"].includes(command);
|
|
17902
18311
|
}
|
|
17903
18312
|
async function monitorWeixinProvider(opts) {
|
|
17904
18313
|
const {
|
|
@@ -18621,7 +19030,7 @@ ${buildFinalHeadsUp({
|
|
|
18621
19030
|
let sent = 0;
|
|
18622
19031
|
for (let index = 0;index < wave.length; index += 1) {
|
|
18623
19032
|
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 });
|
|
19033
|
+
await deps.logger.info(deps.enqueuePendingFinal ? "scheduled.final_parked" : "scheduled.final_dropped", deps.enqueuePendingFinal ? "scheduled turn final response parked due to quota" : "scheduled turn final response dropped due to quota", { chatKey: input.chatKey, reason: "quota_exhausted", chunk: index + 1, total });
|
|
18625
19034
|
break;
|
|
18626
19035
|
}
|
|
18627
19036
|
const delivered = await sendTextViaAvailableAccount(wave[index], "scheduled.final_send_failed");
|
|
@@ -18630,7 +19039,7 @@ ${buildFinalHeadsUp({
|
|
|
18630
19039
|
sent += 1;
|
|
18631
19040
|
}
|
|
18632
19041
|
const restToPark = chunks.slice(sent);
|
|
18633
|
-
if (
|
|
19042
|
+
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
18634
19043
|
const pending = restToPark.map((text, index) => {
|
|
18635
19044
|
const entry = { text, seq: sent + index + 1, total };
|
|
18636
19045
|
if (deliveryContextToken)
|
|
@@ -18640,6 +19049,12 @@ ${buildFinalHeadsUp({
|
|
|
18640
19049
|
return entry;
|
|
18641
19050
|
});
|
|
18642
19051
|
deps.enqueuePendingFinal(quotaKey, pending);
|
|
19052
|
+
if (sent === 0) {
|
|
19053
|
+
const noticeDelivered = await sendTextViaAvailableAccount(t().misc.finalAllParked(restToPark.length), "scheduled.final_parked_notice_failed");
|
|
19054
|
+
if (!noticeDelivered) {
|
|
19055
|
+
await deps.logger.info("scheduled.final_parked_notice_failed", "scheduled parked-final notice could not be delivered", { chatKey: input.chatKey, parked: restToPark.length });
|
|
19056
|
+
}
|
|
19057
|
+
}
|
|
18643
19058
|
}
|
|
18644
19059
|
}
|
|
18645
19060
|
async function sendTextViaAvailableAccount(text, errorEvent) {
|
|
@@ -18677,8 +19092,8 @@ var init_scheduled_turn = __esm(() => {
|
|
|
18677
19092
|
});
|
|
18678
19093
|
|
|
18679
19094
|
// src/weixin/monitor/consumer-lock.ts
|
|
18680
|
-
import { mkdir as
|
|
18681
|
-
import { dirname as
|
|
19095
|
+
import { mkdir as mkdir6, open as open3, readFile as readFile8, rm as rm7 } from "node:fs/promises";
|
|
19096
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
18682
19097
|
import { homedir as homedir4 } from "node:os";
|
|
18683
19098
|
function createWeixinConsumerLock(options = {}) {
|
|
18684
19099
|
const lockFilePath = options.lockFilePath ?? join9(coreHomeDir(homedir4()), "runtime", "weixin-consumer.lock.json");
|
|
@@ -18686,7 +19101,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
18686
19101
|
const onDiagnostic = options.onDiagnostic;
|
|
18687
19102
|
return {
|
|
18688
19103
|
async acquire(meta2) {
|
|
18689
|
-
await
|
|
19104
|
+
await mkdir6(dirname7(lockFilePath), { recursive: true, mode: 448 });
|
|
18690
19105
|
while (true) {
|
|
18691
19106
|
try {
|
|
18692
19107
|
const handle = await open3(lockFilePath, "wx");
|
|
@@ -18759,7 +19174,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
18759
19174
|
}
|
|
18760
19175
|
async function loadLockMetadata(path14) {
|
|
18761
19176
|
try {
|
|
18762
|
-
const raw = await
|
|
19177
|
+
const raw = await readFile8(path14, "utf8");
|
|
18763
19178
|
const parsed = JSON.parse(raw);
|
|
18764
19179
|
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
18765
19180
|
return null;
|
|
@@ -18824,6 +19239,13 @@ class WeixinChannel {
|
|
|
18824
19239
|
logout() {
|
|
18825
19240
|
logout();
|
|
18826
19241
|
}
|
|
19242
|
+
stop() {
|
|
19243
|
+
this.agent = null;
|
|
19244
|
+
this.quota = null;
|
|
19245
|
+
this.logger = null;
|
|
19246
|
+
this.markDelivered = null;
|
|
19247
|
+
this.markFailed = null;
|
|
19248
|
+
}
|
|
18827
19249
|
createConsumerLock(options) {
|
|
18828
19250
|
return createWeixinConsumerLock({
|
|
18829
19251
|
...options?.lockFilePath ? { lockFilePath: options.lockFilePath } : {},
|
|
@@ -19404,8 +19826,8 @@ var init_bootstrap = __esm(() => {
|
|
|
19404
19826
|
});
|
|
19405
19827
|
|
|
19406
19828
|
// src/logging/app-logger.ts
|
|
19407
|
-
import { appendFile, chmod as
|
|
19408
|
-
import { dirname as
|
|
19829
|
+
import { appendFile, chmod as chmod3, mkdir as mkdir7 } from "node:fs/promises";
|
|
19830
|
+
import { dirname as dirname9 } from "node:path";
|
|
19409
19831
|
function createNoopAppLogger() {
|
|
19410
19832
|
return {
|
|
19411
19833
|
debug: async () => {},
|
|
@@ -19419,6 +19841,7 @@ function createAppLogger(options) {
|
|
|
19419
19841
|
const now = options.now ?? (() => new Date);
|
|
19420
19842
|
let writeChain = Promise.resolve();
|
|
19421
19843
|
let modeEnsured = false;
|
|
19844
|
+
let writeErrorLatched = false;
|
|
19422
19845
|
return {
|
|
19423
19846
|
debug: async (event, message, context) => {
|
|
19424
19847
|
await enqueueWrite("debug", event, message, context);
|
|
@@ -19430,14 +19853,21 @@ function createAppLogger(options) {
|
|
|
19430
19853
|
await enqueueWrite("error", event, message, context);
|
|
19431
19854
|
},
|
|
19432
19855
|
cleanup: async () => {
|
|
19433
|
-
|
|
19856
|
+
try {
|
|
19857
|
+
await cleanupExpiredRotatedLogs(options.filePath, options.retentionDays, now);
|
|
19858
|
+
} catch {}
|
|
19434
19859
|
},
|
|
19435
19860
|
flush: async () => {
|
|
19436
19861
|
await writeChain;
|
|
19437
19862
|
}
|
|
19438
19863
|
};
|
|
19439
19864
|
function enqueueWrite(level, event, message, context = {}) {
|
|
19440
|
-
const writePromise = writeChain.
|
|
19865
|
+
const writePromise = writeChain.then(() => writeLog2(level, event, message, context)).catch((error2) => {
|
|
19866
|
+
if (!writeErrorLatched) {
|
|
19867
|
+
writeErrorLatched = true;
|
|
19868
|
+
console.error("[xacpx] app-logger: log file write failed — further write errors will be suppressed.", error2 instanceof Error ? error2.message : String(error2));
|
|
19869
|
+
}
|
|
19870
|
+
});
|
|
19441
19871
|
writeChain = writePromise;
|
|
19442
19872
|
return writePromise;
|
|
19443
19873
|
}
|
|
@@ -19446,13 +19876,14 @@ function createAppLogger(options) {
|
|
|
19446
19876
|
return;
|
|
19447
19877
|
}
|
|
19448
19878
|
const line = formatLogLine(now(), level, event, message, context);
|
|
19449
|
-
await
|
|
19879
|
+
await mkdir7(dirname9(options.filePath), { recursive: true, mode: 448 });
|
|
19450
19880
|
if (!modeEnsured) {
|
|
19451
19881
|
modeEnsured = true;
|
|
19452
|
-
await
|
|
19882
|
+
await chmod3(options.filePath, 384).catch(() => {});
|
|
19453
19883
|
}
|
|
19454
19884
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
|
|
19455
19885
|
await appendFile(options.filePath, line, { encoding: "utf8", mode: 384 });
|
|
19886
|
+
writeErrorLatched = false;
|
|
19456
19887
|
}
|
|
19457
19888
|
}
|
|
19458
19889
|
function formatLogLine(time3, level, event, message, context) {
|
|
@@ -19481,7 +19912,7 @@ var init_app_logger = __esm(() => {
|
|
|
19481
19912
|
});
|
|
19482
19913
|
|
|
19483
19914
|
// src/transport/acpx-session-index.ts
|
|
19484
|
-
import { readFile as
|
|
19915
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
19485
19916
|
import { homedir as homedir5 } from "node:os";
|
|
19486
19917
|
import { resolve as resolve2 } from "node:path";
|
|
19487
19918
|
async function resolveSessionAgentCommandFromIndex(session3) {
|
|
@@ -19490,7 +19921,7 @@ async function resolveSessionAgentCommandFromIndex(session3) {
|
|
|
19490
19921
|
return;
|
|
19491
19922
|
}
|
|
19492
19923
|
try {
|
|
19493
|
-
const raw = await
|
|
19924
|
+
const raw = await readFile12(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
|
|
19494
19925
|
const parsed = JSON.parse(raw);
|
|
19495
19926
|
const targetCwd = resolve2(session3.cwd);
|
|
19496
19927
|
const match = parsed.entries?.find((entry) => entry.name === session3.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
|
|
@@ -19672,7 +20103,9 @@ function parseCommand(input) {
|
|
|
19672
20103
|
if (!trimmed.startsWith("/")) {
|
|
19673
20104
|
return { kind: "prompt", text: trimmed };
|
|
19674
20105
|
}
|
|
19675
|
-
const
|
|
20106
|
+
const tokens = tokenizeCommand(trimmed);
|
|
20107
|
+
const parts = tokens.map((token) => token.value);
|
|
20108
|
+
const rawTail = (index) => index < tokens.length ? trimmed.slice(tokens[index]?.start ?? 0) : "";
|
|
19676
20109
|
const command = normalizeCommand(parts[0] ?? "");
|
|
19677
20110
|
if (command === "/help" && parts.length === 1)
|
|
19678
20111
|
return { kind: "help" };
|
|
@@ -19781,7 +20214,7 @@ function parseCommand(input) {
|
|
|
19781
20214
|
return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
|
|
19782
20215
|
}
|
|
19783
20216
|
if (command === "/group" && parts[1] === "new" && parts.length > 2) {
|
|
19784
|
-
const title =
|
|
20217
|
+
const title = rawTail(2);
|
|
19785
20218
|
if (title.trim().length > 0) {
|
|
19786
20219
|
return { kind: "group.new", title };
|
|
19787
20220
|
}
|
|
@@ -19805,7 +20238,7 @@ function parseCommand(input) {
|
|
|
19805
20238
|
}
|
|
19806
20239
|
break;
|
|
19807
20240
|
}
|
|
19808
|
-
const task =
|
|
20241
|
+
const task = rawTail(index);
|
|
19809
20242
|
if (groupId.trim().length > 0 && targetAgent.trim().length > 0 && task.trim().length > 0) {
|
|
19810
20243
|
return {
|
|
19811
20244
|
kind: "group.delegate",
|
|
@@ -19859,7 +20292,7 @@ function parseCommand(input) {
|
|
|
19859
20292
|
return { kind: "agent.rm", name: parts[2] };
|
|
19860
20293
|
}
|
|
19861
20294
|
if ((command === "/delegate" || command === "/dg") && parts[1]) {
|
|
19862
|
-
const parsedDelegate = parseDelegateRequest(parts);
|
|
20295
|
+
const parsedDelegate = parseDelegateRequest(parts, rawTail);
|
|
19863
20296
|
if (parsedDelegate) {
|
|
19864
20297
|
return parsedDelegate;
|
|
19865
20298
|
}
|
|
@@ -19887,7 +20320,11 @@ function parseCommand(input) {
|
|
|
19887
20320
|
if (parts[1] === "cancel" && parts[2] && parts.length === 3) {
|
|
19888
20321
|
return { kind: "later.cancel", id: parts[2] };
|
|
19889
20322
|
}
|
|
19890
|
-
return {
|
|
20323
|
+
return {
|
|
20324
|
+
kind: "later.create",
|
|
20325
|
+
tokens: parts.slice(1),
|
|
20326
|
+
tails: tokens.slice(1).map((_, index) => rawTail(index + 1))
|
|
20327
|
+
};
|
|
19891
20328
|
}
|
|
19892
20329
|
if (command === "/workspace" && parts[1] === "new" && parts[2]) {
|
|
19893
20330
|
const name = parts[2];
|
|
@@ -20140,17 +20577,18 @@ function readNativeAttachCommand(parts, identifierIndex) {
|
|
|
20140
20577
|
return { kind: "session.native.attach", identifier };
|
|
20141
20578
|
}
|
|
20142
20579
|
function normalizeCommand(command) {
|
|
20143
|
-
|
|
20580
|
+
const lowered = command.toLowerCase();
|
|
20581
|
+
if (lowered === "/ss")
|
|
20144
20582
|
return "/session";
|
|
20145
|
-
if (
|
|
20583
|
+
if (lowered === "/ws")
|
|
20146
20584
|
return "/workspace";
|
|
20147
|
-
if (
|
|
20585
|
+
if (lowered === "/pm")
|
|
20148
20586
|
return "/permission";
|
|
20149
|
-
if (
|
|
20587
|
+
if (lowered === "/stop")
|
|
20150
20588
|
return "/cancel";
|
|
20151
|
-
if (
|
|
20589
|
+
if (lowered === "/lt")
|
|
20152
20590
|
return "/later";
|
|
20153
|
-
return
|
|
20591
|
+
return lowered;
|
|
20154
20592
|
}
|
|
20155
20593
|
function isRecognizedCommand(command) {
|
|
20156
20594
|
return isKnownXacpxCommandPrefix(command);
|
|
@@ -20173,31 +20611,41 @@ function toNonInteractivePermission(value) {
|
|
|
20173
20611
|
function tokenizeCommand(input) {
|
|
20174
20612
|
const tokens = [];
|
|
20175
20613
|
let current = "";
|
|
20176
|
-
let
|
|
20614
|
+
let start2 = -1;
|
|
20615
|
+
let closingQuote = null;
|
|
20616
|
+
let offset = 0;
|
|
20177
20617
|
for (const char of input) {
|
|
20178
|
-
|
|
20179
|
-
|
|
20180
|
-
|
|
20618
|
+
const charStart = offset;
|
|
20619
|
+
offset += char.length;
|
|
20620
|
+
if (closingQuote) {
|
|
20621
|
+
if (char === closingQuote) {
|
|
20622
|
+
closingQuote = null;
|
|
20181
20623
|
} else {
|
|
20182
20624
|
current += char;
|
|
20183
20625
|
}
|
|
20184
20626
|
continue;
|
|
20185
20627
|
}
|
|
20186
|
-
|
|
20187
|
-
|
|
20628
|
+
const close = QUOTE_PAIRS[char];
|
|
20629
|
+
if (close) {
|
|
20630
|
+
if (start2 === -1)
|
|
20631
|
+
start2 = charStart;
|
|
20632
|
+
closingQuote = close;
|
|
20188
20633
|
continue;
|
|
20189
20634
|
}
|
|
20190
20635
|
if (/\s/.test(char)) {
|
|
20191
20636
|
if (current.length > 0) {
|
|
20192
|
-
tokens.push(current);
|
|
20637
|
+
tokens.push({ value: current, start: start2 });
|
|
20193
20638
|
current = "";
|
|
20194
20639
|
}
|
|
20640
|
+
start2 = -1;
|
|
20195
20641
|
continue;
|
|
20196
20642
|
}
|
|
20643
|
+
if (start2 === -1)
|
|
20644
|
+
start2 = charStart;
|
|
20197
20645
|
current += char;
|
|
20198
20646
|
}
|
|
20199
20647
|
if (current.length > 0) {
|
|
20200
|
-
tokens.push(current);
|
|
20648
|
+
tokens.push({ value: current, start: start2 });
|
|
20201
20649
|
}
|
|
20202
20650
|
return tokens;
|
|
20203
20651
|
}
|
|
@@ -20240,7 +20688,7 @@ function parseListFilterFlags(parts, validStatuses) {
|
|
|
20240
20688
|
}
|
|
20241
20689
|
return { filter, ok: true };
|
|
20242
20690
|
}
|
|
20243
|
-
function parseDelegateRequest(parts) {
|
|
20691
|
+
function parseDelegateRequest(parts, rawTail) {
|
|
20244
20692
|
const targetAgent = parts[1];
|
|
20245
20693
|
if (!targetAgent) {
|
|
20246
20694
|
return null;
|
|
@@ -20268,7 +20716,7 @@ function parseDelegateRequest(parts) {
|
|
|
20268
20716
|
}
|
|
20269
20717
|
break;
|
|
20270
20718
|
}
|
|
20271
|
-
const task =
|
|
20719
|
+
const task = rawTail(index);
|
|
20272
20720
|
if (task.trim().length === 0) {
|
|
20273
20721
|
return null;
|
|
20274
20722
|
}
|
|
@@ -20280,9 +20728,16 @@ function parseDelegateRequest(parts) {
|
|
|
20280
20728
|
task
|
|
20281
20729
|
};
|
|
20282
20730
|
}
|
|
20283
|
-
var TASK_STATUS_VALUES, GROUP_STATUS_VALUES;
|
|
20731
|
+
var QUOTE_PAIRS, TASK_STATUS_VALUES, GROUP_STATUS_VALUES;
|
|
20284
20732
|
var init_parse_command = __esm(() => {
|
|
20285
20733
|
init_command_list();
|
|
20734
|
+
QUOTE_PAIRS = {
|
|
20735
|
+
'"': '"',
|
|
20736
|
+
"'": "'",
|
|
20737
|
+
"“": "”",
|
|
20738
|
+
"‘": "’",
|
|
20739
|
+
""": """
|
|
20740
|
+
};
|
|
20286
20741
|
TASK_STATUS_VALUES = [
|
|
20287
20742
|
"pending",
|
|
20288
20743
|
"needs_confirmation",
|
|
@@ -20295,7 +20750,50 @@ var init_parse_command = __esm(() => {
|
|
|
20295
20750
|
});
|
|
20296
20751
|
|
|
20297
20752
|
// src/commands/command-policy.ts
|
|
20753
|
+
function isInternalScheduledTurn(metadata) {
|
|
20754
|
+
return Boolean(metadata?.scheduledSessionAlias || metadata?.scheduledSessionDescriptor);
|
|
20755
|
+
}
|
|
20756
|
+
function resolveChannelOwnerIds(config4, channel) {
|
|
20757
|
+
if (!config4 || !channel) {
|
|
20758
|
+
return;
|
|
20759
|
+
}
|
|
20760
|
+
let configured = false;
|
|
20761
|
+
const ids = new Set;
|
|
20762
|
+
if (config4.channel?.type === channel && config4.channel.ownerIds) {
|
|
20763
|
+
configured = true;
|
|
20764
|
+
for (const id of config4.channel.ownerIds)
|
|
20765
|
+
ids.add(id);
|
|
20766
|
+
}
|
|
20767
|
+
for (const entry of config4.channels ?? []) {
|
|
20768
|
+
if ((entry.type === channel || entry.id === channel) && entry.ownerIds) {
|
|
20769
|
+
configured = true;
|
|
20770
|
+
for (const id of entry.ownerIds)
|
|
20771
|
+
ids.add(id);
|
|
20772
|
+
}
|
|
20773
|
+
}
|
|
20774
|
+
return configured ? [...ids] : undefined;
|
|
20775
|
+
}
|
|
20776
|
+
function withEffectiveOwner(metadata, config4) {
|
|
20777
|
+
if (!metadata?.channel || isInternalScheduledTurn(metadata)) {
|
|
20778
|
+
return metadata;
|
|
20779
|
+
}
|
|
20780
|
+
const ownerIds = resolveChannelOwnerIds(config4, metadata.channel);
|
|
20781
|
+
if (ownerIds === undefined) {
|
|
20782
|
+
return metadata;
|
|
20783
|
+
}
|
|
20784
|
+
const isOwner = metadata.isOwner === true || typeof metadata.senderId === "string" && ownerIds.includes(metadata.senderId);
|
|
20785
|
+
return { ...metadata, isOwner };
|
|
20786
|
+
}
|
|
20298
20787
|
function authorizeCommandForChat(command, metadata) {
|
|
20788
|
+
if (metadata?.channel && !isInternalScheduledTurn(metadata) && metadata.chatType !== "direct" && metadata.chatType !== "group") {
|
|
20789
|
+
if (GROUP_PUBLIC_COMMAND_KINDS.has(command.kind)) {
|
|
20790
|
+
return { allowed: true };
|
|
20791
|
+
}
|
|
20792
|
+
return {
|
|
20793
|
+
allowed: false,
|
|
20794
|
+
reason: "chat-type-missing"
|
|
20795
|
+
};
|
|
20796
|
+
}
|
|
20299
20797
|
if (metadata?.chatType !== "group") {
|
|
20300
20798
|
return { allowed: true };
|
|
20301
20799
|
}
|
|
@@ -20310,7 +20808,14 @@ function authorizeCommandForChat(command, metadata) {
|
|
|
20310
20808
|
reason: "group-owner-required"
|
|
20311
20809
|
};
|
|
20312
20810
|
}
|
|
20313
|
-
function renderCommandAccessDenied(command) {
|
|
20811
|
+
function renderCommandAccessDenied(command, reason) {
|
|
20812
|
+
if (reason === "chat-type-missing") {
|
|
20813
|
+
return [
|
|
20814
|
+
`⚠️ ${renderCommandLabel(command)}${t().misc.commandAccessDeniedChatTypeMissingSuffix}`,
|
|
20815
|
+
t().misc.commandAccessDeniedChatTypeMissingHint
|
|
20816
|
+
].join(`
|
|
20817
|
+
`);
|
|
20818
|
+
}
|
|
20314
20819
|
return [
|
|
20315
20820
|
`⚠️ ${renderCommandLabel(command)}${t().misc.commandAccessDeniedSuffix}`,
|
|
20316
20821
|
t().misc.commandAccessDeniedHint
|
|
@@ -20436,13 +20941,18 @@ async function handlePermissionModeSet(context, mode) {
|
|
|
20436
20941
|
return { text: p.noWritableConfig };
|
|
20437
20942
|
}
|
|
20438
20943
|
const previous = cloneAppConfig(context.config);
|
|
20944
|
+
const previousRaw = await context.configStore.getRawValue(["transport", "permissionMode"]);
|
|
20439
20945
|
const updated = await context.configStore.updateTransport({
|
|
20440
20946
|
permissionMode: mode
|
|
20441
20947
|
});
|
|
20442
20948
|
try {
|
|
20443
20949
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20444
20950
|
} catch (error2) {
|
|
20445
|
-
|
|
20951
|
+
if (previousRaw.present) {
|
|
20952
|
+
await context.configStore.setRawValue(["transport", "permissionMode"], previousRaw.value);
|
|
20953
|
+
} else {
|
|
20954
|
+
await context.configStore.unsetRawValue(["transport", "permissionMode"]);
|
|
20955
|
+
}
|
|
20446
20956
|
context.replaceConfig(previous);
|
|
20447
20957
|
throw error2;
|
|
20448
20958
|
}
|
|
@@ -20458,13 +20968,18 @@ async function handlePermissionAutoSet(context, policy) {
|
|
|
20458
20968
|
return { text: p.noWritableConfig };
|
|
20459
20969
|
}
|
|
20460
20970
|
const previous = cloneAppConfig(context.config);
|
|
20971
|
+
const previousRaw = await context.configStore.getRawValue(["transport", "nonInteractivePermissions"]);
|
|
20461
20972
|
const updated = await context.configStore.updateTransport({
|
|
20462
20973
|
nonInteractivePermissions: policy
|
|
20463
20974
|
});
|
|
20464
20975
|
try {
|
|
20465
20976
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20466
20977
|
} catch (error2) {
|
|
20467
|
-
|
|
20978
|
+
if (previousRaw.present) {
|
|
20979
|
+
await context.configStore.setRawValue(["transport", "nonInteractivePermissions"], previousRaw.value);
|
|
20980
|
+
} else {
|
|
20981
|
+
await context.configStore.unsetRawValue(["transport", "nonInteractivePermissions"]);
|
|
20982
|
+
}
|
|
20468
20983
|
context.replaceConfig(previous);
|
|
20469
20984
|
throw error2;
|
|
20470
20985
|
}
|
|
@@ -20514,99 +21029,83 @@ async function handleConfigSet(context, path14, rawValue) {
|
|
|
20514
21029
|
if (!context.config || !context.configStore) {
|
|
20515
21030
|
return { text: c.noWritableConfig };
|
|
20516
21031
|
}
|
|
20517
|
-
const
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
if ("error" in result) {
|
|
20521
|
-
return { text: result.error };
|
|
21032
|
+
const plan = planSupportedConfigUpdate(context.config, path14, rawValue);
|
|
21033
|
+
if ("error" in plan) {
|
|
21034
|
+
return { text: plan.error };
|
|
20522
21035
|
}
|
|
20523
|
-
|
|
21036
|
+
const previousConfig = cloneAppConfig(context.config);
|
|
21037
|
+
const previousRaw = await context.configStore.getRawValue(plan.rawPath);
|
|
21038
|
+
const updated = await context.configStore.setRawValue(plan.rawPath, plan.value);
|
|
20524
21039
|
if (path14 === "transport.permissionMode" || path14 === "transport.nonInteractivePermissions" || path14 === "transport.permissionPolicy") {
|
|
20525
21040
|
try {
|
|
20526
21041
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
20527
21042
|
} catch (error2) {
|
|
20528
|
-
|
|
20529
|
-
|
|
21043
|
+
if (previousRaw.present) {
|
|
21044
|
+
await context.configStore.setRawValue(plan.rawPath, previousRaw.value);
|
|
21045
|
+
} else {
|
|
21046
|
+
await context.configStore.unsetRawValue(plan.rawPath);
|
|
21047
|
+
}
|
|
21048
|
+
context.replaceConfig(previousConfig);
|
|
20530
21049
|
throw error2;
|
|
20531
21050
|
}
|
|
20532
21051
|
}
|
|
20533
21052
|
context.replaceConfig(updated);
|
|
20534
|
-
return { text: c.updated(path14,
|
|
21053
|
+
return { text: c.updated(path14, plan.renderedValue) };
|
|
20535
21054
|
}
|
|
20536
|
-
function
|
|
21055
|
+
function planSupportedConfigUpdate(config4, path14, rawValue) {
|
|
20537
21056
|
const c = t().config;
|
|
20538
21057
|
switch (path14) {
|
|
20539
21058
|
case "language": {
|
|
20540
21059
|
if (!isLocale(rawValue))
|
|
20541
21060
|
return { error: c.languageInvalid };
|
|
20542
|
-
|
|
20543
|
-
return { renderedValue: rawValue };
|
|
21061
|
+
return { rawPath: ["language"], value: rawValue, renderedValue: rawValue };
|
|
20544
21062
|
}
|
|
20545
21063
|
case "transport.type": {
|
|
20546
21064
|
const parsed = parseEnum(rawValue, ["acpx-cli", "acpx-bridge"]);
|
|
20547
21065
|
if (!parsed)
|
|
20548
21066
|
return { error: c.transportTypeInvalid };
|
|
20549
|
-
|
|
20550
|
-
return { renderedValue: parsed };
|
|
21067
|
+
return { rawPath: ["transport", "type"], value: parsed, renderedValue: parsed };
|
|
20551
21068
|
}
|
|
20552
21069
|
case "transport.command":
|
|
20553
21070
|
if (!rawValue.trim())
|
|
20554
21071
|
return { error: c.transportCommandEmpty };
|
|
20555
|
-
|
|
20556
|
-
return { renderedValue: rawValue };
|
|
21072
|
+
return { rawPath: ["transport", "command"], value: rawValue, renderedValue: rawValue };
|
|
20557
21073
|
case "transport.sessionInitTimeoutMs": {
|
|
20558
21074
|
const parsed = parsePositiveNumber(rawValue, "transport.sessionInitTimeoutMs");
|
|
20559
21075
|
if ("error" in parsed)
|
|
20560
21076
|
return parsed;
|
|
20561
|
-
|
|
20562
|
-
return { renderedValue: String(parsed.value) };
|
|
21077
|
+
return { rawPath: ["transport", "sessionInitTimeoutMs"], value: parsed.value, renderedValue: String(parsed.value) };
|
|
20563
21078
|
}
|
|
20564
21079
|
case "transport.permissionMode": {
|
|
20565
21080
|
const parsed = parseEnum(rawValue, ["approve-all", "approve-reads", "deny-all"]);
|
|
20566
21081
|
if (!parsed)
|
|
20567
21082
|
return { error: c.transportPermissionModeInvalid };
|
|
20568
|
-
|
|
20569
|
-
return { renderedValue: parsed };
|
|
21083
|
+
return { rawPath: ["transport", "permissionMode"], value: parsed, renderedValue: parsed };
|
|
20570
21084
|
}
|
|
20571
21085
|
case "transport.nonInteractivePermissions": {
|
|
20572
21086
|
const parsed = parseEnum(rawValue, ["deny", "fail"]);
|
|
20573
21087
|
if (!parsed)
|
|
20574
21088
|
return { error: c.transportNonInteractiveInvalid };
|
|
20575
|
-
|
|
20576
|
-
return { renderedValue: parsed };
|
|
21089
|
+
return { rawPath: ["transport", "nonInteractivePermissions"], value: parsed, renderedValue: parsed };
|
|
20577
21090
|
}
|
|
20578
21091
|
case "transport.permissionPolicy":
|
|
20579
21092
|
if (!rawValue.trim())
|
|
20580
21093
|
return { error: c.transportPermissionPolicyEmpty };
|
|
20581
|
-
|
|
20582
|
-
return { renderedValue: rawValue };
|
|
21094
|
+
return { rawPath: ["transport", "permissionPolicy"], value: rawValue, renderedValue: rawValue };
|
|
20583
21095
|
case "logging.level": {
|
|
20584
21096
|
const parsed = parseEnum(rawValue, ["error", "info", "debug"]);
|
|
20585
21097
|
if (!parsed)
|
|
20586
21098
|
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) };
|
|
21099
|
+
return { rawPath: ["logging", "level"], value: parsed, renderedValue: parsed };
|
|
20603
21100
|
}
|
|
21101
|
+
case "logging.maxSizeBytes":
|
|
21102
|
+
case "logging.maxFiles":
|
|
20604
21103
|
case "logging.retentionDays": {
|
|
20605
|
-
const
|
|
21104
|
+
const field = path14.slice("logging.".length);
|
|
21105
|
+
const parsed = parsePositiveNumber(rawValue, path14);
|
|
20606
21106
|
if ("error" in parsed)
|
|
20607
21107
|
return parsed;
|
|
20608
|
-
|
|
20609
|
-
return { renderedValue: String(parsed.value) };
|
|
21108
|
+
return { rawPath: ["logging", field], value: parsed.value, renderedValue: String(parsed.value) };
|
|
20610
21109
|
}
|
|
20611
21110
|
case "channel.type":
|
|
20612
21111
|
return { error: c.channelTypeDisabled };
|
|
@@ -20614,15 +21113,15 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20614
21113
|
const parsed = parseEnum(rawValue, ["stream", "final", "verbose"]);
|
|
20615
21114
|
if (!parsed)
|
|
20616
21115
|
return { error: c.channelReplyModeInvalid };
|
|
20617
|
-
|
|
20618
|
-
return { renderedValue: parsed };
|
|
21116
|
+
return { rawPath: ["channel", "replyMode"], value: parsed, renderedValue: parsed };
|
|
20619
21117
|
}
|
|
20620
21118
|
case "wechat.replyMode": {
|
|
20621
21119
|
const parsed = parseEnum(rawValue, ["stream", "final", "verbose"]);
|
|
20622
21120
|
if (!parsed)
|
|
20623
21121
|
return { error: c.wechatReplyModeInvalid };
|
|
20624
|
-
config4.channel.replyMode = parsed;
|
|
20625
21122
|
return {
|
|
21123
|
+
rawPath: ["channel", "replyMode"],
|
|
21124
|
+
value: parsed,
|
|
20626
21125
|
renderedValue: c.wechatReplyModeMapped(parsed)
|
|
20627
21126
|
};
|
|
20628
21127
|
}
|
|
@@ -20630,42 +21129,30 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20630
21129
|
const agentMatch = path14.match(/^agents\.([^.]+)\.(driver|command)$/);
|
|
20631
21130
|
if (agentMatch) {
|
|
20632
21131
|
const [, name, field] = agentMatch;
|
|
20633
|
-
if (!name || !field) {
|
|
21132
|
+
if (!name || !field || isPrototypePollutingKey(name)) {
|
|
20634
21133
|
return { error: c.pathNotSupported(path14) };
|
|
20635
21134
|
}
|
|
20636
|
-
|
|
20637
|
-
if (!agent3) {
|
|
21135
|
+
if (!Object.hasOwn(config4.agents, name)) {
|
|
20638
21136
|
return { error: c.agentNotFound(name) };
|
|
20639
21137
|
}
|
|
20640
21138
|
if (!rawValue.trim()) {
|
|
20641
21139
|
return { error: c.fieldEmpty(path14) };
|
|
20642
21140
|
}
|
|
20643
|
-
|
|
20644
|
-
agent3.driver = rawValue;
|
|
20645
|
-
} else {
|
|
20646
|
-
agent3.command = rawValue;
|
|
20647
|
-
}
|
|
20648
|
-
return { renderedValue: rawValue };
|
|
21141
|
+
return { rawPath: ["agents", name, field], value: rawValue, renderedValue: rawValue };
|
|
20649
21142
|
}
|
|
20650
21143
|
const workspaceMatch = path14.match(/^workspaces\.([^.]+)\.(cwd|description)$/);
|
|
20651
21144
|
if (workspaceMatch) {
|
|
20652
21145
|
const [, name, field] = workspaceMatch;
|
|
20653
|
-
if (!name || !field) {
|
|
21146
|
+
if (!name || !field || isPrototypePollutingKey(name)) {
|
|
20654
21147
|
return { error: c.pathNotSupported(path14) };
|
|
20655
21148
|
}
|
|
20656
|
-
|
|
20657
|
-
if (!workspace3) {
|
|
21149
|
+
if (!Object.hasOwn(config4.workspaces, name)) {
|
|
20658
21150
|
return { error: c.workspaceNotFound(name) };
|
|
20659
21151
|
}
|
|
20660
21152
|
if (!rawValue.trim()) {
|
|
20661
21153
|
return { error: c.fieldEmpty(path14) };
|
|
20662
21154
|
}
|
|
20663
|
-
|
|
20664
|
-
workspace3.cwd = rawValue;
|
|
20665
|
-
} else {
|
|
20666
|
-
workspace3.description = rawValue;
|
|
20667
|
-
}
|
|
20668
|
-
return { renderedValue: rawValue };
|
|
21155
|
+
return { rawPath: ["workspaces", name, field], value: rawValue, renderedValue: rawValue };
|
|
20669
21156
|
}
|
|
20670
21157
|
const channelMatch = path14.match(/^channels\.([^.]+)\.replyMode$/);
|
|
20671
21158
|
if (channelMatch) {
|
|
@@ -20681,11 +21168,21 @@ function applySupportedConfigUpdate(config4, path14, rawValue) {
|
|
|
20681
21168
|
if (!parsed) {
|
|
20682
21169
|
return { error: c.channelRuntimeReplyModeInvalid(id) };
|
|
20683
21170
|
}
|
|
20684
|
-
|
|
20685
|
-
|
|
21171
|
+
return {
|
|
21172
|
+
rawPath: [
|
|
21173
|
+
"channels",
|
|
21174
|
+
{ id, createWith: { id: channel.id, type: channel.type, enabled: channel.enabled } },
|
|
21175
|
+
"replyMode"
|
|
21176
|
+
],
|
|
21177
|
+
value: parsed,
|
|
21178
|
+
renderedValue: parsed
|
|
21179
|
+
};
|
|
20686
21180
|
}
|
|
20687
21181
|
return { error: c.pathNotSupported(path14) };
|
|
20688
21182
|
}
|
|
21183
|
+
function isPrototypePollutingKey(key) {
|
|
21184
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
21185
|
+
}
|
|
20689
21186
|
function parseEnum(value, allowed) {
|
|
20690
21187
|
return allowed.includes(value) ? value : null;
|
|
20691
21188
|
}
|
|
@@ -21227,6 +21724,10 @@ async function handleSessions(context, chatKey) {
|
|
|
21227
21724
|
async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
|
|
21228
21725
|
const channelId = getChannelIdFromChatKey(chatKey);
|
|
21229
21726
|
const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
|
|
21727
|
+
const existing = context.sessions.getResolvedSessionByInternalAlias(internalAlias);
|
|
21728
|
+
if (existing) {
|
|
21729
|
+
return { text: t().session.sessionAlreadyExists(alias, existing.agent, existing.workspace) };
|
|
21730
|
+
}
|
|
21230
21731
|
const session3 = context.lifecycle.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
|
|
21231
21732
|
const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session3.transportSession);
|
|
21232
21733
|
try {
|
|
@@ -21497,7 +21998,9 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
21497
21998
|
}
|
|
21498
21999
|
}
|
|
21499
22000
|
const sharedAliasCount = context.sessions.countAliasesSharingTransport(session3.transportSession, internalAlias);
|
|
22001
|
+
const wasCurrentInThisChat = context.sessions.peekCurrentSessionAlias(chatKey) === internalAlias;
|
|
21500
22002
|
const { wasActive } = await context.sessions.removeSession(internalAlias);
|
|
22003
|
+
const promotedAlias = wasCurrentInThisChat ? context.sessions.peekCurrentSessionAlias(chatKey) || undefined : undefined;
|
|
21501
22004
|
let orchestrationPurgeWarning;
|
|
21502
22005
|
if (context.orchestration) {
|
|
21503
22006
|
try {
|
|
@@ -21535,7 +22038,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
21535
22038
|
const s = t().session;
|
|
21536
22039
|
const lines = [s.sessionRemoved(alias)];
|
|
21537
22040
|
if (wasActive) {
|
|
21538
|
-
lines.push(s.sessionRemovedWasActive);
|
|
22041
|
+
lines.push(promotedAlias ? s.sessionRemovedWasActivePromoted(toDisplaySessionAlias(promotedAlias)) : s.sessionRemovedWasActive);
|
|
21539
22042
|
}
|
|
21540
22043
|
if (!shouldTeardownTransport) {
|
|
21541
22044
|
lines.push(s.sessionTransportShared(session3.transportSession, sharedAliasCount));
|
|
@@ -22363,7 +22866,7 @@ async function handleWorkspaceCreate(context, workspaceName, cwd, options = {})
|
|
|
22363
22866
|
name = allocateWorkspaceName(base, context.config.workspaces);
|
|
22364
22867
|
notice = w.nameSanitized(workspaceName, name);
|
|
22365
22868
|
}
|
|
22366
|
-
const updated = await context.configStore.upsertWorkspace(name,
|
|
22869
|
+
const updated = await context.configStore.upsertWorkspace(name, cwd);
|
|
22367
22870
|
context.replaceConfig(updated);
|
|
22368
22871
|
const savedLine = w.saved(name);
|
|
22369
22872
|
return { text: notice ? `${notice}
|
|
@@ -22479,10 +22982,10 @@ function validateResult(executeAt, messageStartIndex, tokens, now, pastTodayValu
|
|
|
22479
22982
|
if (tokens.slice(messageStartIndex).join(" ").trim().length === 0)
|
|
22480
22983
|
return { ok: false, code: "missing_message" };
|
|
22481
22984
|
const delta = executeAt.getTime() - now.getTime();
|
|
22985
|
+
if (isNaN(delta) || delta > LATER_MAX_DELAY_MS)
|
|
22986
|
+
return { ok: false, code: "out_of_range" };
|
|
22482
22987
|
if (delta < LATER_MIN_DELAY_MS)
|
|
22483
22988
|
return { ok: false, code: "too_soon" };
|
|
22484
|
-
if (delta > LATER_MAX_DELAY_MS)
|
|
22485
|
-
return { ok: false, code: "out_of_range" };
|
|
22486
22989
|
return { ok: true, executeAt, messageStartIndex };
|
|
22487
22990
|
}
|
|
22488
22991
|
var WEEKDAYS, ZH_MIN = "分钟", ZH_HOUR = "小时", ZH_DAY_UNIT = "天", ZH_TODAY = "今天", ZH_TOMORROW = "明天", ZH_DAY_AFTER = "后天", ZH_AFTER = "后", ZH_RELATIVE_RE;
|
|
@@ -22558,16 +23061,17 @@ function laterHelp() {
|
|
|
22558
23061
|
function handleLaterHelp() {
|
|
22559
23062
|
return { text: renderLaterHelp() };
|
|
22560
23063
|
}
|
|
22561
|
-
async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
23064
|
+
async function handleLaterCreate(tokens, tails, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
22562
23065
|
const l = t().later;
|
|
22563
|
-
let
|
|
23066
|
+
let restStart = 0;
|
|
22564
23067
|
const seenFlags = new Set;
|
|
22565
23068
|
let flagMode;
|
|
22566
|
-
while (
|
|
22567
|
-
seenFlags.add(
|
|
22568
|
-
flagMode =
|
|
22569
|
-
|
|
23069
|
+
while (restStart < tokens.length && (tokens[restStart] === "--bind" || tokens[restStart] === "--temp")) {
|
|
23070
|
+
seenFlags.add(tokens[restStart] ?? "");
|
|
23071
|
+
flagMode = tokens[restStart] === "--bind" ? "bound" : "temp";
|
|
23072
|
+
restStart += 1;
|
|
22570
23073
|
}
|
|
23074
|
+
const rest = tokens.slice(restStart);
|
|
22571
23075
|
if (seenFlags.size > 1) {
|
|
22572
23076
|
return { text: l.bindAndTempMutuallyExclusive };
|
|
22573
23077
|
}
|
|
@@ -22588,7 +23092,7 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, def
|
|
|
22588
23092
|
if (!result.ok) {
|
|
22589
23093
|
return { text: renderTimeParseError(result.code, result.value) };
|
|
22590
23094
|
}
|
|
22591
|
-
const message =
|
|
23095
|
+
const message = (tails[restStart + result.messageStartIndex] ?? "").trim();
|
|
22592
23096
|
if (message.startsWith("/")) {
|
|
22593
23097
|
return {
|
|
22594
23098
|
text: [
|
|
@@ -22612,12 +23116,12 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, def
|
|
|
22612
23116
|
});
|
|
22613
23117
|
return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSession.alias)) };
|
|
22614
23118
|
}
|
|
22615
|
-
function handleLaterList(scheduled) {
|
|
22616
|
-
const tasks = scheduled.listPending();
|
|
23119
|
+
function handleLaterList(scheduled, chatKey) {
|
|
23120
|
+
const tasks = scheduled.listPending(chatKey);
|
|
22617
23121
|
return { text: renderLaterList(tasks, (alias) => toDisplaySessionAlias(alias)) };
|
|
22618
23122
|
}
|
|
22619
|
-
async function handleLaterCancel(id, scheduled) {
|
|
22620
|
-
const ok = await scheduled.cancelPending(id);
|
|
23123
|
+
async function handleLaterCancel(id, scheduled, chatKey) {
|
|
23124
|
+
const ok = await scheduled.cancelPending(id, chatKey);
|
|
22621
23125
|
const displayId = id.replace(/^#/, "").toLowerCase();
|
|
22622
23126
|
if (ok) {
|
|
22623
23127
|
return { text: t().later.cancelSuccess(displayId) };
|
|
@@ -23454,7 +23958,7 @@ var init_session_recovery_handler = __esm(() => {
|
|
|
23454
23958
|
// src/recovery/auto-install-optional-dep.ts
|
|
23455
23959
|
import { spawn as spawn5 } from "node:child_process";
|
|
23456
23960
|
import { createWriteStream } from "node:fs";
|
|
23457
|
-
import { mkdir as
|
|
23961
|
+
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
23458
23962
|
import { homedir as homedir6 } from "node:os";
|
|
23459
23963
|
import { join as join14 } from "node:path";
|
|
23460
23964
|
async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
|
|
@@ -23572,7 +24076,7 @@ ${err.message}`, reason: "spawn" });
|
|
|
23572
24076
|
});
|
|
23573
24077
|
}, defaultLogSink = async () => {
|
|
23574
24078
|
const dir = join14(coreHomeDir(homedir6()), "logs");
|
|
23575
|
-
await
|
|
24079
|
+
await mkdir8(dir, { recursive: true });
|
|
23576
24080
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
|
|
23577
24081
|
const path14 = join14(dir, `auto-install-${timestamp}.log`);
|
|
23578
24082
|
const stream = createWriteStream(path14, { flags: "a" });
|
|
@@ -23601,7 +24105,7 @@ import { spawn as spawn6 } from "node:child_process";
|
|
|
23601
24105
|
import { createRequire as createRequire3 } from "node:module";
|
|
23602
24106
|
import { access as access3 } from "node:fs/promises";
|
|
23603
24107
|
import { homedir as homedir7 } from "node:os";
|
|
23604
|
-
import { dirname as
|
|
24108
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
23605
24109
|
function deriveParentPackageName(platformPackage) {
|
|
23606
24110
|
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
23607
24111
|
}
|
|
@@ -23674,7 +24178,7 @@ function defaultResolveFromCwd(name, cwd) {
|
|
|
23674
24178
|
const pkgJson = require2.resolve(`${name}/package.json`, {
|
|
23675
24179
|
paths: [cwd, ...require2.resolve.paths(name) ?? []]
|
|
23676
24180
|
});
|
|
23677
|
-
return
|
|
24181
|
+
return dirname10(pkgJson);
|
|
23678
24182
|
} catch {
|
|
23679
24183
|
return null;
|
|
23680
24184
|
}
|
|
@@ -23809,11 +24313,11 @@ async function handleSessionResetCommand(context, ops, chatKey) {
|
|
|
23809
24313
|
chatKey,
|
|
23810
24314
|
native: wasNative && Boolean(freshAgentSessionId)
|
|
23811
24315
|
});
|
|
23812
|
-
if (
|
|
24316
|
+
if (context.transport.removeSession && context.sessions.countAliasesSharingTransport(previous.transportSession) === 0) {
|
|
23813
24317
|
try {
|
|
23814
24318
|
await context.transport.removeSession(previous);
|
|
23815
24319
|
} catch (error2) {
|
|
23816
|
-
await context.logger.info("session.reset.close_previous_failed", "failed to close previous
|
|
24320
|
+
await context.logger.info("session.reset.close_previous_failed", "failed to close previous session after reset", {
|
|
23817
24321
|
transportSession: previous.transportSession,
|
|
23818
24322
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
23819
24323
|
});
|
|
@@ -23875,9 +24379,20 @@ class CommandRouter {
|
|
|
23875
24379
|
chatKey,
|
|
23876
24380
|
kind: command.kind
|
|
23877
24381
|
});
|
|
24382
|
+
await this.refreshConfigFromStore();
|
|
24383
|
+
perfSpan?.mark("router.config_refreshed");
|
|
24384
|
+
metadata = withEffectiveOwner(metadata, this.config);
|
|
23878
24385
|
const access4 = authorizeCommandForChat(command, metadata);
|
|
23879
24386
|
perfSpan?.mark("router.authorized", { decision: access4.allowed ? "allow" : "deny" });
|
|
23880
24387
|
if (!access4.allowed) {
|
|
24388
|
+
if (access4.reason === "chat-type-missing") {
|
|
24389
|
+
await this.logger.error("channel.chat_type_missing", "channel turn carried no chatType; denying privileged command (channel metadata contract violation)", {
|
|
24390
|
+
chatKey,
|
|
24391
|
+
kind: command.kind,
|
|
24392
|
+
channel: metadata?.channel,
|
|
24393
|
+
senderId: metadata?.senderId
|
|
24394
|
+
});
|
|
24395
|
+
}
|
|
23881
24396
|
await this.logger.info("command.blocked", "blocked command by chat policy", {
|
|
23882
24397
|
chatKey,
|
|
23883
24398
|
kind: command.kind,
|
|
@@ -23885,10 +24400,8 @@ class CommandRouter {
|
|
|
23885
24400
|
channel: metadata?.channel,
|
|
23886
24401
|
senderId: metadata?.senderId
|
|
23887
24402
|
});
|
|
23888
|
-
return { text: renderCommandAccessDenied(command) };
|
|
24403
|
+
return { text: renderCommandAccessDenied(command, access4.reason) };
|
|
23889
24404
|
}
|
|
23890
|
-
await this.refreshConfigFromStore();
|
|
23891
|
-
perfSpan?.mark("router.config_refreshed");
|
|
23892
24405
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
23893
24406
|
switch (command.kind) {
|
|
23894
24407
|
case "invalid":
|
|
@@ -23990,7 +24503,7 @@ class CommandRouter {
|
|
|
23990
24503
|
case "later.list":
|
|
23991
24504
|
if (!this.scheduled)
|
|
23992
24505
|
return { text: t().later.serviceNotEnabled };
|
|
23993
|
-
return handleLaterList(this.scheduled);
|
|
24506
|
+
return handleLaterList(this.scheduled, chatKey);
|
|
23994
24507
|
case "later.create": {
|
|
23995
24508
|
if (!this.scheduled)
|
|
23996
24509
|
return { text: t().later.serviceNotEnabled };
|
|
@@ -23998,12 +24511,12 @@ class CommandRouter {
|
|
|
23998
24511
|
return { text: renderLaterUnsupportedChannel() };
|
|
23999
24512
|
}
|
|
24000
24513
|
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);
|
|
24514
|
+
return await handleLaterCreate(command.tokens, command.tails, this.scheduled, chatKey, currentSession ? { alias: currentSession.alias, agent: currentSession.agent, workspace: currentSession.workspace } : null, this.config?.later?.defaultMode === "bind" ? "bound" : "temp", accountId, replyContextToken);
|
|
24002
24515
|
}
|
|
24003
24516
|
case "later.cancel":
|
|
24004
24517
|
if (!this.scheduled)
|
|
24005
24518
|
return { text: t().later.serviceNotEnabled };
|
|
24006
|
-
return await handleLaterCancel(command.id, this.scheduled);
|
|
24519
|
+
return await handleLaterCancel(command.id, this.scheduled, chatKey);
|
|
24007
24520
|
case "prompt": {
|
|
24008
24521
|
const sessionContext = this.createSessionHandlerContext(undefined, perfSpan);
|
|
24009
24522
|
if (metadata?.scheduledSessionDescriptor) {
|
|
@@ -24507,7 +25020,7 @@ var init_console_agent = __esm(() => {
|
|
|
24507
25020
|
});
|
|
24508
25021
|
|
|
24509
25022
|
// src/orchestration/orchestration-server.ts
|
|
24510
|
-
import { rm as rm8 } from "node:fs/promises";
|
|
25023
|
+
import { chmod as chmod4, rm as rm8 } from "node:fs/promises";
|
|
24511
25024
|
import { createServer } from "node:net";
|
|
24512
25025
|
|
|
24513
25026
|
class OrchestrationServer {
|
|
@@ -24554,6 +25067,7 @@ class OrchestrationServer {
|
|
|
24554
25067
|
});
|
|
24555
25068
|
});
|
|
24556
25069
|
await this.listenWithUnixSocketRecovery();
|
|
25070
|
+
await this.hardenUnixSocketPermissions();
|
|
24557
25071
|
this.started = true;
|
|
24558
25072
|
}
|
|
24559
25073
|
async stop() {
|
|
@@ -24742,17 +25256,19 @@ class OrchestrationServer {
|
|
|
24742
25256
|
return task;
|
|
24743
25257
|
}
|
|
24744
25258
|
parseRequestDelegateRpcInput(params) {
|
|
24745
|
-
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId"], "params");
|
|
25259
|
+
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId", "parallel"], "params");
|
|
24746
25260
|
const cwd = requireOptionalString(params, "cwd");
|
|
24747
25261
|
const role = requireOptionalString(params, "role");
|
|
24748
25262
|
const groupId = requireOptionalString(params, "groupId");
|
|
25263
|
+
const parallel = requireOptionalBoolean(params, "parallel");
|
|
24749
25264
|
return {
|
|
24750
25265
|
sourceHandle: requireString(params, "sourceHandle"),
|
|
24751
25266
|
targetAgent: requireString(params, "targetAgent"),
|
|
24752
25267
|
task: requireString(params, "task"),
|
|
24753
25268
|
...cwd !== undefined ? { cwd } : {},
|
|
24754
25269
|
...role !== undefined ? { role } : {},
|
|
24755
|
-
...groupId !== undefined ? { groupId } : {}
|
|
25270
|
+
...groupId !== undefined ? { groupId } : {},
|
|
25271
|
+
...parallel !== undefined ? { parallel } : {}
|
|
24756
25272
|
};
|
|
24757
25273
|
}
|
|
24758
25274
|
parseTaskListFilter(params) {
|
|
@@ -24835,6 +25351,17 @@ class OrchestrationServer {
|
|
|
24835
25351
|
whatIsNeeded: requireString(params, "whatIsNeeded")
|
|
24836
25352
|
};
|
|
24837
25353
|
}
|
|
25354
|
+
async hardenUnixSocketPermissions() {
|
|
25355
|
+
if (this.endpoint.kind !== "unix") {
|
|
25356
|
+
return;
|
|
25357
|
+
}
|
|
25358
|
+
const chmodFile = this.deps.chmodFile ?? chmod4;
|
|
25359
|
+
try {
|
|
25360
|
+
await chmodFile(this.endpoint.path, 384);
|
|
25361
|
+
} catch (error2) {
|
|
25362
|
+
this.deps.onSocketHardenError?.(error2);
|
|
25363
|
+
}
|
|
25364
|
+
}
|
|
24838
25365
|
async cleanupEndpoint() {
|
|
24839
25366
|
if (this.endpoint.kind !== "unix") {
|
|
24840
25367
|
return;
|
|
@@ -28316,18 +28843,39 @@ class ScheduledTaskScheduler {
|
|
|
28316
28843
|
return;
|
|
28317
28844
|
this.ticking = true;
|
|
28318
28845
|
try {
|
|
28319
|
-
|
|
28846
|
+
let dueTasks;
|
|
28847
|
+
try {
|
|
28848
|
+
dueTasks = await this.service.claimDueTasks();
|
|
28849
|
+
} catch (claimError) {
|
|
28850
|
+
await this.logger?.error("scheduled.claim.failed", "claimDueTasks threw; skipping tick", { message: claimError instanceof Error ? claimError.message : String(claimError) });
|
|
28851
|
+
return;
|
|
28852
|
+
}
|
|
28320
28853
|
for (const task of dueTasks) {
|
|
28321
28854
|
try {
|
|
28322
28855
|
await this.dispatchWithTimeout(task);
|
|
28323
|
-
await this.service.markExecuted(task.id);
|
|
28324
28856
|
} catch (error2) {
|
|
28325
28857
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
28326
28858
|
await this.logger?.error("scheduled.dispatch.failed", "failed to dispatch scheduled task", {
|
|
28327
28859
|
taskId: task.id,
|
|
28328
28860
|
message
|
|
28329
28861
|
});
|
|
28330
|
-
|
|
28862
|
+
try {
|
|
28863
|
+
await this.service.markFailed(task.id, error2);
|
|
28864
|
+
} catch (markError) {
|
|
28865
|
+
await this.logger?.error("scheduled.dispatch.mark_failed", "markFailed threw; task state may be stale", {
|
|
28866
|
+
taskId: task.id,
|
|
28867
|
+
message: markError instanceof Error ? markError.message : String(markError)
|
|
28868
|
+
});
|
|
28869
|
+
}
|
|
28870
|
+
continue;
|
|
28871
|
+
}
|
|
28872
|
+
try {
|
|
28873
|
+
await this.service.markExecuted(task.id);
|
|
28874
|
+
} catch (markError) {
|
|
28875
|
+
await this.logger?.error("scheduled.dispatch.mark_executed_failed", "markExecuted threw after a successful dispatch; leaving task state for startup reconciliation", {
|
|
28876
|
+
taskId: task.id,
|
|
28877
|
+
message: markError instanceof Error ? markError.message : String(markError)
|
|
28878
|
+
});
|
|
28331
28879
|
}
|
|
28332
28880
|
}
|
|
28333
28881
|
} finally {
|
|
@@ -28507,12 +29055,12 @@ var init_scheduled_route_create = __esm(() => {
|
|
|
28507
29055
|
|
|
28508
29056
|
// src/scheduled/scheduled-route-manage.ts
|
|
28509
29057
|
async function listScheduledTasksFromRoute(input, deps) {
|
|
28510
|
-
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
28511
|
-
return deps.scheduled.listPending();
|
|
29058
|
+
const route = resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
29059
|
+
return deps.scheduled.listPending(route.chatKey);
|
|
28512
29060
|
}
|
|
28513
29061
|
async function cancelScheduledTaskFromRoute(input, deps) {
|
|
28514
|
-
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
28515
|
-
const cancelled = await deps.scheduled.cancelPending(input.id);
|
|
29062
|
+
const route = resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
29063
|
+
const cancelled = await deps.scheduled.cancelPending(input.id, route.chatKey);
|
|
28516
29064
|
return { id: normalizeId(input.id), cancelled };
|
|
28517
29065
|
}
|
|
28518
29066
|
function resolveOwnedCoordinatorRoute(coordinatorSession, state, label) {
|
|
@@ -28557,25 +29105,37 @@ class SessionService {
|
|
|
28557
29105
|
const seen = new Set;
|
|
28558
29106
|
const resolved = [];
|
|
28559
29107
|
for (const session3 of Object.values(this.state.sessions)) {
|
|
28560
|
-
|
|
29108
|
+
let candidate;
|
|
29109
|
+
try {
|
|
29110
|
+
candidate = this.toResolvedSession(session3);
|
|
29111
|
+
} catch {
|
|
28561
29112
|
continue;
|
|
28562
29113
|
}
|
|
28563
|
-
|
|
28564
|
-
|
|
28565
|
-
|
|
28566
|
-
|
|
29114
|
+
const key = JSON.stringify([
|
|
29115
|
+
candidate.agent,
|
|
29116
|
+
candidate.agentCommand ?? null,
|
|
29117
|
+
candidate.cwd,
|
|
29118
|
+
candidate.transportSession
|
|
29119
|
+
]);
|
|
29120
|
+
if (seen.has(key)) {
|
|
29121
|
+
continue;
|
|
29122
|
+
}
|
|
29123
|
+
seen.add(key);
|
|
29124
|
+
resolved.push(candidate);
|
|
28567
29125
|
}
|
|
28568
29126
|
return resolved;
|
|
28569
29127
|
}
|
|
28570
29128
|
resolveSession(alias, agent3, workspace3, transportSession) {
|
|
28571
29129
|
this.validateSession(alias, agent3, workspace3);
|
|
29130
|
+
const existing = this.state.sessions[alias];
|
|
29131
|
+
const sameAgentExisting = existing && existing.agent === agent3 ? existing : undefined;
|
|
28572
29132
|
return this.toResolvedSession({
|
|
28573
29133
|
alias,
|
|
28574
29134
|
agent: agent3,
|
|
28575
29135
|
workspace: workspace3,
|
|
28576
29136
|
transport_session: transportSession,
|
|
28577
|
-
transport_agent_command:
|
|
28578
|
-
created_at:
|
|
29137
|
+
transport_agent_command: sameAgentExisting?.transport_agent_command,
|
|
29138
|
+
created_at: existing?.created_at ?? new Date().toISOString(),
|
|
28579
29139
|
last_used_at: new Date().toISOString()
|
|
28580
29140
|
});
|
|
28581
29141
|
}
|
|
@@ -28647,10 +29207,13 @@ class SessionService {
|
|
|
28647
29207
|
const previousCurrent = prevCtx?.current_session;
|
|
28648
29208
|
const carriedPrevious = previousCurrent && previousCurrent !== internalAlias ? previousCurrent : prevCtx?.previous_session;
|
|
28649
29209
|
session3.last_used_at = new Date().toISOString();
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
}
|
|
29210
|
+
const nextCtx = { ...prevCtx, current_session: internalAlias };
|
|
29211
|
+
if (carriedPrevious) {
|
|
29212
|
+
nextCtx.previous_session = carriedPrevious;
|
|
29213
|
+
} else {
|
|
29214
|
+
delete nextCtx.previous_session;
|
|
29215
|
+
}
|
|
29216
|
+
this.state.chat_contexts[chatKey] = nextCtx;
|
|
28654
29217
|
await this.persist();
|
|
28655
29218
|
return {
|
|
28656
29219
|
alias: toDisplaySessionAlias(session3.alias),
|
|
@@ -28677,10 +29240,13 @@ class SessionService {
|
|
|
28677
29240
|
}
|
|
28678
29241
|
const currentInternal = ctx?.current_session;
|
|
28679
29242
|
prevSession.last_used_at = new Date().toISOString();
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
}
|
|
29243
|
+
const nextCtx = { ...ctx, current_session: prevInternal };
|
|
29244
|
+
if (currentInternal && currentInternal !== prevInternal) {
|
|
29245
|
+
nextCtx.previous_session = currentInternal;
|
|
29246
|
+
} else {
|
|
29247
|
+
delete nextCtx.previous_session;
|
|
29248
|
+
}
|
|
29249
|
+
this.state.chat_contexts[chatKey] = nextCtx;
|
|
28684
29250
|
await this.persist();
|
|
28685
29251
|
return {
|
|
28686
29252
|
alias: toDisplaySessionAlias(prevSession.alias),
|
|
@@ -28851,13 +29417,26 @@ class SessionService {
|
|
|
28851
29417
|
const wasActive = Object.values(this.state.chat_contexts).some((ctx) => ctx.current_session === alias);
|
|
28852
29418
|
delete this.state.sessions[alias];
|
|
28853
29419
|
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
29420
|
if (ctx.previous_session === alias) {
|
|
28859
29421
|
delete ctx.previous_session;
|
|
28860
29422
|
}
|
|
29423
|
+
if (ctx.current_session === alias) {
|
|
29424
|
+
if (ctx.previous_session) {
|
|
29425
|
+
ctx.current_session = ctx.previous_session;
|
|
29426
|
+
delete ctx.previous_session;
|
|
29427
|
+
} else {
|
|
29428
|
+
ctx.current_session = "";
|
|
29429
|
+
}
|
|
29430
|
+
}
|
|
29431
|
+
if (ctx.background_results && alias in ctx.background_results) {
|
|
29432
|
+
delete ctx.background_results[alias];
|
|
29433
|
+
if (Object.keys(ctx.background_results).length === 0) {
|
|
29434
|
+
delete ctx.background_results;
|
|
29435
|
+
}
|
|
29436
|
+
}
|
|
29437
|
+
if (!ctx.current_session && !ctx.previous_session && !ctx.background_results) {
|
|
29438
|
+
delete this.state.chat_contexts[chatKey];
|
|
29439
|
+
}
|
|
28861
29440
|
}
|
|
28862
29441
|
await this.persist();
|
|
28863
29442
|
return { wasActive };
|
|
@@ -28971,6 +29550,7 @@ class SessionService {
|
|
|
28971
29550
|
throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
|
|
28972
29551
|
}
|
|
28973
29552
|
const existingSession = this.state.sessions[alias];
|
|
29553
|
+
const sameAgentExisting = existingSession && existingSession.agent === agent3 ? existingSession : undefined;
|
|
28974
29554
|
const now = new Date(this.now()).toISOString();
|
|
28975
29555
|
const normalizedTransportAgentCommand = transportAgentCommand?.trim();
|
|
28976
29556
|
const session3 = {
|
|
@@ -28983,9 +29563,9 @@ class SessionService {
|
|
|
28983
29563
|
agent_session_title: native?.title ?? undefined,
|
|
28984
29564
|
agent_session_updated_at: native?.updatedAt,
|
|
28985
29565
|
attached_at: native ? now : undefined,
|
|
28986
|
-
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } :
|
|
28987
|
-
mode_id:
|
|
28988
|
-
reply_mode:
|
|
29566
|
+
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : sameAgentExisting?.transport_agent_command ? { transport_agent_command: sameAgentExisting.transport_agent_command } : {},
|
|
29567
|
+
mode_id: sameAgentExisting?.mode_id,
|
|
29568
|
+
reply_mode: sameAgentExisting?.reply_mode,
|
|
28989
29569
|
created_at: existingSession?.created_at ?? now,
|
|
28990
29570
|
last_used_at: now
|
|
28991
29571
|
};
|
|
@@ -29431,15 +30011,15 @@ class AcpxBridgeClient {
|
|
|
29431
30011
|
onEvent
|
|
29432
30012
|
});
|
|
29433
30013
|
try {
|
|
29434
|
-
|
|
30014
|
+
this.writeLine(encodeBridgeRequest({
|
|
29435
30015
|
id,
|
|
29436
30016
|
method,
|
|
29437
30017
|
params
|
|
29438
|
-
}))
|
|
29439
|
-
|
|
29440
|
-
|
|
29441
|
-
|
|
29442
|
-
}
|
|
30018
|
+
}), (error2) => {
|
|
30019
|
+
if (error2 && this.pending.delete(id)) {
|
|
30020
|
+
reject(error2);
|
|
30021
|
+
}
|
|
30022
|
+
});
|
|
29443
30023
|
} catch (error2) {
|
|
29444
30024
|
this.pending.delete(id);
|
|
29445
30025
|
reject(error2);
|
|
@@ -29519,6 +30099,17 @@ class AcpxBridgeClient {
|
|
|
29519
30099
|
}
|
|
29520
30100
|
}
|
|
29521
30101
|
}
|
|
30102
|
+
function buildBridgeSpawnEnv(options = {}) {
|
|
30103
|
+
return {
|
|
30104
|
+
XACPX_LANG: getLocale(),
|
|
30105
|
+
XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
|
|
30106
|
+
XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
|
|
30107
|
+
XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
|
|
30108
|
+
...typeof options.permissionPolicy === "string" && options.permissionPolicy.trim().length > 0 ? { XACPX_BRIDGE_PERMISSION_POLICY: options.permissionPolicy } : {},
|
|
30109
|
+
...typeof options.queueOwnerTtlSeconds === "number" && Number.isFinite(options.queueOwnerTtlSeconds) ? { XACPX_BRIDGE_QUEUE_OWNER_TTL_SECONDS: String(options.queueOwnerTtlSeconds) } : {},
|
|
30110
|
+
...typeof options.sessionInitTimeoutMs === "number" && Number.isFinite(options.sessionInitTimeoutMs) && options.sessionInitTimeoutMs > 0 ? { XACPX_BRIDGE_SESSION_INIT_TIMEOUT_MS: String(options.sessionInitTimeoutMs) } : {}
|
|
30111
|
+
};
|
|
30112
|
+
}
|
|
29522
30113
|
function buildBridgeSpawnSpec(options) {
|
|
29523
30114
|
if (options.execPath.endsWith("bun")) {
|
|
29524
30115
|
return {
|
|
@@ -29541,15 +30132,17 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
29541
30132
|
cwd: options.cwd ?? process.cwd(),
|
|
29542
30133
|
env: {
|
|
29543
30134
|
...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) } : {}
|
|
30135
|
+
...buildBridgeSpawnEnv(options)
|
|
29549
30136
|
},
|
|
29550
30137
|
stdio: ["pipe", "pipe", "inherit"]
|
|
29551
30138
|
});
|
|
29552
|
-
const client =
|
|
30139
|
+
const client = manageBridgeChild(child);
|
|
30140
|
+
await client.waitUntilReady();
|
|
30141
|
+
return client;
|
|
30142
|
+
}
|
|
30143
|
+
function manageBridgeChild(child) {
|
|
30144
|
+
const client = new AcpxBridgeClient((line, onWriteError) => child.stdin.write(line, onWriteError));
|
|
30145
|
+
child.stdin.on("error", () => {});
|
|
29553
30146
|
const output = createInterface({
|
|
29554
30147
|
input: child.stdout,
|
|
29555
30148
|
crlfDelay: Infinity
|
|
@@ -29575,7 +30168,6 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
29575
30168
|
await terminateProcessTree(child.pid ?? 0, { detachedProcessGroup: false });
|
|
29576
30169
|
}
|
|
29577
30170
|
};
|
|
29578
|
-
await client.waitUntilReady();
|
|
29579
30171
|
return client;
|
|
29580
30172
|
}
|
|
29581
30173
|
function awaitable(executor) {
|
|
@@ -30417,19 +31009,19 @@ var init_streaming_prompt = __esm(() => {
|
|
|
30417
31009
|
|
|
30418
31010
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
30419
31011
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
30420
|
-
import { dirname as
|
|
31012
|
+
import { dirname as dirname11, join as join16 } from "node:path";
|
|
30421
31013
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
30422
31014
|
if (platform === "win32") {
|
|
30423
31015
|
return null;
|
|
30424
31016
|
}
|
|
30425
|
-
return join16(
|
|
31017
|
+
return join16(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
30426
31018
|
}
|
|
30427
|
-
async function ensureNodePtyHelperExecutable(helperPath,
|
|
31019
|
+
async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
|
|
30428
31020
|
if (!helperPath) {
|
|
30429
31021
|
return;
|
|
30430
31022
|
}
|
|
30431
31023
|
try {
|
|
30432
|
-
await
|
|
31024
|
+
await chmod5(helperPath, 493);
|
|
30433
31025
|
} catch (error2) {
|
|
30434
31026
|
if (error2.code === "ENOENT") {
|
|
30435
31027
|
return;
|
|
@@ -30442,7 +31034,7 @@ var init_node_pty_helper = () => {};
|
|
|
30442
31034
|
// src/transport/acpx-queue-owner-launcher.ts
|
|
30443
31035
|
import { createHash as createHash3 } from "node:crypto";
|
|
30444
31036
|
import { spawn as spawn8 } from "node:child_process";
|
|
30445
|
-
import { readFile as
|
|
31037
|
+
import { readFile as readFile13, unlink } from "node:fs/promises";
|
|
30446
31038
|
import { homedir as homedir8 } from "node:os";
|
|
30447
31039
|
import { join as join17 } from "node:path";
|
|
30448
31040
|
function buildXacpxMcpServerSpec(input) {
|
|
@@ -30605,7 +31197,7 @@ async function terminateAcpxQueueOwner(sessionId) {
|
|
|
30605
31197
|
const lockPath = queueLockFilePath(sessionId);
|
|
30606
31198
|
let owner;
|
|
30607
31199
|
try {
|
|
30608
|
-
owner = JSON.parse(await
|
|
31200
|
+
owner = JSON.parse(await readFile13(lockPath, "utf8"));
|
|
30609
31201
|
} catch {
|
|
30610
31202
|
return;
|
|
30611
31203
|
}
|
|
@@ -31264,10 +31856,11 @@ async function reapQueueOwners(acpxCommand, targets, deps = {}) {
|
|
|
31264
31856
|
const timeoutMs = deps.timeoutMs ?? 5000;
|
|
31265
31857
|
const seen = new Set;
|
|
31266
31858
|
const unique = targets.filter((target) => {
|
|
31267
|
-
|
|
31859
|
+
const key = JSON.stringify([target.agent, target.agentCommand ?? null, target.cwd, target.transportSession]);
|
|
31860
|
+
if (seen.has(key)) {
|
|
31268
31861
|
return false;
|
|
31269
31862
|
}
|
|
31270
|
-
seen.add(
|
|
31863
|
+
seen.add(key);
|
|
31271
31864
|
return true;
|
|
31272
31865
|
});
|
|
31273
31866
|
let terminated = 0;
|
|
@@ -31419,10 +32012,21 @@ class MessageChannelRegistry {
|
|
|
31419
32012
|
throw new Error("all channels failed to start");
|
|
31420
32013
|
}
|
|
31421
32014
|
}
|
|
31422
|
-
stopAll() {
|
|
32015
|
+
async stopAll() {
|
|
32016
|
+
let firstError;
|
|
31423
32017
|
for (const channel of this.channels.values()) {
|
|
31424
|
-
|
|
32018
|
+
try {
|
|
32019
|
+
if (channel.stop) {
|
|
32020
|
+
await channel.stop();
|
|
32021
|
+
} else {
|
|
32022
|
+
channel.logout();
|
|
32023
|
+
}
|
|
32024
|
+
} catch (error2) {
|
|
32025
|
+
firstError ??= error2;
|
|
32026
|
+
}
|
|
31425
32027
|
}
|
|
32028
|
+
if (firstError !== undefined)
|
|
32029
|
+
throw firstError;
|
|
31426
32030
|
}
|
|
31427
32031
|
getByChatKey(chatKey) {
|
|
31428
32032
|
return this.channels.get(getChannelIdFromChatKey(chatKey)) ?? null;
|
|
@@ -31670,7 +32274,7 @@ __export(exports_main, {
|
|
|
31670
32274
|
});
|
|
31671
32275
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
31672
32276
|
import { homedir as homedir9 } from "node:os";
|
|
31673
|
-
import { dirname as
|
|
32277
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
31674
32278
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
31675
32279
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
31676
32280
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
@@ -31742,6 +32346,35 @@ async function buildApp(paths, deps = {}) {
|
|
|
31742
32346
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config4.transport.command });
|
|
31743
32347
|
const stateStore = new StateStore(paths.statePath);
|
|
31744
32348
|
const state = await stateStore.load();
|
|
32349
|
+
const stateLoadReport = stateStore.lastLoadReport;
|
|
32350
|
+
if (stateLoadReport) {
|
|
32351
|
+
for (const record3 of stateLoadReport.dropped) {
|
|
32352
|
+
await logger2.error("state.record_quarantined", "dropped malformed state.json record", {
|
|
32353
|
+
statePath: paths.statePath,
|
|
32354
|
+
section: record3.section,
|
|
32355
|
+
key: record3.key,
|
|
32356
|
+
reason: record3.reason
|
|
32357
|
+
});
|
|
32358
|
+
}
|
|
32359
|
+
if (stateLoadReport.corruptPath) {
|
|
32360
|
+
await logger2.error("state.file_corrupt", "state.json was unreadable; renamed aside and starting empty", {
|
|
32361
|
+
statePath: paths.statePath,
|
|
32362
|
+
corruptPath: stateLoadReport.corruptPath
|
|
32363
|
+
});
|
|
32364
|
+
}
|
|
32365
|
+
if (stateLoadReport.quarantinePath) {
|
|
32366
|
+
await logger2.error("state.file_quarantined", "original state.json backed up before dropping records", {
|
|
32367
|
+
statePath: paths.statePath,
|
|
32368
|
+
quarantinePath: stateLoadReport.quarantinePath
|
|
32369
|
+
});
|
|
32370
|
+
}
|
|
32371
|
+
if (stateLoadReport.backupError) {
|
|
32372
|
+
await logger2.error("state.quarantine_backup_failed", "failed to back up the original state.json", {
|
|
32373
|
+
statePath: paths.statePath,
|
|
32374
|
+
message: stateLoadReport.backupError
|
|
32375
|
+
});
|
|
32376
|
+
}
|
|
32377
|
+
}
|
|
31745
32378
|
const stateMutex = new AsyncMutex;
|
|
31746
32379
|
const debouncedStateStore = new DebouncedStateStore({
|
|
31747
32380
|
delegate: stateStore,
|
|
@@ -31761,7 +32394,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
31761
32394
|
bridgeEntryPath: resolveBridgeEntryPath(),
|
|
31762
32395
|
permissionMode: config4.transport.permissionMode,
|
|
31763
32396
|
nonInteractivePermissions: config4.transport.nonInteractivePermissions,
|
|
31764
|
-
...typeof config4.transport.
|
|
32397
|
+
...typeof config4.transport.permissionPolicy === "string" ? { permissionPolicy: config4.transport.permissionPolicy } : {},
|
|
32398
|
+
...typeof config4.transport.queueOwnerTtlSeconds === "number" ? { queueOwnerTtlSeconds: config4.transport.queueOwnerTtlSeconds } : {},
|
|
32399
|
+
...typeof config4.transport.sessionInitTimeoutMs === "number" ? { sessionInitTimeoutMs: config4.transport.sessionInitTimeoutMs } : {}
|
|
31765
32400
|
})))) : deps.createCliTransport?.(acpxCommand) ?? new AcpxCliTransport({ ...config4.transport, command: acpxCommand });
|
|
31766
32401
|
const quota = new QuotaManager({
|
|
31767
32402
|
onInbound: (chatKey) => {
|
|
@@ -32114,6 +32749,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
32114
32749
|
const progressHeartbeatInterval = startProgressHeartbeat(orchestration3, config4, logger2, deps.channel ?? null);
|
|
32115
32750
|
const orchestrationEndpoint = createOrchestrationEndpoint(paths.orchestrationSocketPath ?? resolveOrchestrationSocketPathFromConfigPath(paths.configPath));
|
|
32116
32751
|
const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration3, {
|
|
32752
|
+
onSocketHardenError: (error2) => {
|
|
32753
|
+
logger2.error("orchestration.socket.chmod_failed", "failed to restrict orchestration socket to owner-only (0600); falling back to runtime dir permissions", { message: error2 instanceof Error ? error2.message : String(error2) });
|
|
32754
|
+
},
|
|
32117
32755
|
createScheduledTaskFromRoute: async (input) => await createScheduledTaskFromRoute(input, {
|
|
32118
32756
|
state,
|
|
32119
32757
|
config: config4,
|
|
@@ -32251,7 +32889,7 @@ async function main() {
|
|
|
32251
32889
|
}
|
|
32252
32890
|
}
|
|
32253
32891
|
async function prepareChannelMedia(configPath, config4) {
|
|
32254
|
-
const runtimeDir = join18(
|
|
32892
|
+
const runtimeDir = join18(dirname12(configPath), "runtime");
|
|
32255
32893
|
const mediaRootDir = join18(runtimeDir, "media");
|
|
32256
32894
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
32257
32895
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -32266,7 +32904,7 @@ function resolveRuntimePaths() {
|
|
|
32266
32904
|
throw new Error("Unable to resolve the current user home directory");
|
|
32267
32905
|
}
|
|
32268
32906
|
const configPath = coreEnv("CONFIG") ?? join18(coreHomeDir(home), "config.json");
|
|
32269
|
-
const runtimeDir = join18(
|
|
32907
|
+
const runtimeDir = join18(dirname12(configPath), "runtime");
|
|
32270
32908
|
return {
|
|
32271
32909
|
configPath,
|
|
32272
32910
|
statePath: coreEnv("STATE") ?? join18(coreHomeDir(home), "state.json"),
|
|
@@ -32281,12 +32919,12 @@ function resolveBridgeEntryPath() {
|
|
|
32281
32919
|
return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
32282
32920
|
}
|
|
32283
32921
|
function resolveAppLogPath(configPath) {
|
|
32284
|
-
const rootDir =
|
|
32922
|
+
const rootDir = dirname12(configPath);
|
|
32285
32923
|
const runtimeDir = join18(rootDir, "runtime");
|
|
32286
32924
|
return join18(runtimeDir, "app.log");
|
|
32287
32925
|
}
|
|
32288
32926
|
function resolvePerfLogPath(configPath) {
|
|
32289
|
-
const rootDir =
|
|
32927
|
+
const rootDir = dirname12(configPath);
|
|
32290
32928
|
const runtimeDir = join18(rootDir, "runtime");
|
|
32291
32929
|
return join18(runtimeDir, "perf.log");
|
|
32292
32930
|
}
|
|
@@ -32682,7 +33320,7 @@ var init_orchestration_health = __esm(() => {
|
|
|
32682
33320
|
// src/doctor/checks/runtime-check.ts
|
|
32683
33321
|
import { constants } from "node:fs";
|
|
32684
33322
|
import { access as access4, stat as stat3 } from "node:fs/promises";
|
|
32685
|
-
import { dirname as
|
|
33323
|
+
import { dirname as dirname13 } from "node:path";
|
|
32686
33324
|
import { homedir as homedir11 } from "node:os";
|
|
32687
33325
|
async function checkRuntime(options = {}) {
|
|
32688
33326
|
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
@@ -32783,7 +33421,7 @@ async function checkFileCreatable(label, path15, probe, platform) {
|
|
|
32783
33421
|
detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
|
|
32784
33422
|
};
|
|
32785
33423
|
}
|
|
32786
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
33424
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname13(path15), probe, platform);
|
|
32787
33425
|
if (!parentCheck.ok) {
|
|
32788
33426
|
return {
|
|
32789
33427
|
ok: false,
|
|
@@ -32819,7 +33457,7 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
|
32819
33457
|
blockingPath: path15
|
|
32820
33458
|
};
|
|
32821
33459
|
}
|
|
32822
|
-
const parent =
|
|
33460
|
+
const parent = dirname13(path15);
|
|
32823
33461
|
if (parent === path15) {
|
|
32824
33462
|
return {
|
|
32825
33463
|
ok: false,
|
|
@@ -33344,11 +33982,13 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33344
33982
|
}
|
|
33345
33983
|
try {
|
|
33346
33984
|
const store = new StateStore(deps.runtimePaths.statePath);
|
|
33347
|
-
|
|
33348
|
-
|
|
33985
|
+
const inspection = await store.inspect();
|
|
33986
|
+
const result = await checkOrchestrationHealth({
|
|
33987
|
+
loadState: async () => inspection.state,
|
|
33349
33988
|
now: () => new Date,
|
|
33350
33989
|
heartbeatThresholdSeconds: config4.orchestration.progressHeartbeatSeconds
|
|
33351
33990
|
});
|
|
33991
|
+
return applyStateInspectionReport(result, inspection.report, deps.runtimePaths.statePath);
|
|
33352
33992
|
} catch (error2) {
|
|
33353
33993
|
return {
|
|
33354
33994
|
id: "orchestration",
|
|
@@ -33359,6 +33999,27 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33359
33999
|
};
|
|
33360
34000
|
}
|
|
33361
34001
|
}
|
|
34002
|
+
function applyStateInspectionReport(result, report, statePath) {
|
|
34003
|
+
if (!report) {
|
|
34004
|
+
return result;
|
|
34005
|
+
}
|
|
34006
|
+
const fileCorrupt = report.dropped.some((record3) => record3.section === "file");
|
|
34007
|
+
const details = [
|
|
34008
|
+
...result.details ?? [],
|
|
34009
|
+
`state path: ${statePath}`,
|
|
34010
|
+
...report.dropped.map((record3) => record3.section === "file" ? `state.json is unreadable: ${record3.reason}` : `invalid state record ${record3.section}["${record3.key}"]: ${record3.reason}`)
|
|
34011
|
+
];
|
|
34012
|
+
return {
|
|
34013
|
+
...result,
|
|
34014
|
+
severity: result.severity === "fail" ? "fail" : "warn",
|
|
34015
|
+
summary: fileCorrupt ? `state.json is unreadable and will be reset (renamed to state.json.corrupt-*) at next daemon startup; ${result.summary}` : `state.json has ${report.dropped.length} invalid record(s) that will be quarantined at next daemon startup; ${result.summary}`,
|
|
34016
|
+
details,
|
|
34017
|
+
suggestions: [
|
|
34018
|
+
...result.suggestions ?? [],
|
|
34019
|
+
fileCorrupt ? "back up the state file before the next daemon start if you want to attempt manual recovery" : "the daemon backs the original file up as state.json.quarantine-* before dropping these records"
|
|
34020
|
+
]
|
|
34021
|
+
};
|
|
34022
|
+
}
|
|
33362
34023
|
function formatError9(error2) {
|
|
33363
34024
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33364
34025
|
}
|
|
@@ -33401,7 +34062,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
33401
34062
|
init_core_home();
|
|
33402
34063
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
33403
34064
|
import { homedir as homedir13 } from "node:os";
|
|
33404
|
-
import { dirname as
|
|
34065
|
+
import { dirname as dirname14, join as join20, sep } from "node:path";
|
|
33405
34066
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
33406
34067
|
|
|
33407
34068
|
// src/runtime/migrate-core-home.ts
|
|
@@ -33485,8 +34146,8 @@ init_daemon_files();
|
|
|
33485
34146
|
|
|
33486
34147
|
// src/daemon/daemon-runtime.ts
|
|
33487
34148
|
init_daemon_status();
|
|
33488
|
-
|
|
33489
|
-
import {
|
|
34149
|
+
init_private_runtime_dir();
|
|
34150
|
+
import { rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
33490
34151
|
|
|
33491
34152
|
class DaemonRuntime {
|
|
33492
34153
|
paths;
|
|
@@ -33512,8 +34173,8 @@ class DaemonRuntime {
|
|
|
33512
34173
|
stdout_log: this.paths.stdoutLog,
|
|
33513
34174
|
stderr_log: this.paths.stderrLog
|
|
33514
34175
|
};
|
|
33515
|
-
await
|
|
33516
|
-
await
|
|
34176
|
+
await ensurePrivateRuntimeDir(this.paths.runtimeDir);
|
|
34177
|
+
await writeFile2(this.paths.pidFile, `${this.options.pid}
|
|
33517
34178
|
`);
|
|
33518
34179
|
await this.statusStore.save(this.currentStatus);
|
|
33519
34180
|
}
|
|
@@ -46188,7 +46849,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
46188
46849
|
});
|
|
46189
46850
|
tools.push({
|
|
46190
46851
|
name: "scheduled_list",
|
|
46191
|
-
description: "List pending one-shot scheduled tasks
|
|
46852
|
+
description: "List pending one-shot scheduled tasks created in the current chat. Use to recover task ids before cancelling, or to see what is scheduled. Owner-only in group chats. Routing and account are resolved from the current session; pass no other arguments.",
|
|
46192
46853
|
inputSchema: exports_external.object({}).strict(),
|
|
46193
46854
|
handler: async () => await asToolResult(async () => {
|
|
46194
46855
|
const tasks = await transport.scheduledList({ coordinatorSession });
|
|
@@ -46206,7 +46867,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
46206
46867
|
});
|
|
46207
46868
|
tools.push({
|
|
46208
46869
|
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.",
|
|
46870
|
+
description: "Cancel a pending scheduled task by id (only tasks created in the current chat). Owner-only in group chats. Returns whether a pending task with that id was found and cancelled. Routing is resolved from the current session.",
|
|
46210
46871
|
inputSchema: exports_external.object({
|
|
46211
46872
|
id: exports_external.string().min(1).describe("The scheduled task id, e.g. 'k8f2' (a leading # is allowed).")
|
|
46212
46873
|
}).strict(),
|
|
@@ -47416,7 +48077,10 @@ async function maybeRunFirstUseOnboarding(input) {
|
|
|
47416
48077
|
const agentExisted = Boolean(input.config.agents[agentName]);
|
|
47417
48078
|
input.config.workspaces[workspaceName] = { cwd };
|
|
47418
48079
|
input.config.agents[agentName] = template;
|
|
47419
|
-
await input.
|
|
48080
|
+
await input.saveFirstRunConfig({
|
|
48081
|
+
workspace: { name: workspaceName, cwd },
|
|
48082
|
+
agent: { name: agentName, config: template }
|
|
48083
|
+
});
|
|
47420
48084
|
const alias = `${workspaceName}:${agentName}`;
|
|
47421
48085
|
input.deps.print(t().misc.onboardingCreatedWorkspace(workspaceName, alias));
|
|
47422
48086
|
return {
|
|
@@ -47439,8 +48103,8 @@ function resolveTemplateChoice(answer, names) {
|
|
|
47439
48103
|
// src/cli-update.ts
|
|
47440
48104
|
init_plugin_home();
|
|
47441
48105
|
import { spawn as spawn4 } from "node:child_process";
|
|
47442
|
-
import { readFile as
|
|
47443
|
-
import { dirname as
|
|
48106
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
48107
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
47444
48108
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
47445
48109
|
|
|
47446
48110
|
// src/plugins/package-manager.ts
|
|
@@ -47448,9 +48112,14 @@ init_plugin_home();
|
|
|
47448
48112
|
import { spawn as spawn3 } from "node:child_process";
|
|
47449
48113
|
import { rm as rm4 } from "node:fs/promises";
|
|
47450
48114
|
import { join as join7 } from "node:path";
|
|
48115
|
+
function shellSpawnPlan(args) {
|
|
48116
|
+
const shell = process.platform === "win32";
|
|
48117
|
+
return { shell, args: shell ? args.map((arg) => `"${arg}"`) : args };
|
|
48118
|
+
}
|
|
47451
48119
|
async function defaultRunCommand(command, args, options) {
|
|
47452
48120
|
await new Promise((resolve, reject) => {
|
|
47453
|
-
const
|
|
48121
|
+
const plan = shellSpawnPlan(args);
|
|
48122
|
+
const child = spawn3(command, plan.args, { cwd: options.cwd, stdio: "inherit", shell: plan.shell });
|
|
47454
48123
|
child.on("error", reject);
|
|
47455
48124
|
child.on("exit", (code) => {
|
|
47456
48125
|
if (code === 0)
|
|
@@ -47462,7 +48131,8 @@ async function defaultRunCommand(command, args, options) {
|
|
|
47462
48131
|
}
|
|
47463
48132
|
async function silentRun(command, args, options) {
|
|
47464
48133
|
await new Promise((resolve, reject) => {
|
|
47465
|
-
const
|
|
48134
|
+
const plan = shellSpawnPlan(args);
|
|
48135
|
+
const child = spawn3(command, plan.args, { cwd: options.cwd, stdio: "ignore", shell: plan.shell });
|
|
47466
48136
|
child.on("error", reject);
|
|
47467
48137
|
child.on("exit", (code) => {
|
|
47468
48138
|
if (code === 0)
|
|
@@ -47638,7 +48308,7 @@ async function handleUpdateCli(args, deps) {
|
|
|
47638
48308
|
}
|
|
47639
48309
|
if (selected.targets.some((target) => target.kind === "plugin")) {
|
|
47640
48310
|
config4.plugins = updatedPlugins;
|
|
47641
|
-
await deps.
|
|
48311
|
+
await deps.savePlugins(updatedPlugins);
|
|
47642
48312
|
}
|
|
47643
48313
|
return 0;
|
|
47644
48314
|
}
|
|
@@ -47748,9 +48418,10 @@ function compareSemver2(a, b) {
|
|
|
47748
48418
|
return 0;
|
|
47749
48419
|
return left.prerelease ? -1 : 1;
|
|
47750
48420
|
}
|
|
48421
|
+
var spawnUsesShell = () => process.platform === "win32";
|
|
47751
48422
|
async function runCapture(command, args) {
|
|
47752
48423
|
return await new Promise((resolve, reject) => {
|
|
47753
|
-
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
48424
|
+
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: spawnUsesShell() });
|
|
47754
48425
|
let stdout2 = "";
|
|
47755
48426
|
let stderr = "";
|
|
47756
48427
|
child.stdout.setEncoding("utf8");
|
|
@@ -47767,7 +48438,7 @@ async function runCapture(command, args) {
|
|
|
47767
48438
|
}
|
|
47768
48439
|
async function runInherit(command, args) {
|
|
47769
48440
|
await new Promise((resolve, reject) => {
|
|
47770
|
-
const child = spawn4(command, args, { stdio: "inherit" });
|
|
48441
|
+
const child = spawn4(command, args, { stdio: "inherit", shell: spawnUsesShell() });
|
|
47771
48442
|
child.on("error", reject);
|
|
47772
48443
|
child.on("exit", (code) => {
|
|
47773
48444
|
if (code === 0)
|
|
@@ -47779,10 +48450,10 @@ async function runInherit(command, args) {
|
|
|
47779
48450
|
}
|
|
47780
48451
|
async function readPackageName() {
|
|
47781
48452
|
try {
|
|
47782
|
-
const here =
|
|
48453
|
+
const here = dirname8(fileURLToPath3(import.meta.url));
|
|
47783
48454
|
for (const candidate of [join11(here, "..", "package.json"), join11(here, "..", "..", "package.json")]) {
|
|
47784
48455
|
try {
|
|
47785
|
-
const parsed = JSON.parse(await
|
|
48456
|
+
const parsed = JSON.parse(await readFile9(candidate, "utf8"));
|
|
47786
48457
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
47787
48458
|
return parsed.name.trim();
|
|
47788
48459
|
} catch {}
|
|
@@ -48024,7 +48695,7 @@ async function addChannel(type, rawArgs, deps) {
|
|
|
48024
48695
|
return 1;
|
|
48025
48696
|
}
|
|
48026
48697
|
config4.channels = [...config4.channels ?? [], candidate];
|
|
48027
|
-
await deps.
|
|
48698
|
+
await deps.saveChannels(config4.channels);
|
|
48028
48699
|
deps.print(t().channelCli.channelAdded(type));
|
|
48029
48700
|
for (const line of provider.renderSummary(candidate))
|
|
48030
48701
|
deps.print(line);
|
|
@@ -48125,7 +48796,7 @@ async function removeChannel(type, rawArgs, deps) {
|
|
|
48125
48796
|
return 1;
|
|
48126
48797
|
}
|
|
48127
48798
|
config4.channels = config4.channels.filter((entry) => entry.id !== channel.id);
|
|
48128
|
-
await deps.
|
|
48799
|
+
await deps.saveChannels(config4.channels);
|
|
48129
48800
|
deps.print(t().channelCli.channelRemoved(channel.id));
|
|
48130
48801
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48131
48802
|
}
|
|
@@ -48147,7 +48818,7 @@ async function setChannelEnabled(type, enabled, rawArgs, deps) {
|
|
|
48147
48818
|
return 1;
|
|
48148
48819
|
}
|
|
48149
48820
|
channel.enabled = enabled;
|
|
48150
|
-
await deps.
|
|
48821
|
+
await deps.saveChannels(config4.channels);
|
|
48151
48822
|
deps.print(t().channelCli.channelEnabledToggled(channel.id, enabled));
|
|
48152
48823
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48153
48824
|
}
|
|
@@ -48169,7 +48840,7 @@ async function setChannelReplyMode(type, mode, rawArgs, deps) {
|
|
|
48169
48840
|
return 1;
|
|
48170
48841
|
}
|
|
48171
48842
|
channel.replyMode = mode;
|
|
48172
|
-
await deps.
|
|
48843
|
+
await deps.saveChannels(config4.channels);
|
|
48173
48844
|
deps.print(t().channelCli.channelReplyModeSet(channel.id, mode));
|
|
48174
48845
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48175
48846
|
}
|
|
@@ -48315,7 +48986,7 @@ async function addChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48315
48986
|
deps.print(validation.map((issue2) => issue2.message).join(";"));
|
|
48316
48987
|
return 1;
|
|
48317
48988
|
}
|
|
48318
|
-
await deps.
|
|
48989
|
+
await deps.saveChannels(config4.channels);
|
|
48319
48990
|
deps.print(t().channelCli.channelAccountAdded(type, accountId));
|
|
48320
48991
|
if (reEnabledChannel)
|
|
48321
48992
|
deps.print(t().channelCli.channelReEnabled(type));
|
|
@@ -48355,7 +49026,7 @@ async function removeChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48355
49026
|
return 1;
|
|
48356
49027
|
}
|
|
48357
49028
|
config4.channels = config4.channels.filter((channel) => channel.id !== existing.id);
|
|
48358
|
-
await deps.
|
|
49029
|
+
await deps.saveChannels(config4.channels);
|
|
48359
49030
|
deps.print(t().channelCli.channelAccountRemovedWithChannel(type, accountId));
|
|
48360
49031
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48361
49032
|
}
|
|
@@ -48372,7 +49043,7 @@ async function removeChannelAccount(type, accountId, rawArgs, deps) {
|
|
|
48372
49043
|
deps.print(t().channelCli.channelAccountDefaultSwitched(options.defaultAccount));
|
|
48373
49044
|
}
|
|
48374
49045
|
existing.options = options;
|
|
48375
|
-
await deps.
|
|
49046
|
+
await deps.saveChannels(config4.channels);
|
|
48376
49047
|
deps.print(t().channelCli.channelAccountRemoved(type, accountId));
|
|
48377
49048
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48378
49049
|
}
|
|
@@ -48426,7 +49097,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
48426
49097
|
return 1;
|
|
48427
49098
|
}
|
|
48428
49099
|
existing.options = options;
|
|
48429
|
-
await deps.
|
|
49100
|
+
await deps.saveChannels(config4.channels);
|
|
48430
49101
|
deps.print(t().channelCli.channelAccountEnabledToggled(type, accountId, enabled));
|
|
48431
49102
|
return await maybeRestartAfterMutation(restartFlags.restart, deps);
|
|
48432
49103
|
}
|
|
@@ -48434,7 +49105,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
48434
49105
|
// src/plugins/plugin-cli.ts
|
|
48435
49106
|
init_core_home();
|
|
48436
49107
|
init_plugin_home();
|
|
48437
|
-
import { readFile as
|
|
49108
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
48438
49109
|
import { isAbsolute, join as join13, resolve } from "node:path";
|
|
48439
49110
|
init_plugin_loader();
|
|
48440
49111
|
init_validate_plugin();
|
|
@@ -48444,14 +49115,15 @@ init_channel_scope();
|
|
|
48444
49115
|
init_plugin_loader();
|
|
48445
49116
|
init_validate_plugin();
|
|
48446
49117
|
init_known_plugins();
|
|
48447
|
-
|
|
49118
|
+
init_plugin_renames();
|
|
49119
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
48448
49120
|
import { join as join12 } from "node:path";
|
|
48449
49121
|
function suggestedPluginPackageForChannel(type) {
|
|
48450
49122
|
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
48451
49123
|
}
|
|
48452
49124
|
async function readDependencyEntries(pluginHome) {
|
|
48453
49125
|
try {
|
|
48454
|
-
const raw = await
|
|
49126
|
+
const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
|
|
48455
49127
|
const parsed = JSON.parse(raw);
|
|
48456
49128
|
const out = {};
|
|
48457
49129
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -48475,8 +49147,8 @@ async function inspectPlugins(input) {
|
|
|
48475
49147
|
}
|
|
48476
49148
|
const importPlugin = input.importPlugin ?? importPluginFromHome;
|
|
48477
49149
|
const allConfigured = input.config.plugins;
|
|
48478
|
-
const filterByName = input.pluginName
|
|
48479
|
-
if (filterByName && !allConfigured.some((plugin) => plugin.name === filterByName)) {
|
|
49150
|
+
const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
|
|
49151
|
+
if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
|
|
48480
49152
|
return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
|
|
48481
49153
|
}
|
|
48482
49154
|
const pushIfRelevant = (issue2) => {
|
|
@@ -48523,6 +49195,8 @@ async function inspectPlugins(input) {
|
|
|
48523
49195
|
}
|
|
48524
49196
|
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
48525
49197
|
for (const channel of input.config.channels) {
|
|
49198
|
+
if (channel.enabled === false)
|
|
49199
|
+
continue;
|
|
48526
49200
|
if (builtInChannelTypes.has(channel.type))
|
|
48527
49201
|
continue;
|
|
48528
49202
|
const provider = channelProviders.get(channel.type);
|
|
@@ -48550,12 +49224,31 @@ async function inspectPlugins(input) {
|
|
|
48550
49224
|
init_known_plugins();
|
|
48551
49225
|
init_plugin_renames();
|
|
48552
49226
|
init_i18n();
|
|
49227
|
+
function findPluginSpecViolation(spec, platform) {
|
|
49228
|
+
if (spec.includes('"'))
|
|
49229
|
+
return "double-quote";
|
|
49230
|
+
if (platform === "win32" && spec.includes("%"))
|
|
49231
|
+
return "percent-on-windows";
|
|
49232
|
+
return null;
|
|
49233
|
+
}
|
|
49234
|
+
function invalidSpecMessage(specs, platform) {
|
|
49235
|
+
for (const spec of specs) {
|
|
49236
|
+
if (!spec)
|
|
49237
|
+
continue;
|
|
49238
|
+
const violation = findPluginSpecViolation(spec, platform);
|
|
49239
|
+
if (violation === "double-quote")
|
|
49240
|
+
return t().pluginCli.pluginSpecHasDoubleQuote(spec);
|
|
49241
|
+
if (violation === "percent-on-windows")
|
|
49242
|
+
return t().pluginCli.pluginSpecHasPercentOnWindows(spec);
|
|
49243
|
+
}
|
|
49244
|
+
return null;
|
|
49245
|
+
}
|
|
48553
49246
|
function looksLikePath(spec) {
|
|
48554
49247
|
return spec === "." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || spec.startsWith(".\\") || spec.startsWith("..\\") || spec.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(spec) || isAbsolute(spec);
|
|
48555
49248
|
}
|
|
48556
49249
|
async function readDependencyEntries2(pluginHome) {
|
|
48557
49250
|
try {
|
|
48558
|
-
const raw = await
|
|
49251
|
+
const raw = await readFile11(join13(pluginHome, "package.json"), "utf8");
|
|
48559
49252
|
const parsed = JSON.parse(raw);
|
|
48560
49253
|
const out = {};
|
|
48561
49254
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -48581,7 +49274,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
|
|
|
48581
49274
|
return name;
|
|
48582
49275
|
}
|
|
48583
49276
|
try {
|
|
48584
|
-
const raw = await
|
|
49277
|
+
const raw = await readFile11(join13(installSpec, "package.json"), "utf8");
|
|
48585
49278
|
const parsed = JSON.parse(raw);
|
|
48586
49279
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
48587
49280
|
return parsed.name.trim();
|
|
@@ -48724,6 +49417,11 @@ async function addPlugin(packageSpec, rawArgs, deps) {
|
|
|
48724
49417
|
deps.print(t().pluginCli.unrecognizedArgs(flags.rest.join(" ")));
|
|
48725
49418
|
return 1;
|
|
48726
49419
|
}
|
|
49420
|
+
const invalidSpec = invalidSpecMessage([packageSpec, flags.version], deps.platform ?? process.platform);
|
|
49421
|
+
if (invalidSpec) {
|
|
49422
|
+
deps.print(invalidSpec);
|
|
49423
|
+
return 1;
|
|
49424
|
+
}
|
|
48727
49425
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
48728
49426
|
await ensurePluginHome(pluginHome);
|
|
48729
49427
|
const installSpec = looksLikePath(packageSpec) && !isAbsolute(packageSpec) ? resolve(process.cwd(), packageSpec) : packageSpec;
|
|
@@ -48761,7 +49459,7 @@ async function addPlugin(packageSpec, rawArgs, deps) {
|
|
|
48761
49459
|
} else {
|
|
48762
49460
|
config4.plugins = [...config4.plugins, next];
|
|
48763
49461
|
}
|
|
48764
|
-
await deps.
|
|
49462
|
+
await deps.savePlugins(config4.plugins);
|
|
48765
49463
|
deps.print(t().pluginCli.pluginInstalled(recordedName));
|
|
48766
49464
|
if (summary.channels.length > 0) {
|
|
48767
49465
|
deps.print(t().pluginCli.providesChannels(summary.channels.join(", ")));
|
|
@@ -48787,7 +49485,7 @@ async function removePlugin(packageName, rawArgs, deps) {
|
|
|
48787
49485
|
}
|
|
48788
49486
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
48789
49487
|
const validate = deps.validateInstalledPlugin ?? ((name) => validateInstalledPluginDefault(name, pluginHome));
|
|
48790
|
-
const guard = await dependencyGuard(
|
|
49488
|
+
const guard = await dependencyGuard(existing.name, config4, validate);
|
|
48791
49489
|
if (!guard.allow) {
|
|
48792
49490
|
if (guard.reason)
|
|
48793
49491
|
deps.print(guard.reason);
|
|
@@ -48797,14 +49495,14 @@ async function removePlugin(packageName, rawArgs, deps) {
|
|
|
48797
49495
|
await removePluginPackage({ packageName: name, pluginHome });
|
|
48798
49496
|
});
|
|
48799
49497
|
try {
|
|
48800
|
-
await remove(
|
|
49498
|
+
await remove(existing.name);
|
|
48801
49499
|
} catch (error2) {
|
|
48802
|
-
deps.print(t().pluginCli.pluginUninstallFailed(
|
|
49500
|
+
deps.print(t().pluginCli.pluginUninstallFailed(existing.name, describeError(error2)));
|
|
48803
49501
|
return 1;
|
|
48804
49502
|
}
|
|
48805
|
-
config4.plugins = config4.plugins.filter((entry) => entry.name !==
|
|
48806
|
-
await deps.
|
|
48807
|
-
deps.print(t().pluginCli.pluginRemoved(
|
|
49503
|
+
config4.plugins = config4.plugins.filter((entry) => entry.name !== existing.name);
|
|
49504
|
+
await deps.savePlugins(config4.plugins);
|
|
49505
|
+
deps.print(t().pluginCli.pluginRemoved(existing.name));
|
|
48808
49506
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48809
49507
|
}
|
|
48810
49508
|
async function updatePlugins(args, deps) {
|
|
@@ -48824,6 +49522,11 @@ async function updatePlugins(args, deps) {
|
|
|
48824
49522
|
deps.print("--all cannot be combined with --version");
|
|
48825
49523
|
return 1;
|
|
48826
49524
|
}
|
|
49525
|
+
const invalidSpec = invalidSpecMessage([flags.version], deps.platform ?? process.platform);
|
|
49526
|
+
if (invalidSpec) {
|
|
49527
|
+
deps.print(invalidSpec);
|
|
49528
|
+
return 1;
|
|
49529
|
+
}
|
|
48827
49530
|
const config4 = await deps.loadConfig();
|
|
48828
49531
|
ensurePluginsArray(config4);
|
|
48829
49532
|
const pluginHome = deps.pluginHome ?? resolvePluginHome();
|
|
@@ -48880,7 +49583,7 @@ async function updatePlugins(args, deps) {
|
|
|
48880
49583
|
deps.print(t().pluginCli.providesChannels(summary.channels.join(", ")));
|
|
48881
49584
|
}
|
|
48882
49585
|
}
|
|
48883
|
-
await deps.
|
|
49586
|
+
await deps.savePlugins(config4.plugins);
|
|
48884
49587
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48885
49588
|
}
|
|
48886
49589
|
async function setPluginEnabled(packageName, enabled, rawArgs, deps) {
|
|
@@ -48911,7 +49614,7 @@ async function setPluginEnabled(packageName, enabled, rawArgs, deps) {
|
|
|
48911
49614
|
}
|
|
48912
49615
|
}
|
|
48913
49616
|
existing.enabled = enabled;
|
|
48914
|
-
await deps.
|
|
49617
|
+
await deps.savePlugins(config4.plugins);
|
|
48915
49618
|
deps.print(t().pluginCli.pluginEnabledToggled(packageName, enabled));
|
|
48916
49619
|
return await maybeRestartAfterMutation2(flags.restart, deps);
|
|
48917
49620
|
}
|
|
@@ -49429,7 +50132,9 @@ async function defaultUpdate(args, input) {
|
|
|
49429
50132
|
const store = await createCliConfigStore();
|
|
49430
50133
|
const deps = {
|
|
49431
50134
|
loadConfig: async () => await store.load(),
|
|
49432
|
-
|
|
50135
|
+
savePlugins: async (plugins) => {
|
|
50136
|
+
await store.replacePlugins(plugins);
|
|
50137
|
+
},
|
|
49433
50138
|
readCurrentVersion: readVersion,
|
|
49434
50139
|
print: input.print,
|
|
49435
50140
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
@@ -49438,6 +50143,27 @@ async function defaultUpdate(args, input) {
|
|
|
49438
50143
|
};
|
|
49439
50144
|
return await handleUpdateCli(args, deps);
|
|
49440
50145
|
}
|
|
50146
|
+
function warnStateLoadReport(store, writeStderr = (text) => process.stderr.write(text)) {
|
|
50147
|
+
const report = store.lastLoadReport;
|
|
50148
|
+
if (!report)
|
|
50149
|
+
return;
|
|
50150
|
+
for (const record3 of report.dropped) {
|
|
50151
|
+
writeStderr(`[xacpx] state.record_quarantined section=${record3.section}${record3.key ? ` key=${record3.key}` : ""} reason=${record3.reason}
|
|
50152
|
+
`);
|
|
50153
|
+
}
|
|
50154
|
+
if (report.corruptPath) {
|
|
50155
|
+
writeStderr(`[xacpx] state.file_corrupt unreadable state.json renamed to ${report.corruptPath}
|
|
50156
|
+
`);
|
|
50157
|
+
}
|
|
50158
|
+
if (report.quarantinePath) {
|
|
50159
|
+
writeStderr(`[xacpx] state.file_quarantined original state.json backed up to ${report.quarantinePath}
|
|
50160
|
+
`);
|
|
50161
|
+
}
|
|
50162
|
+
if (report.backupError) {
|
|
50163
|
+
writeStderr(`[xacpx] state.quarantine_backup_failed ${report.backupError}
|
|
50164
|
+
`);
|
|
50165
|
+
}
|
|
50166
|
+
}
|
|
49441
50167
|
async function runOnboardingBeforeStart(input) {
|
|
49442
50168
|
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
49443
50169
|
await ensureConfigExists(runtimePaths.configPath);
|
|
@@ -49445,10 +50171,14 @@ async function runOnboardingBeforeStart(input) {
|
|
|
49445
50171
|
const stateStore = new StateStore(runtimePaths.statePath);
|
|
49446
50172
|
const config4 = await configStore.load();
|
|
49447
50173
|
const state = await stateStore.load();
|
|
50174
|
+
warnStateLoadReport(stateStore);
|
|
49448
50175
|
const result = await maybeRunFirstUseOnboarding({
|
|
49449
50176
|
config: config4,
|
|
49450
50177
|
state,
|
|
49451
|
-
|
|
50178
|
+
saveFirstRunConfig: async ({ workspace: workspace3, agent: agent3 }) => {
|
|
50179
|
+
await configStore.upsertWorkspace(workspace3.name, workspace3.cwd);
|
|
50180
|
+
await configStore.upsertAgent(agent3.name, agent3.config);
|
|
50181
|
+
},
|
|
49452
50182
|
deps: {
|
|
49453
50183
|
print: input.print,
|
|
49454
50184
|
cwd: input.cwd,
|
|
@@ -49659,7 +50389,7 @@ async function handleLaterCli(args, deps) {
|
|
|
49659
50389
|
}
|
|
49660
50390
|
async function laterList(print) {
|
|
49661
50391
|
const scheduled = await createCliScheduledTaskService();
|
|
49662
|
-
print(renderLaterList(scheduled.
|
|
50392
|
+
print(renderLaterList(scheduled.listPendingAllChats(), (alias) => toDisplaySessionAlias(alias)));
|
|
49663
50393
|
return 0;
|
|
49664
50394
|
}
|
|
49665
50395
|
async function laterCancel(rawId, print) {
|
|
@@ -49669,7 +50399,7 @@ async function laterCancel(rawId, print) {
|
|
|
49669
50399
|
return 1;
|
|
49670
50400
|
}
|
|
49671
50401
|
const scheduled = await createCliScheduledTaskService();
|
|
49672
|
-
const ok = await scheduled.
|
|
50402
|
+
const ok = await scheduled.cancelPendingAnyChat(id);
|
|
49673
50403
|
if (!ok) {
|
|
49674
50404
|
print(t().cli.laterNotFound(id));
|
|
49675
50405
|
print(t().cli.laterNotFoundHint);
|
|
@@ -49682,6 +50412,7 @@ async function createCliScheduledTaskService() {
|
|
|
49682
50412
|
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
49683
50413
|
const stateStore = new StateStore(runtimePaths.statePath);
|
|
49684
50414
|
const state = await stateStore.load();
|
|
50415
|
+
warnStateLoadReport(stateStore);
|
|
49685
50416
|
return new ScheduledTaskService(state, stateStore);
|
|
49686
50417
|
}
|
|
49687
50418
|
function resolveConfigPathForCurrentEnv() {
|
|
@@ -49784,14 +50515,12 @@ async function createFirstRunSession(runtime, plan) {
|
|
|
49784
50515
|
}
|
|
49785
50516
|
async function rollbackFirstRunConfig(runtime, plan) {
|
|
49786
50517
|
try {
|
|
49787
|
-
|
|
49788
|
-
|
|
49789
|
-
delete config4.workspaces[plan.workspace];
|
|
50518
|
+
if (!plan.rollback.workspaceExisted) {
|
|
50519
|
+
await runtime.configStore.removeWorkspace(plan.workspace);
|
|
49790
50520
|
}
|
|
49791
|
-
if (!plan.rollback.agentExisted
|
|
49792
|
-
|
|
50521
|
+
if (!plan.rollback.agentExisted) {
|
|
50522
|
+
await runtime.configStore.removeAgent(plan.agent);
|
|
49793
50523
|
}
|
|
49794
|
-
await runtime.configStore.save(config4);
|
|
49795
50524
|
} catch (error2) {
|
|
49796
50525
|
await runtime.logger.error("onboarding.rollback_failed", "failed to roll back first-run config", {
|
|
49797
50526
|
alias: plan.alias,
|
|
@@ -49823,7 +50552,9 @@ async function defaultMcpStdio(args, deps = {}) {
|
|
|
49823
50552
|
await ensureConfigExists(runtimePaths.configPath);
|
|
49824
50553
|
const config4 = await loadConfig(runtimePaths.configPath);
|
|
49825
50554
|
availableAgents = Object.keys(config4.agents);
|
|
49826
|
-
const
|
|
50555
|
+
const stateStore = new StateStore(runtimePaths.statePath);
|
|
50556
|
+
const state = await stateStore.load();
|
|
50557
|
+
warnStateLoadReport(stateStore, deps.stderr ?? ((text) => process.stderr.write(text)));
|
|
49827
50558
|
const resolveIdentity = createMcpStdioIdentityResolver({
|
|
49828
50559
|
parsedCoordinatorSession,
|
|
49829
50560
|
sourceHandle,
|
|
@@ -49887,7 +50618,9 @@ async function createChannelCliDeps(input) {
|
|
|
49887
50618
|
const controller = input.controller ?? createDefaultController();
|
|
49888
50619
|
const base = {
|
|
49889
50620
|
loadConfig: async () => await store.load(),
|
|
49890
|
-
|
|
50621
|
+
saveChannels: async (channels) => {
|
|
50622
|
+
await store.replaceChannels(channels);
|
|
50623
|
+
},
|
|
49891
50624
|
print: input.print,
|
|
49892
50625
|
stderr: input.stderr ?? ((text) => process.stderr.write(text)),
|
|
49893
50626
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
@@ -49910,7 +50643,9 @@ async function createPluginCliDeps(input) {
|
|
|
49910
50643
|
const controller = input.controller ?? createDefaultController();
|
|
49911
50644
|
const base = {
|
|
49912
50645
|
loadConfig: async () => await store.load(),
|
|
49913
|
-
|
|
50646
|
+
savePlugins: async (plugins) => {
|
|
50647
|
+
await store.replacePlugins(plugins);
|
|
50648
|
+
},
|
|
49914
50649
|
print: input.print,
|
|
49915
50650
|
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
49916
50651
|
promptText: input.promptText ?? defaultPromptText,
|
|
@@ -50052,7 +50787,7 @@ function safeDaemonLogPaths() {
|
|
|
50052
50787
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
50053
50788
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
50054
50789
|
return {
|
|
50055
|
-
appLog: join20(
|
|
50790
|
+
appLog: join20(dirname14(configPath), "runtime", "app.log"),
|
|
50056
50791
|
stderrLog: paths.stderrLog
|
|
50057
50792
|
};
|
|
50058
50793
|
} catch {
|