@epoch-agent/core 0.15.0 → 0.17.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +7536 -7487
  2. package/dist/index.js +399 -252
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { join, dirname, resolve, relative, isAbsolute, sep, extname, basename }
4
4
  import { execFile, execSync, execFileSync, spawn } from 'child_process';
5
5
  import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, PROJECT_DIR_NAME, guardSpawn, t, suggestKey, misspelledKeyIssues, openDatabase, migrate, releaseDatabase, parseLenient, issueDetails, splitWords, unknownKeyIssues, checkpointsDir, pluginsDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSettingsPath, projectLocalSettingsPath, projectPoliciesDir, trustedImportsPath, canProbeGit, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, countCodePoints, headCodePoints, attachmentsDir, isInWorkspace, workspacesPath, samePath, lastSessionsPath, automationLogsDir, projectComplianceDir, projectSkillsDir, trustPath, projectCommandsDir, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, envPath, checkDangerousCommand, checkObfuscation, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS, tailCodePoints } from '@epoch-agent/infra';
6
6
  export { IS_WINDOWS, WINDOWS_HIDE_FLAGS, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, createLogger, detectBackend, getDefaultShell, getPythonCommand, isInWorkspace, isReadOnlyCommand, isolate, maskApiKey, probeBubblewrap, probeSeatbelt, complianceDir as resolveComplianceDir, policiesDir as resolvePoliciesDir, setLogLevel } from '@epoch-agent/infra';
7
- import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, CURRENCIES, LANGS, PERMISSION_LEVELS, SHELL_KINDS, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, SCHEDULE_INTERVAL_MINUTES, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, isWirePluginArchiveName, systemNote, formatMoney, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, isPromptOversize, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, truncateToBudget, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, imagePartOmittedSummary, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
7
+ import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, CURRENCIES, LANGS, isSettingsPaneId, SETTINGS_PANE_IDS, PERMISSION_LEVELS, SHELL_KINDS, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, SCHEDULE_INTERVAL_MINUTES, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, isWirePluginArchiveName, systemNote, formatMoney, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, isPromptOversize, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, truncateToBudget, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, imagePartOmittedSummary, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
8
8
  export { API_KEY_ENV_VARS, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, ToolExposure, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
9
9
  import { createRequire } from 'module';
10
10
  import { randomUUID, createHash, randomBytes } from 'crypto';
@@ -34,7 +34,8 @@ __export(schtasks_exports, {
34
34
  quoteTaskRun: () => quoteTaskRun,
35
35
  scheduleArgs: () => scheduleArgs,
36
36
  specFromTrigger: () => specFromTrigger,
37
- specFromXml: () => specFromXml
37
+ specFromXml: () => specFromXml,
38
+ taskRunArgv: () => taskRunArgv
38
39
  });
39
40
  function createSchtasksBackend(opts = {}) {
40
41
  const run = opts.run ?? runCommand2;
@@ -99,9 +100,11 @@ function createSchtasksBackend(opts = {}) {
99
100
  async inspect(osTaskId) {
100
101
  const res = await run("schtasks", ["/Query", "/TN", osTaskId, "/XML"]);
101
102
  if (res.code !== 0) return null;
103
+ const commandLine = commandLineFromXml(res.stdout);
102
104
  return {
103
105
  osTaskId,
104
- commandLine: commandLineFromXml(res.stdout),
106
+ commandLine,
107
+ argv: taskRunArgv(commandLine),
105
108
  spec: specFromXml(res.stdout),
106
109
  enabled: !/<Enabled>\s*false\s*<\/Enabled>/i.test(res.stdout)
107
110
  };
@@ -118,6 +121,13 @@ function createSchtasksBackend(opts = {}) {
118
121
  function quoteTaskRun(argv) {
119
122
  return argv.map((part) => `"${part.replace(/"/g, "")}"`).join(" ");
120
123
  }
124
+ function taskRunArgv(commandLine) {
125
+ const out = [];
126
+ const re = /"([^"]*)"|(\S+)/g;
127
+ let m;
128
+ while ((m = re.exec(commandLine)) !== null) out.push(m[1] ?? m[2] ?? "");
129
+ return out;
130
+ }
121
131
  function scheduleArgs(trigger) {
122
132
  if (trigger.kind === "interval") {
123
133
  const slots = intervalSlots(trigger.everyMinutes);
@@ -331,9 +341,11 @@ function createLaunchdBackend(opts = {}) {
331
341
  return null;
332
342
  }
333
343
  const disabled = await isDisabled(run, label);
344
+ const argv = extractProgramArguments(xml2);
334
345
  return {
335
346
  osTaskId: label,
336
- commandLine: extractProgramArguments(xml2).join(" "),
347
+ commandLine: argv.join(" "),
348
+ argv,
337
349
  spec: extractCalendarSpec(xml2),
338
350
  enabled: !disabled
339
351
  };
@@ -1001,6 +1013,11 @@ function createAskQuestionTool(deps) {
1001
1013
  }
1002
1014
  };
1003
1015
  }
1016
+ // src/agent/types.ts
1017
+ function isAbortSignal(signal) {
1018
+ return !!signal && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
1019
+ }
1020
+ // src/tools/builtin/code-exec.ts
1004
1021
  var MAX_CODE_LENGTH = 1e5;
1005
1022
  function createCodeExecTool(sandbox) {
1006
1023
  const iso = sandbox.getIsolation();
@@ -1046,7 +1063,7 @@ function createCodeExecTool(sandbox) {
1046
1063
  },
1047
1064
  operation: "code_exec",
1048
1065
  describeTarget: (args) => `${String(args["language"] ?? "")}:${String(args["code"] ?? "")}`,
1049
- async execute(args, _ctx) {
1066
+ async execute(args, ctx) {
1050
1067
  const start = Date.now();
1051
1068
  const language = String(args["language"] ?? "");
1052
1069
  const code = String(args["code"] ?? "");
@@ -1084,7 +1101,8 @@ function createCodeExecTool(sandbox) {
1084
1101
  language,
1085
1102
  code,
1086
1103
  timeout,
1087
- allowNetwork: args["allow_network"] === true
1104
+ allowNetwork: args["allow_network"] === true,
1105
+ ...isAbortSignal(ctx.signal) ? { signal: ctx.signal } : {}
1088
1106
  });
1089
1107
  const header = `[\u9694\u79BB: ${result.isolation === "os" ? "OS \u7EA7" : "\u4EC5\u8FDB\u7A0B\u7EA7"}]`;
1090
1108
  const body2 = result.output || "(\u65E0\u8F93\u51FA)";
@@ -1096,7 +1114,9 @@ ${body2}`,
1096
1114
  error: {
1097
1115
  code: 6004,
1098
1116
  message: result.error ?? "\u6267\u884C\u5931\u8D25",
1099
- ...result.errorType === "oom" ? { suggestion: "\u51CF\u5C11\u5185\u5B58\u5360\u7528\uFF0C\u6216\u5206\u6279\u5904\u7406\u6570\u636E" } : result.errorType === "timeout" ? { suggestion: "\u4F18\u5316\u7B97\u6CD5\uFF0C\u6216\u63D0\u9AD8 timeout\uFF08\u4E0A\u9650 120 \u79D2\uFF09" } : {}
1117
+ ...result.errorType === "oom" ? { suggestion: "\u51CF\u5C11\u5185\u5B58\u5360\u7528\uFF0C\u6216\u5206\u6279\u5904\u7406\u6570\u636E" } : (
1118
+ result.errorType === "timeout" && !ctx.signal?.aborted ? { suggestion: "\u4F18\u5316\u7B97\u6CD5\uFF0C\u6216\u63D0\u9AD8 timeout\uFF08\u4E0A\u9650 120 \u79D2\uFF09" } : {}
1119
+ )
1100
1120
  },
1101
1121
  duration: result.duration
1102
1122
  };
@@ -1106,6 +1126,218 @@ ${body2}`, duration: result.duration };
1106
1126
  }
1107
1127
  };
1108
1128
  }
1129
+ var WINDOW_CHARS = 1024;
1130
+ var NGRAM = 16;
1131
+ var MAX_DISTINCT_RATIO = 0.2;
1132
+ var CHECK_EVERY_CHARS = 256;
1133
+ var SUSTAIN_CHECKS = 2;
1134
+ var MIN_NORMALIZED_CHARS = 256;
1135
+ function normalizeRuns(text) {
1136
+ return text.replace(/\s+/g, " ").replace(/(.)\1{3,}/gu, (_m, c) => c.repeat(3));
1137
+ }
1138
+ function distinctNgramRatio(text, n = NGRAM) {
1139
+ if (text.length < n) return 1;
1140
+ const seen = /* @__PURE__ */ new Set();
1141
+ let total = 0;
1142
+ for (let i = 0; i + n <= text.length; i++) {
1143
+ seen.add(text.slice(i, i + n));
1144
+ total++;
1145
+ }
1146
+ return total === 0 ? 1 : seen.size / total;
1147
+ }
1148
+ var RepetitionDetector = class {
1149
+ buf = "";
1150
+ sinceCheck = 0;
1151
+ streak = 0;
1152
+ chars = 0;
1153
+ hit = null;
1154
+ push(delta) {
1155
+ if (this.hit) return true;
1156
+ if (!delta) return false;
1157
+ this.chars += delta.length;
1158
+ this.buf = (this.buf + delta).slice(-WINDOW_CHARS);
1159
+ this.sinceCheck += delta.length;
1160
+ if (this.sinceCheck < CHECK_EVERY_CHARS) return false;
1161
+ this.sinceCheck = 0;
1162
+ if (this.buf.length < WINDOW_CHARS) return false;
1163
+ const norm = normalizeRuns(this.buf);
1164
+ if (norm.length < MIN_NORMALIZED_CHARS) {
1165
+ this.streak = 0;
1166
+ return false;
1167
+ }
1168
+ const ratio = distinctNgramRatio(norm);
1169
+ if (ratio > MAX_DISTINCT_RATIO) {
1170
+ this.streak = 0;
1171
+ return false;
1172
+ }
1173
+ if (++this.streak < SUSTAIN_CHECKS) return false;
1174
+ this.hit = { chars: this.chars, distinctRatio: ratio };
1175
+ return true;
1176
+ }
1177
+ pushAll(text) {
1178
+ for (let i = 0; i < text.length; i += CHECK_EVERY_CHARS) {
1179
+ if (this.push(text.slice(i, i + CHECK_EVERY_CHARS))) return true;
1180
+ }
1181
+ return false;
1182
+ }
1183
+ isLooping() {
1184
+ return this.hit !== null;
1185
+ }
1186
+ getInfo() {
1187
+ if (!this.hit) return null;
1188
+ return t("agent_run.looping_info", {
1189
+ chars: this.hit.chars,
1190
+ percent: Math.round(this.hit.distinctRatio * 100)
1191
+ });
1192
+ }
1193
+ };
1194
+ // src/agent/turn.ts
1195
+ async function* runTurn(opts) {
1196
+ if (opts.streaming) {
1197
+ return yield* runTurnStreaming(opts);
1198
+ }
1199
+ return yield* runTurnBuffered(opts);
1200
+ }
1201
+ async function* runTurnStreaming(opts) {
1202
+ const { provider, input, guard, reasoningGuard } = opts;
1203
+ const repetition = new RepetitionDetector();
1204
+ const reasoningRepetition = new RepetitionDetector();
1205
+ let text = "";
1206
+ let reasoningText = "";
1207
+ let finishReason = "stop";
1208
+ let looping;
1209
+ let usage;
1210
+ const toolCalls = [];
1211
+ let streamError;
1212
+ let model;
1213
+ let requestedModel;
1214
+ let warnings;
1215
+ const ac = new AbortController();
1216
+ const unlink = linkAbort(input.signal, ac);
1217
+ try {
1218
+ for await (const chunk of provider.stream({ ...input, signal: ac.signal })) {
1219
+ switch (chunk.type) {
1220
+ case "text-delta": {
1221
+ const raw = chunk.textDelta ?? "";
1222
+ if (!raw) break;
1223
+ const delta = guard ? guard.push(raw) : raw;
1224
+ if (!delta) break;
1225
+ text += delta;
1226
+ yield { type: "text-delta", text: delta };
1227
+ if (repetition.push(delta)) looping = repetition;
1228
+ break;
1229
+ }
1230
+ case "reasoning-delta": {
1231
+ const raw = chunk.textDelta ?? "";
1232
+ if (!raw) break;
1233
+ const delta = reasoningGuard ? reasoningGuard.push(raw) : raw;
1234
+ if (!delta) break;
1235
+ reasoningText += delta;
1236
+ yield { type: "reasoning-delta", text: delta };
1237
+ if (reasoningRepetition.push(delta)) looping = reasoningRepetition;
1238
+ break;
1239
+ }
1240
+ case "tool-call":
1241
+ if (chunk.toolCall) {
1242
+ toolCalls.push({
1243
+ toolCallId: chunk.toolCall.toolCallId,
1244
+ toolName: chunk.toolCall.toolName,
1245
+ input: chunk.toolCall.input
1246
+ });
1247
+ }
1248
+ break;
1249
+ case "finish":
1250
+ if (chunk.usage) usage = chunk.usage;
1251
+ if (chunk.finishReason) finishReason = chunk.finishReason;
1252
+ if (chunk.model) model = chunk.model;
1253
+ if (chunk.requestedModel) requestedModel = chunk.requestedModel;
1254
+ if (chunk.warnings) warnings = chunk.warnings;
1255
+ break;
1256
+ case "error":
1257
+ streamError = chunk.error ?? t("agent_run.unknown_provider_error");
1258
+ break;
1259
+ case "tool-result":
1260
+ break;
1261
+ }
1262
+ if (looping) {
1263
+ ac.abort();
1264
+ break;
1265
+ }
1266
+ }
1267
+ } finally {
1268
+ unlink();
1269
+ }
1270
+ if (streamError && !looping) throw new Error(streamError);
1271
+ if (guard) {
1272
+ const tail = guard.flush();
1273
+ if (tail) {
1274
+ text += tail;
1275
+ yield { type: "text-delta", text: tail };
1276
+ }
1277
+ }
1278
+ if (reasoningGuard) {
1279
+ const tail = reasoningGuard.flush();
1280
+ if (tail) {
1281
+ reasoningText += tail;
1282
+ yield { type: "reasoning-delta", text: tail };
1283
+ }
1284
+ }
1285
+ return {
1286
+ text,
1287
+ ...usage ? { usage } : {},
1288
+ finishReason: looping ? "looping" : finishReason,
1289
+ ...looping ? { loopingInfo: looping.getInfo() ?? "" } : {},
1290
+ ...toolCalls.length > 0 ? { toolCalls } : {},
1291
+ ...reasoningText ? { reasoningText } : {},
1292
+ ...model ? { model } : {},
1293
+ ...requestedModel ? { requestedModel } : {},
1294
+ ...warnings && warnings.length > 0 ? { warnings } : {}
1295
+ };
1296
+ }
1297
+ async function* runTurnBuffered(opts) {
1298
+ const { provider, input, guard, reasoningGuard } = opts;
1299
+ const result = await provider.generate(input);
1300
+ const reasoningText = result.reasoningText ? filterWhole(reasoningGuard, result.reasoningText) : result.reasoningText;
1301
+ if (reasoningText) {
1302
+ yield { type: "reasoning-delta", text: reasoningText };
1303
+ }
1304
+ const text = filterWhole(guard, result.text);
1305
+ if (text) {
1306
+ yield { type: "text-delta", text };
1307
+ }
1308
+ const { reasoningText: _replaced, ...rest } = result;
1309
+ const textLoop = new RepetitionDetector();
1310
+ const reasoningLoop = new RepetitionDetector();
1311
+ textLoop.pushAll(text);
1312
+ if (reasoningText) reasoningLoop.pushAll(reasoningText);
1313
+ let looping;
1314
+ if (textLoop.isLooping()) looping = textLoop;
1315
+ else if (reasoningLoop.isLooping()) looping = reasoningLoop;
1316
+ return {
1317
+ ...rest,
1318
+ text,
1319
+ ...reasoningText ? { reasoningText } : {},
1320
+ ...looping ? { finishReason: "looping", loopingInfo: looping.getInfo() ?? "" } : {}
1321
+ };
1322
+ }
1323
+ function linkAbort(outer, ac) {
1324
+ if (!outer) return () => {
1325
+ };
1326
+ if (outer.aborted) {
1327
+ ac.abort(outer.reason);
1328
+ return () => {
1329
+ };
1330
+ }
1331
+ const onAbort = () => ac.abort(outer.reason);
1332
+ outer.addEventListener("abort", onAbort, { once: true });
1333
+ return () => outer.removeEventListener("abort", onAbort);
1334
+ }
1335
+ function filterWhole(guard, text) {
1336
+ if (!guard || !text) return text;
1337
+ return guard.push(text) + guard.flush();
1338
+ }
1339
+ // src/delegate/manager.ts
1340
+ var ABORTED = "\u5DF2\u4E2D\u6B62\uFF1A\u7528\u6237\u7EC8\u6B62\u4E86\u8FD9\u4E00\u8F6E";
1109
1341
  var BLOCKED_TOOLS = Object.freeze([
1110
1342
  "delegate_task",
1111
1343
  "clarify",
@@ -1135,7 +1367,7 @@ var DelegateManager = class {
1135
1367
  ...config
1136
1368
  };
1137
1369
  }
1138
- async delegate(args, parentSessionId) {
1370
+ async delegate(args, parentSessionId, signal) {
1139
1371
  const start = Date.now();
1140
1372
  const tasks = this.normalizeTasks(args);
1141
1373
  const maxConcurrent = args.max_concurrent ?? this.config.max_concurrent_children;
@@ -1146,7 +1378,7 @@ var DelegateManager = class {
1146
1378
  duration: 0
1147
1379
  };
1148
1380
  }
1149
- const results = await this.runWithConcurrency(tasks, maxConcurrent, parentSessionId);
1381
+ const results = await this.runWithConcurrency(tasks, maxConcurrent, parentSessionId, signal);
1150
1382
  const summary = this.buildSummary(results, Date.now() - start);
1151
1383
  return {
1152
1384
  success: results.every((r) => r.success),
@@ -1161,12 +1393,12 @@ var DelegateManager = class {
1161
1393
  if (args.goal) return [{ goal: args.goal, ...args.role ? { role: args.role } : {} }];
1162
1394
  return [];
1163
1395
  }
1164
- async runWithConcurrency(tasks, maxConcurrent, parentSessionId) {
1396
+ async runWithConcurrency(tasks, maxConcurrent, parentSessionId, signal) {
1165
1397
  const results = [];
1166
1398
  for (let i = 0; i < tasks.length; i += maxConcurrent) {
1167
1399
  const batch = tasks.slice(i, i + maxConcurrent);
1168
1400
  const batchResults = await Promise.allSettled(
1169
- batch.map((t81) => this.runSingleTask(t81, parentSessionId))
1401
+ batch.map((t81) => this.runSingleTask(t81, parentSessionId, signal))
1170
1402
  );
1171
1403
  for (const r of batchResults) {
1172
1404
  if (r.status === "fulfilled") {
@@ -1184,14 +1416,40 @@ var DelegateManager = class {
1184
1416
  }
1185
1417
  return results;
1186
1418
  }
1187
- async runSingleTask(task, parentSessionId) {
1419
+ async runSingleTask(task, parentSessionId, signal) {
1188
1420
  const start = Date.now();
1189
1421
  const sessionId = `delegate-${randomUUID().slice(0, 8)}`;
1422
+ const failed = (error, summary = "") => ({
1423
+ goal: task.goal,
1424
+ success: false,
1425
+ summary,
1426
+ error,
1427
+ duration: Date.now() - start,
1428
+ ...task.role ? { role: task.role } : {}
1429
+ });
1430
+ if (signal?.aborted) return failed(ABORTED);
1431
+ const controller = new AbortController();
1432
+ const unlink = linkAbort(isAbortSignal(signal) ? signal : void 0, controller);
1433
+ const ms = this.config.task_timeout;
1434
+ let timer;
1190
1435
  try {
1191
- const result = await this.runWithTimeout(
1192
- this.runner(task.goal, task.context ?? "", sessionId, task.role, parentSessionId),
1193
- this.config.task_timeout
1194
- );
1436
+ const result = await Promise.race([
1437
+ this.runner(
1438
+ task.goal,
1439
+ task.context ?? "",
1440
+ sessionId,
1441
+ task.role,
1442
+ parentSessionId,
1443
+ controller.signal
1444
+ ),
1445
+ new Promise((_, reject2) => {
1446
+ timer = setTimeout(() => {
1447
+ reject2(new Error(`\u8D85\u65F6 (${ms / 1e3}s)`));
1448
+ controller.abort();
1449
+ }, ms);
1450
+ })
1451
+ ]);
1452
+ if (controller.signal.aborted) return failed(ABORTED, result.text);
1195
1453
  return {
1196
1454
  goal: task.goal,
1197
1455
  success: !result.error,
@@ -1202,22 +1460,13 @@ var DelegateManager = class {
1202
1460
  };
1203
1461
  } catch (err) {
1204
1462
  const msg = err instanceof Error ? err.message : String(err);
1205
- return {
1206
- goal: task.goal,
1207
- success: false,
1208
- summary: "",
1209
- error: msg.includes("\u8D85\u65F6") ? `[\u8D85\u65F6] ${msg}` : msg,
1210
- duration: Date.now() - start,
1211
- ...task.role ? { role: task.role } : {}
1212
- };
1463
+ if (signal?.aborted) return failed(ABORTED);
1464
+ return failed(msg.includes("\u8D85\u65F6") ? `[\u8D85\u65F6] ${msg}` : msg);
1465
+ } finally {
1466
+ clearTimeout(timer);
1467
+ unlink();
1213
1468
  }
1214
1469
  }
1215
- async runWithTimeout(promise, ms) {
1216
- const timeout = new Promise(
1217
- (_, reject2) => setTimeout(() => reject2(new Error(`\u8D85\u65F6 (${ms / 1e3}s)`)), ms)
1218
- );
1219
- return Promise.race([promise, timeout]);
1220
- }
1221
1470
  buildSummary(results, totalDuration) {
1222
1471
  const lines = [];
1223
1472
  const succeeded = results.filter((r) => r.success).length;
@@ -1361,12 +1610,13 @@ function createDelegateTool(runner, config, roles) {
1361
1610
  ...topRole ? { role: topRole } : {},
1362
1611
  ...maxConcurrent ? { max_concurrent: maxConcurrent } : {}
1363
1612
  },
1364
- ctx.sessionId
1613
+ ctx.sessionId,
1614
+ ctx.signal
1365
1615
  );
1366
1616
  return {
1367
1617
  success: result.success,
1368
1618
  output: result.output,
1369
- ...result.success ? {} : { error: { code: 7002, message: "\u90E8\u5206\u5B50\u4EFB\u52A1\u5931\u8D25\uFF0C\u8BE6\u60C5\u89C1\u8F93\u51FA" } },
1619
+ ...result.success ? {} : { error: { code: 7002, message: result.output } },
1370
1620
  duration: result.duration
1371
1621
  };
1372
1622
  }
@@ -10430,6 +10680,17 @@ function blankResponseNote(chars) {
10430
10680
  "\u4E0A\u4E00\u8F6E\u7684\u5DE5\u5177\u7ED3\u679C\u90FD\u8FD8\u5728\u4E0A\u9762\u7684\u4E0A\u4E0B\u6587\u91CC\uFF0C\u76F4\u63A5\u636E\u6B64\u4F5C\u7B54\uFF0C\u522B\u628A\u90A3\u4E9B\u5DE5\u5177\u91CD\u8DD1\u4E00\u904D\u3002"
10431
10681
  ].join("\n");
10432
10682
  }
10683
+ var STALL_NOTE_ARGS_MAX_CHARS = 200;
10684
+ function stallNote(detail) {
10685
+ const args = detail.args.length > STALL_NOTE_ARGS_MAX_CHARS ? `${detail.args.slice(0, STALL_NOTE_ARGS_MAX_CHARS)}\u2026` : detail.args;
10686
+ const fact = detail.kind === "failure" ? `\u4F60\u5DF2\u7ECF\u7528\u540C\u4E00\u4EFD\u53C2\u6570\u8C03\u4E86 ${detail.count} \u6B21 ${detail.tool}\uFF08${args}\uFF09\uFF0C\u6BCF\u6B21\u90FD\u5931\u8D25\uFF0C\u800C\u4E14\u662F\u540C\u4E00\u6761\u9519\u8BEF\uFF1A${detail.error ?? ""}` : `\u4F60\u5DF2\u7ECF\u8FDE\u7EED ${detail.count} \u8F6E\u7528\u540C\u4E00\u4EFD\u53C2\u6570\u8C03 ${detail.tool}\uFF08${args}\uFF09\uFF0C\u7ED3\u679C\u4E0D\u4F1A\u56E0\u4E3A\u518D\u8C03\u4E00\u6B21\u800C\u6539\u53D8\u3002`;
10687
+ return [
10688
+ `${fact} \u2014\u2014 \u8FD9\u662F\u5728\u539F\u5730\u6253\u8F6C\u3002`,
10689
+ detail.kind === "failure" ? "\u522B\u518D\u539F\u6837\u91CD\u8BD5\u3002\u5148\u8BFB\u61C2\u8FD9\u6761\u9519\u8BEF\u8BF4\u7684\u662F\u4EC0\u4E48\uFF0C\u7136\u540E\u6362\u4E00\u79CD\u505A\u6CD5\uFF1A\u6362\u53C2\u6570\u3001\u6362\u4E00\u4E2A\u5DE5\u5177\uFF0C\u6216\u8005\u7ED5\u5F00\u8FD9\u4E00\u6B65\u7528\u5DF2\u7ECF\u62FF\u5230\u7684\u7ED3\u679C\u5F80\u4E0B\u8D70\u3002" : "\u522B\u518D\u539F\u6837\u8C03\u4E00\u6B21\u3002\u5B83\u7684\u7ED3\u679C\u5DF2\u7ECF\u5728\u4E0A\u9762\u7684\u4E0A\u4E0B\u6587\u91CC\u4E86\uFF0C\u76F4\u63A5\u7528\uFF1B\u8981\u4E48\u6362\u53C2\u6570\u3001\u6362\u4E00\u4E2A\u5DE5\u5177\uFF0C\u6362\u4E00\u79CD\u505A\u6CD5\u5F80\u4E0B\u8D70\u3002",
10690
+ "\u5982\u679C\u786E\u5B9E\u6CA1\u6709\u522B\u7684\u8DEF\uFF08\u5DE5\u5177\u672C\u8EAB\u574F\u4E86\u3001\u7F3A\u6743\u9650\u6216\u51ED\u636E\u3001\u9700\u8981\u7528\u6237\u5148\u505A\u70B9\u4EC0\u4E48\uFF09\uFF0C\u5C31\u505C\u4E0B\u6765\uFF0C\u76F4\u63A5\u544A\u8BC9\u7528\u6237\u5361\u5728\u54EA\u4E00\u6B65\u3001\u9700\u8981\u4ED6\u505A\u4EC0\u4E48\u3002",
10691
+ "\u8FD9\u662F\u552F\u4E00\u4E00\u6B21\u63D0\u9192\uFF1A\u518D\u9677\u8FDB\u540C\u6837\u7684\u91CD\u590D\uFF0C\u4EFB\u52A1\u4F1A\u88AB\u76F4\u63A5\u7EC8\u6B62\u3002"
10692
+ ].join("\n");
10693
+ }
10433
10694
  var CODE_VERIFICATION_POLICY = [
10434
10695
  "<code_verification>",
10435
10696
  "\u6539\u5B8C\u4EE3\u7801\u3001\u5BA3\u5E03\u5B8C\u6210\u4E4B\u524D\uFF0C\u8C03\u4E00\u6B21 `lsp_diagnostics`\uFF0C\u628A\u672C\u8F6E\u65B0\u5EFA\u6216\u4FEE\u6539\u8FC7\u7684\u6E90\u6587\u4EF6\u5217\u8FDB\u53BB\u3002",
@@ -10615,6 +10876,28 @@ var StallDetector = class {
10615
10876
  }
10616
10877
  return null;
10617
10878
  }
10879
+ getStallDetail() {
10880
+ const failure = this.stalledFailure();
10881
+ if (failure) {
10882
+ return {
10883
+ kind: "failure",
10884
+ tool: failure.toolName,
10885
+ args: failure.args,
10886
+ count: failure.count,
10887
+ error: failure.error
10888
+ };
10889
+ }
10890
+ const streak = this.stalledStreak();
10891
+ if (streak) {
10892
+ return {
10893
+ kind: "streak",
10894
+ tool: streak.toolName,
10895
+ args: streak.args,
10896
+ count: streak.consecutive
10897
+ };
10898
+ }
10899
+ return null;
10900
+ }
10618
10901
  reset() {
10619
10902
  this.history = [];
10620
10903
  this.failures.clear();
@@ -10933,7 +11216,7 @@ var ToolExecutor = class {
10933
11216
  output: `\u64CD\u4F5C\u88AB hook \u963B\u6B62: ${blocked.reason || "\u672A\u77E5\u539F\u56E0"}`
10934
11217
  });
10935
11218
  }
10936
- const gate = await this.checkPermission(et, tc.input);
11219
+ const gate = await this.checkPermission(et, tc.input, signal);
10937
11220
  if (!gate.allowed) {
10938
11221
  return finish({ ...base, success: false, output: gate.output });
10939
11222
  }
@@ -11076,7 +11359,7 @@ var ToolExecutor = class {
11076
11359
  );
11077
11360
  return guessed || argsKey(args);
11078
11361
  }
11079
- async checkPermission(et, args) {
11362
+ async checkPermission(et, args, signal) {
11080
11363
  const pm = this.deps.permissionManager;
11081
11364
  if (!pm) return { allowed: true };
11082
11365
  const toolName = et.name;
@@ -11110,6 +11393,9 @@ var ToolExecutor = class {
11110
11393
  ...decision.rememberable === false ? { rememberable: false } : {}
11111
11394
  })
11112
11395
  );
11396
+ if (signal?.aborted) {
11397
+ return { allowed: false, output: "\u64CD\u4F5C\u5DF2\u4E2D\u6B62\uFF1A\u7528\u6237\u7EC8\u6B62\u4E86\u8FD9\u4E00\u8F6E\u3002" };
11398
+ }
11113
11399
  this.deps.telemetry?.counter(METRIC.TOOL_APPROVAL, 1, {
11114
11400
  [GEN_AI.TOOL_NAME]: toolName,
11115
11401
  "epoch.approval.outcome": outcome
@@ -11131,220 +11417,6 @@ var ToolExecutor = class {
11131
11417
  return { allowed: true };
11132
11418
  }
11133
11419
  };
11134
- var WINDOW_CHARS = 1024;
11135
- var NGRAM = 16;
11136
- var MAX_DISTINCT_RATIO = 0.2;
11137
- var CHECK_EVERY_CHARS = 256;
11138
- var SUSTAIN_CHECKS = 2;
11139
- var MIN_NORMALIZED_CHARS = 256;
11140
- function normalizeRuns(text) {
11141
- return text.replace(/\s+/g, " ").replace(/(.)\1{3,}/gu, (_m, c) => c.repeat(3));
11142
- }
11143
- function distinctNgramRatio(text, n = NGRAM) {
11144
- if (text.length < n) return 1;
11145
- const seen = /* @__PURE__ */ new Set();
11146
- let total = 0;
11147
- for (let i = 0; i + n <= text.length; i++) {
11148
- seen.add(text.slice(i, i + n));
11149
- total++;
11150
- }
11151
- return total === 0 ? 1 : seen.size / total;
11152
- }
11153
- var RepetitionDetector = class {
11154
- buf = "";
11155
- sinceCheck = 0;
11156
- streak = 0;
11157
- chars = 0;
11158
- hit = null;
11159
- push(delta) {
11160
- if (this.hit) return true;
11161
- if (!delta) return false;
11162
- this.chars += delta.length;
11163
- this.buf = (this.buf + delta).slice(-WINDOW_CHARS);
11164
- this.sinceCheck += delta.length;
11165
- if (this.sinceCheck < CHECK_EVERY_CHARS) return false;
11166
- this.sinceCheck = 0;
11167
- if (this.buf.length < WINDOW_CHARS) return false;
11168
- const norm = normalizeRuns(this.buf);
11169
- if (norm.length < MIN_NORMALIZED_CHARS) {
11170
- this.streak = 0;
11171
- return false;
11172
- }
11173
- const ratio = distinctNgramRatio(norm);
11174
- if (ratio > MAX_DISTINCT_RATIO) {
11175
- this.streak = 0;
11176
- return false;
11177
- }
11178
- if (++this.streak < SUSTAIN_CHECKS) return false;
11179
- this.hit = { chars: this.chars, distinctRatio: ratio };
11180
- return true;
11181
- }
11182
- pushAll(text) {
11183
- for (let i = 0; i < text.length; i += CHECK_EVERY_CHARS) {
11184
- if (this.push(text.slice(i, i + CHECK_EVERY_CHARS))) return true;
11185
- }
11186
- return false;
11187
- }
11188
- isLooping() {
11189
- return this.hit !== null;
11190
- }
11191
- getInfo() {
11192
- if (!this.hit) return null;
11193
- return t("agent_run.looping_info", {
11194
- chars: this.hit.chars,
11195
- percent: Math.round(this.hit.distinctRatio * 100)
11196
- });
11197
- }
11198
- };
11199
- // src/agent/turn.ts
11200
- async function* runTurn(opts) {
11201
- if (opts.streaming) {
11202
- return yield* runTurnStreaming(opts);
11203
- }
11204
- return yield* runTurnBuffered(opts);
11205
- }
11206
- async function* runTurnStreaming(opts) {
11207
- const { provider, input, guard, reasoningGuard } = opts;
11208
- const repetition = new RepetitionDetector();
11209
- const reasoningRepetition = new RepetitionDetector();
11210
- let text = "";
11211
- let reasoningText = "";
11212
- let finishReason = "stop";
11213
- let looping;
11214
- let usage;
11215
- const toolCalls = [];
11216
- let streamError;
11217
- let model;
11218
- let requestedModel;
11219
- let warnings;
11220
- const ac = new AbortController();
11221
- const unlink = linkAbort(input.signal, ac);
11222
- try {
11223
- for await (const chunk of provider.stream({ ...input, signal: ac.signal })) {
11224
- switch (chunk.type) {
11225
- case "text-delta": {
11226
- const raw = chunk.textDelta ?? "";
11227
- if (!raw) break;
11228
- const delta = guard ? guard.push(raw) : raw;
11229
- if (!delta) break;
11230
- text += delta;
11231
- yield { type: "text-delta", text: delta };
11232
- if (repetition.push(delta)) looping = repetition;
11233
- break;
11234
- }
11235
- case "reasoning-delta": {
11236
- const raw = chunk.textDelta ?? "";
11237
- if (!raw) break;
11238
- const delta = reasoningGuard ? reasoningGuard.push(raw) : raw;
11239
- if (!delta) break;
11240
- reasoningText += delta;
11241
- yield { type: "reasoning-delta", text: delta };
11242
- if (reasoningRepetition.push(delta)) looping = reasoningRepetition;
11243
- break;
11244
- }
11245
- case "tool-call":
11246
- if (chunk.toolCall) {
11247
- toolCalls.push({
11248
- toolCallId: chunk.toolCall.toolCallId,
11249
- toolName: chunk.toolCall.toolName,
11250
- input: chunk.toolCall.input
11251
- });
11252
- }
11253
- break;
11254
- case "finish":
11255
- if (chunk.usage) usage = chunk.usage;
11256
- if (chunk.finishReason) finishReason = chunk.finishReason;
11257
- if (chunk.model) model = chunk.model;
11258
- if (chunk.requestedModel) requestedModel = chunk.requestedModel;
11259
- if (chunk.warnings) warnings = chunk.warnings;
11260
- break;
11261
- case "error":
11262
- streamError = chunk.error ?? t("agent_run.unknown_provider_error");
11263
- break;
11264
- case "tool-result":
11265
- break;
11266
- }
11267
- if (looping) {
11268
- ac.abort();
11269
- break;
11270
- }
11271
- }
11272
- } finally {
11273
- unlink();
11274
- }
11275
- if (streamError && !looping) throw new Error(streamError);
11276
- if (guard) {
11277
- const tail = guard.flush();
11278
- if (tail) {
11279
- text += tail;
11280
- yield { type: "text-delta", text: tail };
11281
- }
11282
- }
11283
- if (reasoningGuard) {
11284
- const tail = reasoningGuard.flush();
11285
- if (tail) {
11286
- reasoningText += tail;
11287
- yield { type: "reasoning-delta", text: tail };
11288
- }
11289
- }
11290
- return {
11291
- text,
11292
- ...usage ? { usage } : {},
11293
- finishReason: looping ? "looping" : finishReason,
11294
- ...looping ? { loopingInfo: looping.getInfo() ?? "" } : {},
11295
- ...toolCalls.length > 0 ? { toolCalls } : {},
11296
- ...reasoningText ? { reasoningText } : {},
11297
- ...model ? { model } : {},
11298
- ...requestedModel ? { requestedModel } : {},
11299
- ...warnings && warnings.length > 0 ? { warnings } : {}
11300
- };
11301
- }
11302
- async function* runTurnBuffered(opts) {
11303
- const { provider, input, guard, reasoningGuard } = opts;
11304
- const result = await provider.generate(input);
11305
- const reasoningText = result.reasoningText ? filterWhole(reasoningGuard, result.reasoningText) : result.reasoningText;
11306
- if (reasoningText) {
11307
- yield { type: "reasoning-delta", text: reasoningText };
11308
- }
11309
- const text = filterWhole(guard, result.text);
11310
- if (text) {
11311
- yield { type: "text-delta", text };
11312
- }
11313
- const { reasoningText: _replaced, ...rest } = result;
11314
- const textLoop = new RepetitionDetector();
11315
- const reasoningLoop = new RepetitionDetector();
11316
- textLoop.pushAll(text);
11317
- if (reasoningText) reasoningLoop.pushAll(reasoningText);
11318
- let looping;
11319
- if (textLoop.isLooping()) looping = textLoop;
11320
- else if (reasoningLoop.isLooping()) looping = reasoningLoop;
11321
- return {
11322
- ...rest,
11323
- text,
11324
- ...reasoningText ? { reasoningText } : {},
11325
- ...looping ? { finishReason: "looping", loopingInfo: looping.getInfo() ?? "" } : {}
11326
- };
11327
- }
11328
- function linkAbort(outer, ac) {
11329
- if (!outer) return () => {
11330
- };
11331
- if (outer.aborted) {
11332
- ac.abort(outer.reason);
11333
- return () => {
11334
- };
11335
- }
11336
- const onAbort = () => ac.abort(outer.reason);
11337
- outer.addEventListener("abort", onAbort, { once: true });
11338
- return () => outer.removeEventListener("abort", onAbort);
11339
- }
11340
- function filterWhole(guard, text) {
11341
- if (!guard || !text) return text;
11342
- return guard.push(text) + guard.flush();
11343
- }
11344
- // src/agent/types.ts
11345
- function isAbortSignal(signal) {
11346
- return !!signal && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
11347
- }
11348
11420
  // src/agent/loop.ts
11349
11421
  var DEFAULT_CONTEXT_LENGTH = 128e3;
11350
11422
  function shrank(before, after) {
@@ -11671,6 +11743,7 @@ var AgentLoop = class {
11671
11743
  let idleNudged = false;
11672
11744
  let blankNudged = false;
11673
11745
  let blankAfterNudge = false;
11746
+ let stallNudged = false;
11674
11747
  for (let turn = 1; turn <= maxTurns; turn++) {
11675
11748
  if (this.isAborted(input.signal)) {
11676
11749
  if (turns.length > 0 || lastText) {
@@ -12069,7 +12142,17 @@ ${finalText}` };
12069
12142
  turn
12070
12143
  });
12071
12144
  yield { type: "turn-complete", turn };
12072
- if (this.stallDetector.isStalled()) {
12145
+ const stall = this.stallDetector.getStallDetail();
12146
+ if (stall && !stallNudged && turn < maxTurns) {
12147
+ stallNudged = true;
12148
+ yield {
12149
+ type: "notice",
12150
+ code: "stall-nudged",
12151
+ text: t("agent_run.stall_nudged", { info: this.stallDetector.getStallInfo() ?? "" })
12152
+ };
12153
+ this.stallDetector.reset();
12154
+ messages.push({ role: "user", content: systemNote(stallNote(stall)) });
12155
+ } else if (stall) {
12073
12156
  const msg2 = t("agent_run.stalled", { info: this.stallDetector.getStallInfo() ?? "" });
12074
12157
  yield { type: "text-delta", text: msg2 };
12075
12158
  await this.prompt.syncMemory(userText, msg2);
@@ -12421,8 +12504,48 @@ var EpochConfigSchema = z.object({
12421
12504
  message: "blankCards: id must be unique"
12422
12505
  }).optional(),
12423
12506
  blankGreeting: BlankGreetingSchema.optional(),
12424
- user: HostUserSchema.optional()
12507
+ user: HostUserSchema.optional(),
12508
+ settingsHidden: z.array(z.string().min(1)).min(1).optional(),
12509
+ settingsPanes: z.array(
12510
+ z.object({
12511
+ id: z.string().min(1).refine((id) => !isSettingsPaneId(id), {
12512
+ message: `id must not collide with a built-in pane (${SETTINGS_PANE_IDS.join(", ")})`
12513
+ }),
12514
+ label: z.string().min(1),
12515
+ icon: z.string().min(1).optional(),
12516
+ url: z.string().min(1).refine(
12517
+ (url) => {
12518
+ try {
12519
+ const scheme = new URL(url).protocol;
12520
+ return scheme === "http:" || scheme === "https:";
12521
+ } catch {
12522
+ return false;
12523
+ }
12524
+ },
12525
+ { message: "url must be an absolute http(s) URL" }
12526
+ ),
12527
+ before: z.string().min(1).optional()
12528
+ })
12529
+ ).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
12530
+ message: "settingsPanes: id must be unique"
12531
+ }).optional()
12425
12532
  });
12533
+ function settingsHiddenIssues(config) {
12534
+ const hidden = config.settingsHidden;
12535
+ if (hidden === void 0) return [];
12536
+ return hidden.filter((id) => !isSettingsPaneId(id)).map((id) => ({
12537
+ path: "settingsHidden",
12538
+ message: `unknown settings pane id: ${id} (known: ${SETTINGS_PANE_IDS.join(", ")})`
12539
+ }));
12540
+ }
12541
+ function settingsPaneBeforeIssues(config) {
12542
+ const panes = config.settingsPanes;
12543
+ if (panes === void 0) return [];
12544
+ return panes.filter((pane) => pane.before !== void 0 && !isSettingsPaneId(pane.before)).map((pane) => ({
12545
+ path: `settingsPanes.${pane.id}.before`,
12546
+ message: `unknown settings pane id: ${String(pane.before)} (known: ${SETTINGS_PANE_IDS.join(", ")})`
12547
+ }));
12548
+ }
12426
12549
  function defaultConfig() {
12427
12550
  return EpochConfigSchema.parse({});
12428
12551
  }
@@ -12893,6 +13016,8 @@ function parseRawConfig(raw, issues, track) {
12893
13016
  EpochConfigSchema.parse({})
12894
13017
  );
12895
13018
  issues.push(...outcome.issues);
13019
+ issues.push(...settingsHiddenIssues(outcome.value));
13020
+ issues.push(...settingsPaneBeforeIssues(outcome.value));
12896
13021
  if (outcome.issues.length === 0) collectKeyPaths(migrated.raw, track.file);
12897
13022
  return outcome.value;
12898
13023
  }
@@ -19524,6 +19649,9 @@ var ScheduleRegistrar = class {
19524
19649
  this.opts = opts;
19525
19650
  }
19526
19651
  opts;
19652
+ get homeDir() {
19653
+ return this.opts.homeDir;
19654
+ }
19527
19655
  osTaskIdFor(taskId) {
19528
19656
  const backend = this.opts.backend;
19529
19657
  if (!backend) return "";
@@ -19594,7 +19722,6 @@ var ScheduleRegistrar = class {
19594
19722
  if (this.opts.backend && def.osTaskId) await this.opts.backend.retire(def.osTaskId);
19595
19723
  }
19596
19724
  };
19597
- // src/schedule/doctor.ts
19598
19725
  async function diagnoseSchedules(opts) {
19599
19726
  const { store, backend, registrar } = opts;
19600
19727
  if (!backend) return { checked: 0, drifts: [], unsupported: true };
@@ -19631,8 +19758,18 @@ async function diagnoseSchedules(opts) {
19631
19758
  });
19632
19759
  }
19633
19760
  }
19761
+ let foreign = 0;
19634
19762
  for (const osTaskId of osTaskIds) {
19635
19763
  if (seen.has(osTaskId)) continue;
19764
+ const snapshot = await backend.inspect(osTaskId);
19765
+ if (!snapshot) {
19766
+ foreign++;
19767
+ continue;
19768
+ }
19769
+ if (!ownedByThisHome(snapshot.argv, registrar.homeDir)) {
19770
+ foreign++;
19771
+ continue;
19772
+ }
19636
19773
  const taskId = backend.taskIdFrom(osTaskId);
19637
19774
  drifts.push({
19638
19775
  kind: "orphan-in-os",
@@ -19640,7 +19777,17 @@ async function diagnoseSchedules(opts) {
19640
19777
  ...taskId ? { scheduleId: taskId } : {}
19641
19778
  });
19642
19779
  }
19643
- return { checked: defs.length + osTaskIds.length - seen.size, drifts, unsupported: false };
19780
+ return {
19781
+ checked: defs.length + osTaskIds.length - seen.size - foreign,
19782
+ drifts,
19783
+ unsupported: false
19784
+ };
19785
+ }
19786
+ function ownedByThisHome(argv, homeDir) {
19787
+ const at = argv.indexOf("--home");
19788
+ const home = at >= 0 ? argv[at + 1] : void 0;
19789
+ if (home === void 0 || home.length === 0) return true;
19790
+ return samePath(home, homeDir);
19644
19791
  }
19645
19792
  function compare2(def, snapshot, backend, registrar) {
19646
19793
  const expected = backend.expectedSnapshot(registrar.toRegisterInput(def, def.osCommand));