@zhushanwen/subagent-core 0.7.0 → 0.8.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/{chunk-VURUAGMM.js → chunk-OL5BK4VN.js} +237 -40
- package/dist/{engine-discovery-scan-ocpM8FmI.d.cts → engine-discovery-scan-BXLA8y-z.d.cts} +17 -1
- package/dist/{engine-discovery-scan-ocpM8FmI.d.ts → engine-discovery-scan-BXLA8y-z.d.ts} +17 -1
- package/dist/execution/engine/engine-discovery-scan.cjs +150 -40
- package/dist/execution/engine/engine-discovery-scan.d.cts +1 -1
- package/dist/execution/engine/engine-discovery-scan.d.ts +1 -1
- package/dist/execution/engine/engine-discovery-scan.js +1 -1
- package/dist/index.cjs +278 -225
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +188 -304
- package/dist.bundle/index.cjs +279 -226
- package/package.json +5 -5
- package/src/__tests__/record-store-last-line.test.ts +12 -0
- package/src/core/logger.ts +1 -1
- package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
- package/src/execution/__tests__/nested-visibility.test.ts +13 -1
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
- package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
- package/src/execution/engine/client/engine-client.ts +30 -1
- package/src/execution/engine/host/host-bridge.ts +7 -0
- package/src/execution/engine/host/spawned-children.ts +1 -1
- package/src/execution/engine/inflight-snapshot.ts +92 -0
- package/src/execution/engine/port.ts +18 -0
- package/src/execution/path-encoding.ts +6 -0
- package/src/execution/subagent-service.ts +25 -0
- package/src/execution/worktree-registry.ts +11 -7
- package/src/index.ts +11 -1
package/dist/index.cjs
CHANGED
|
@@ -125,6 +125,7 @@ __export(src_exports, {
|
|
|
125
125
|
getCachedFileContent: () => getCachedFileContent,
|
|
126
126
|
getCachedParsed: () => getCachedParsed,
|
|
127
127
|
getHostServices: () => getHostServices,
|
|
128
|
+
getInFlightSnapshot: () => getInFlightSnapshot,
|
|
128
129
|
getLogger: () => getLogger,
|
|
129
130
|
getModelConfigService: () => getModelConfigService,
|
|
130
131
|
getOrCreateChannelRegistry: () => getOrCreateChannelRegistry,
|
|
@@ -182,6 +183,7 @@ __export(src_exports, {
|
|
|
182
183
|
saveWorkflow: () => saveWorkflow,
|
|
183
184
|
scheduleTimeBudget: () => scheduleTimeBudget,
|
|
184
185
|
setEngineDiscoveryRescanOptions: () => setEngineDiscoveryRescanOptions,
|
|
186
|
+
setInFlightListener: () => setInFlightListener,
|
|
185
187
|
setModelConfigService: () => setModelConfigService,
|
|
186
188
|
setSubagentService: () => setSubagentService,
|
|
187
189
|
sortByCodepoint: () => sortByCodepoint,
|
|
@@ -676,16 +678,16 @@ var SpawnedChildrenMirror = class {
|
|
|
676
678
|
return this.entries.size;
|
|
677
679
|
}
|
|
678
680
|
/** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
|
|
679
|
-
onChange(
|
|
680
|
-
this.listeners.add(
|
|
681
|
+
onChange(listener2) {
|
|
682
|
+
this.listeners.add(listener2);
|
|
681
683
|
return () => {
|
|
682
|
-
this.listeners.delete(
|
|
684
|
+
this.listeners.delete(listener2);
|
|
683
685
|
};
|
|
684
686
|
}
|
|
685
687
|
emit(event) {
|
|
686
|
-
for (const
|
|
688
|
+
for (const listener2 of this.listeners) {
|
|
687
689
|
try {
|
|
688
|
-
|
|
690
|
+
listener2(event);
|
|
689
691
|
} catch (err) {
|
|
690
692
|
logger4.debug(
|
|
691
693
|
`[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1134,8 +1136,189 @@ function waitForChildExit(child, timeoutMs) {
|
|
|
1134
1136
|
});
|
|
1135
1137
|
}
|
|
1136
1138
|
|
|
1139
|
+
// src/execution/engine/host/spawned-children.ts
|
|
1140
|
+
var CoreSpawnedChildrenMirror = class {
|
|
1141
|
+
entries = /* @__PURE__ */ new Map();
|
|
1142
|
+
/** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
|
|
1143
|
+
register(recordId, child) {
|
|
1144
|
+
this.entries.set(recordId, {
|
|
1145
|
+
pid: child.pid,
|
|
1146
|
+
killed: child.killed,
|
|
1147
|
+
updatedAt: Date.now()
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
|
|
1151
|
+
markKilled(recordId) {
|
|
1152
|
+
const entry = this.entries.get(recordId);
|
|
1153
|
+
if (entry !== void 0) {
|
|
1154
|
+
entry.killed = true;
|
|
1155
|
+
entry.updatedAt = Date.now();
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
/** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
|
|
1159
|
+
killAll() {
|
|
1160
|
+
this.entries.clear();
|
|
1161
|
+
}
|
|
1162
|
+
getChildByRecord(recordId) {
|
|
1163
|
+
return this.entries.get(recordId);
|
|
1164
|
+
}
|
|
1165
|
+
/** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
|
|
1166
|
+
hasLiveProcessHandle(recordId) {
|
|
1167
|
+
const entry = this.entries.get(recordId);
|
|
1168
|
+
return entry !== void 0 && !entry.killed;
|
|
1169
|
+
}
|
|
1170
|
+
/** 快照(诊断/测试)。 */
|
|
1171
|
+
snapshot() {
|
|
1172
|
+
return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
|
|
1173
|
+
}
|
|
1174
|
+
/** 测试隔离专用(生产禁用——进程级全局状态)。 */
|
|
1175
|
+
clear() {
|
|
1176
|
+
this.entries.clear();
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
|
|
1180
|
+
"@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
|
|
1181
|
+
);
|
|
1182
|
+
function coreMirrorSlot() {
|
|
1183
|
+
let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
|
|
1184
|
+
if (!slot) {
|
|
1185
|
+
slot = new CoreSpawnedChildrenMirror();
|
|
1186
|
+
Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
|
|
1187
|
+
}
|
|
1188
|
+
return slot;
|
|
1189
|
+
}
|
|
1190
|
+
function coreSpawnedChildrenMirror() {
|
|
1191
|
+
return coreMirrorSlot();
|
|
1192
|
+
}
|
|
1193
|
+
function registerSpawnedChildForRecord(recordId, child) {
|
|
1194
|
+
coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
|
|
1195
|
+
}
|
|
1196
|
+
function killRecordChildWithEscalation(recordId, _source) {
|
|
1197
|
+
coreMirrorSlot().markKilled(recordId);
|
|
1198
|
+
}
|
|
1199
|
+
function killAllSpawnedChildren(_signal = "SIGTERM") {
|
|
1200
|
+
const before = coreMirrorSlot().snapshot().length;
|
|
1201
|
+
coreMirrorSlot().killAll();
|
|
1202
|
+
return before;
|
|
1203
|
+
}
|
|
1204
|
+
function hasLiveProcessHandleCore(recordId) {
|
|
1205
|
+
return coreMirrorSlot().hasLiveProcessHandle(recordId);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/shared/timer-delay.ts
|
|
1209
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
1210
|
+
function assertSafeTimerDelay(ms, source) {
|
|
1211
|
+
if (!Number.isFinite(ms)) {
|
|
1212
|
+
throw new Error(
|
|
1213
|
+
`[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
if (ms > MAX_TIMER_DELAY_MS) {
|
|
1217
|
+
throw new Error(
|
|
1218
|
+
`[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/execution/lifecycle-manager.ts
|
|
1224
|
+
var logger9 = getLogger("subagents");
|
|
1225
|
+
var MS_PER_SECOND = 1e3;
|
|
1226
|
+
var SECONDS_PER_MINUTE = 60;
|
|
1227
|
+
var IDLE_TIMEOUT_MINUTES = 5;
|
|
1228
|
+
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
1229
|
+
function getEnvIdleTimeoutMs() {
|
|
1230
|
+
const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
|
|
1231
|
+
if (!raw) return void 0;
|
|
1232
|
+
const parsed = Number(raw);
|
|
1233
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1234
|
+
logger9.warn(
|
|
1235
|
+
`[lifecycle-manager] XYZ_SUBAGENT_IDLE_TIMEOUT_MS="${raw}" is invalid (expected a positive millisecond number) \u2014 falling back to DEFAULT_IDLE_TIMEOUT_MS (${DEFAULT_IDLE_TIMEOUT_MS}ms); set a plain ms value (e.g. 1800000) to override`
|
|
1236
|
+
);
|
|
1237
|
+
return void 0;
|
|
1238
|
+
}
|
|
1239
|
+
return parsed;
|
|
1240
|
+
}
|
|
1241
|
+
var idleTimers = /* @__PURE__ */ new Map();
|
|
1242
|
+
function armIdleTimer(recordId, onTimeout, timeoutMs) {
|
|
1243
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
1244
|
+
disarmIdleTimer(recordId);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
1248
|
+
assertSafeTimerDelay(resolved, "idleTimeoutMs");
|
|
1249
|
+
disarmIdleTimer(recordId);
|
|
1250
|
+
const timer = setTimeout(() => {
|
|
1251
|
+
if (idleTimers.get(recordId)?.timer === timer) {
|
|
1252
|
+
idleTimers.delete(recordId);
|
|
1253
|
+
}
|
|
1254
|
+
onTimeout();
|
|
1255
|
+
}, resolved);
|
|
1256
|
+
timer.unref?.();
|
|
1257
|
+
idleTimers.set(recordId, { timer, timeoutMs: resolved });
|
|
1258
|
+
}
|
|
1259
|
+
function disarmIdleTimer(recordId) {
|
|
1260
|
+
const entry = idleTimers.get(recordId);
|
|
1261
|
+
if (!entry) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
clearTimeout(entry.timer);
|
|
1265
|
+
idleTimers.delete(recordId);
|
|
1266
|
+
}
|
|
1267
|
+
function hasIdleTimer(recordId) {
|
|
1268
|
+
return idleTimers.has(recordId);
|
|
1269
|
+
}
|
|
1270
|
+
var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
|
|
1271
|
+
var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND;
|
|
1272
|
+
|
|
1273
|
+
// src/execution/lifecycle-predicates.ts
|
|
1274
|
+
function hasLiveProcessHandle(recordId) {
|
|
1275
|
+
return hasLiveProcessHandleCore(recordId);
|
|
1276
|
+
}
|
|
1277
|
+
function isIdle(record) {
|
|
1278
|
+
return hasIdleTimer(record.id);
|
|
1279
|
+
}
|
|
1280
|
+
function isResumable(record) {
|
|
1281
|
+
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/execution/engine/inflight-snapshot.ts
|
|
1285
|
+
var listener = null;
|
|
1286
|
+
function setInFlightListener(next) {
|
|
1287
|
+
listener = next;
|
|
1288
|
+
}
|
|
1289
|
+
function getInFlightSnapshot() {
|
|
1290
|
+
return { inFlight: countInFlight() };
|
|
1291
|
+
}
|
|
1292
|
+
function notifyInFlightChanged() {
|
|
1293
|
+
if (listener === null) return;
|
|
1294
|
+
try {
|
|
1295
|
+
listener(getInFlightSnapshot());
|
|
1296
|
+
} catch {
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function countInFlight() {
|
|
1300
|
+
let count = 0;
|
|
1301
|
+
for (const entry of coreSpawnedChildrenMirror().snapshot()) {
|
|
1302
|
+
const recordId = entry.recordId;
|
|
1303
|
+
if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
|
|
1304
|
+
}
|
|
1305
|
+
return count;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1137
1308
|
// src/execution/engine/client/engine-client.ts
|
|
1138
|
-
var
|
|
1309
|
+
var logger10 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
|
|
1310
|
+
function bridgeMirrorEventToCoreMirror(event, mirror) {
|
|
1311
|
+
if (event.recordId === void 0 || event.pid === void 0) return;
|
|
1312
|
+
const entry = mirror.getEntry(event.pid);
|
|
1313
|
+
if (entry === void 0) return;
|
|
1314
|
+
const core = coreSpawnedChildrenMirror();
|
|
1315
|
+
if (entry.killed) {
|
|
1316
|
+
core.markKilled(event.recordId);
|
|
1317
|
+
} else {
|
|
1318
|
+
core.register(event.recordId, { pid: entry.pid, killed: false });
|
|
1319
|
+
}
|
|
1320
|
+
notifyInFlightChanged();
|
|
1321
|
+
}
|
|
1139
1322
|
var DISPOSE_GRACE_MS = 3e3;
|
|
1140
1323
|
var SIGKILL_REAP_TIMEOUT_MS = 1e4;
|
|
1141
1324
|
var FRAME_ECHO_MAX_CHARS = 200;
|
|
@@ -1198,6 +1381,7 @@ var EngineClient = class {
|
|
|
1198
1381
|
};
|
|
1199
1382
|
this.mirror.onChange((event) => {
|
|
1200
1383
|
opts.onMirrorChanged?.(event);
|
|
1384
|
+
bridgeMirrorEventToCoreMirror(event, this.mirror);
|
|
1201
1385
|
});
|
|
1202
1386
|
}
|
|
1203
1387
|
get currentState() {
|
|
@@ -1260,19 +1444,19 @@ var EngineClient = class {
|
|
|
1260
1444
|
matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
|
|
1261
1445
|
});
|
|
1262
1446
|
if (sweep.killed.length > 0) {
|
|
1263
|
-
|
|
1447
|
+
logger10.warn(
|
|
1264
1448
|
`[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
|
|
1265
1449
|
);
|
|
1266
1450
|
}
|
|
1267
1451
|
for (const removed of sweep.removed) {
|
|
1268
|
-
|
|
1452
|
+
logger10.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
|
|
1269
1453
|
}
|
|
1270
1454
|
}
|
|
1271
1455
|
let attempt = 0;
|
|
1272
1456
|
while (attempt <= import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS) {
|
|
1273
1457
|
if (attempt > 0) {
|
|
1274
1458
|
const backoff = import_subagent_engine_sdk6.CRASH_REBUILD_BACKOFF_MS[attempt - 1];
|
|
1275
|
-
|
|
1459
|
+
logger10.warn(
|
|
1276
1460
|
`[engine-client:${this.engineId}] rebuild attempt ${attempt}/${import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
|
|
1277
1461
|
);
|
|
1278
1462
|
await delay(backoff);
|
|
@@ -1305,7 +1489,7 @@ var EngineClient = class {
|
|
|
1305
1489
|
markUnavailable(reason) {
|
|
1306
1490
|
this.state = "unavailable";
|
|
1307
1491
|
this.unavailableReason = reason;
|
|
1308
|
-
|
|
1492
|
+
logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
|
|
1309
1493
|
}
|
|
1310
1494
|
/** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
|
|
1311
1495
|
async spawnAndInitialize() {
|
|
@@ -1335,13 +1519,13 @@ var EngineClient = class {
|
|
|
1335
1519
|
});
|
|
1336
1520
|
const child = this.child;
|
|
1337
1521
|
child.on("error", (err) => {
|
|
1338
|
-
|
|
1522
|
+
logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
|
|
1339
1523
|
this.appendStderrTail(`spawn error: ${err.message}
|
|
1340
1524
|
`);
|
|
1341
1525
|
});
|
|
1342
1526
|
for (const pipe of ["stdin", "stdout", "stderr"]) {
|
|
1343
1527
|
child[pipe]?.on("error", (err) => {
|
|
1344
|
-
|
|
1528
|
+
logger10.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
|
|
1345
1529
|
});
|
|
1346
1530
|
}
|
|
1347
1531
|
child.stdout?.setEncoding("utf-8");
|
|
@@ -1410,7 +1594,7 @@ var EngineClient = class {
|
|
|
1410
1594
|
try {
|
|
1411
1595
|
frame = JSON.parse(line);
|
|
1412
1596
|
} catch {
|
|
1413
|
-
|
|
1597
|
+
logger10.warn(
|
|
1414
1598
|
`[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
1415
1599
|
);
|
|
1416
1600
|
return;
|
|
@@ -1427,7 +1611,7 @@ var EngineClient = class {
|
|
|
1427
1611
|
this.onReverseRequest(frame);
|
|
1428
1612
|
return;
|
|
1429
1613
|
}
|
|
1430
|
-
|
|
1614
|
+
logger10.warn(
|
|
1431
1615
|
`[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
1432
1616
|
);
|
|
1433
1617
|
}
|
|
@@ -1551,7 +1735,7 @@ var EngineClient = class {
|
|
|
1551
1735
|
try {
|
|
1552
1736
|
await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
|
|
1553
1737
|
} catch (err) {
|
|
1554
|
-
|
|
1738
|
+
logger10.debug(
|
|
1555
1739
|
`[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
|
|
1556
1740
|
);
|
|
1557
1741
|
}
|
|
@@ -1563,9 +1747,9 @@ var EngineClient = class {
|
|
|
1563
1747
|
onEngineExit(code, signal) {
|
|
1564
1748
|
const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
|
|
1565
1749
|
if (this.intentionalKill) {
|
|
1566
|
-
|
|
1750
|
+
logger10.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
|
|
1567
1751
|
} else {
|
|
1568
|
-
|
|
1752
|
+
logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
|
|
1569
1753
|
}
|
|
1570
1754
|
this.teardownProcess(detail);
|
|
1571
1755
|
this.state = "exited";
|
|
@@ -1614,7 +1798,7 @@ var EngineClient = class {
|
|
|
1614
1798
|
`);
|
|
1615
1799
|
return true;
|
|
1616
1800
|
} catch (err) {
|
|
1617
|
-
|
|
1801
|
+
logger10.debug(
|
|
1618
1802
|
`[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1619
1803
|
);
|
|
1620
1804
|
return false;
|
|
@@ -1976,7 +2160,7 @@ function getHostUiRequestEndpoint() {
|
|
|
1976
2160
|
}
|
|
1977
2161
|
|
|
1978
2162
|
// src/execution/engine/engine-inspect-package.ts
|
|
1979
|
-
var
|
|
2163
|
+
var logger11 = getLogger("subagents");
|
|
1980
2164
|
function readEnginePackageJson(pkgDir) {
|
|
1981
2165
|
let raw;
|
|
1982
2166
|
try {
|
|
@@ -2055,12 +2239,12 @@ function parseOptionalDisplayFields(m, id) {
|
|
|
2055
2239
|
if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
|
|
2056
2240
|
displayName = rawDisplayName;
|
|
2057
2241
|
} else {
|
|
2058
|
-
|
|
2242
|
+
logger11.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
|
|
2059
2243
|
}
|
|
2060
2244
|
}
|
|
2061
2245
|
const rawDescription = m["description"];
|
|
2062
2246
|
if (rawDescription !== void 0 && typeof rawDescription !== "string") {
|
|
2063
|
-
|
|
2247
|
+
logger11.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
|
|
2064
2248
|
}
|
|
2065
2249
|
return displayName;
|
|
2066
2250
|
}
|
|
@@ -2149,7 +2333,7 @@ function errorMessage(err) {
|
|
|
2149
2333
|
// src/execution/engine/engine-discovery-roots.ts
|
|
2150
2334
|
var fs2 = __toESM(require("fs"), 1);
|
|
2151
2335
|
var path3 = __toESM(require("path"), 1);
|
|
2152
|
-
var
|
|
2336
|
+
var logger12 = getLogger("subagents");
|
|
2153
2337
|
var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
|
|
2154
2338
|
function parseEngineRootsEnv(env) {
|
|
2155
2339
|
const raw = env[ENGINE_ROOTS_ENV];
|
|
@@ -2160,7 +2344,7 @@ function parseEngineRootsEnv(env) {
|
|
|
2160
2344
|
const dir = part.trim();
|
|
2161
2345
|
if (dir === "") continue;
|
|
2162
2346
|
if (!path3.isAbsolute(dir)) {
|
|
2163
|
-
|
|
2347
|
+
logger12.warn(
|
|
2164
2348
|
`[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
|
|
2165
2349
|
);
|
|
2166
2350
|
continue;
|
|
@@ -2229,7 +2413,7 @@ function isFile(p) {
|
|
|
2229
2413
|
// src/execution/engine/config.ts
|
|
2230
2414
|
var fs3 = __toESM(require("fs"), 1);
|
|
2231
2415
|
var path4 = __toESM(require("path"), 1);
|
|
2232
|
-
var
|
|
2416
|
+
var logger13 = getLogger("subagents");
|
|
2233
2417
|
function readExplicitEngines(agentDir) {
|
|
2234
2418
|
const configPath = path4.join(agentDir, "subagents", "config.json");
|
|
2235
2419
|
let parsed;
|
|
@@ -2242,7 +2426,7 @@ function readExplicitEngines(agentDir) {
|
|
|
2242
2426
|
const engines = parsed["engines"];
|
|
2243
2427
|
if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
|
|
2244
2428
|
if (engines !== void 0) {
|
|
2245
|
-
|
|
2429
|
+
logger13.warn(
|
|
2246
2430
|
`[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
|
|
2247
2431
|
);
|
|
2248
2432
|
}
|
|
@@ -2270,7 +2454,7 @@ function sanitizeExplicitEntry(id, raw) {
|
|
|
2270
2454
|
}
|
|
2271
2455
|
function requireEntryObject(id, raw) {
|
|
2272
2456
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2273
|
-
|
|
2457
|
+
logger13.warn(
|
|
2274
2458
|
`[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
|
|
2275
2459
|
);
|
|
2276
2460
|
return void 0;
|
|
@@ -2279,7 +2463,7 @@ function requireEntryObject(id, raw) {
|
|
|
2279
2463
|
}
|
|
2280
2464
|
function requireCommand(id, command) {
|
|
2281
2465
|
if (typeof command !== "string" || command.trim() === "") {
|
|
2282
|
-
|
|
2466
|
+
logger13.warn(
|
|
2283
2467
|
`[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
|
|
2284
2468
|
);
|
|
2285
2469
|
return void 0;
|
|
@@ -2288,7 +2472,7 @@ function requireCommand(id, command) {
|
|
|
2288
2472
|
}
|
|
2289
2473
|
function normalizeArgs(id, args) {
|
|
2290
2474
|
if (args !== void 0 && !isStringArray(args)) {
|
|
2291
|
-
|
|
2475
|
+
logger13.warn(
|
|
2292
2476
|
`[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
|
|
2293
2477
|
);
|
|
2294
2478
|
}
|
|
@@ -2296,7 +2480,7 @@ function normalizeArgs(id, args) {
|
|
|
2296
2480
|
}
|
|
2297
2481
|
function normalizeConfig(id, config) {
|
|
2298
2482
|
if (config !== void 0 && !isStringRecord(config)) {
|
|
2299
|
-
|
|
2483
|
+
logger13.warn(
|
|
2300
2484
|
`[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
|
|
2301
2485
|
);
|
|
2302
2486
|
}
|
|
@@ -2304,7 +2488,7 @@ function normalizeConfig(id, config) {
|
|
|
2304
2488
|
}
|
|
2305
2489
|
function normalizeCwd(id, cwd) {
|
|
2306
2490
|
if (cwd !== void 0 && typeof cwd !== "string") {
|
|
2307
|
-
|
|
2491
|
+
logger13.warn(
|
|
2308
2492
|
`[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
|
|
2309
2493
|
);
|
|
2310
2494
|
}
|
|
@@ -2324,14 +2508,14 @@ function jsonKindOf(value) {
|
|
|
2324
2508
|
}
|
|
2325
2509
|
|
|
2326
2510
|
// src/execution/engine/engine-discovery-scan.ts
|
|
2327
|
-
var
|
|
2511
|
+
var logger14 = getLogger("subagents");
|
|
2328
2512
|
function scanEngines(opts) {
|
|
2329
2513
|
const env = opts.env ?? process.env;
|
|
2330
2514
|
const result = { discovered: [], skipped: [], unusable: [] };
|
|
2331
2515
|
const present = (entry) => {
|
|
2332
2516
|
const idx = result.discovered.findIndex((d) => d.id === entry.id);
|
|
2333
2517
|
if (idx >= 0) {
|
|
2334
|
-
|
|
2518
|
+
logger14.debug(
|
|
2335
2519
|
`[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
|
|
2336
2520
|
);
|
|
2337
2521
|
result.discovered[idx] = entry;
|
|
@@ -2343,13 +2527,13 @@ function scanEngines(opts) {
|
|
|
2343
2527
|
const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
|
|
2344
2528
|
if (inspection.status === "skip") {
|
|
2345
2529
|
if (!inspection.reason.includes("not an engine package")) {
|
|
2346
|
-
|
|
2530
|
+
logger14.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
|
|
2347
2531
|
result.skipped.push({ pkgDir, reason: inspection.reason });
|
|
2348
2532
|
}
|
|
2349
2533
|
return;
|
|
2350
2534
|
}
|
|
2351
2535
|
if (inspection.status === "unusable") {
|
|
2352
|
-
|
|
2536
|
+
logger14.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
|
|
2353
2537
|
result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
|
|
2354
2538
|
return;
|
|
2355
2539
|
}
|
|
@@ -2374,7 +2558,7 @@ function scanEngines(opts) {
|
|
|
2374
2558
|
const command = resolveExplicitCommand(entry.command, env);
|
|
2375
2559
|
if (command === void 0) {
|
|
2376
2560
|
const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
|
|
2377
|
-
|
|
2561
|
+
logger14.warn(`[engine-discovery] ${reason} \u2014 not registering`);
|
|
2378
2562
|
result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
|
|
2379
2563
|
continue;
|
|
2380
2564
|
}
|
|
@@ -2391,7 +2575,7 @@ function discoverAndRegisterEngines(opts) {
|
|
|
2391
2575
|
registerEngineDescriptor(entry.id, entry.descriptor);
|
|
2392
2576
|
loadedDiscoveryIdSet.add(entry.id);
|
|
2393
2577
|
if (existed) {
|
|
2394
|
-
|
|
2578
|
+
logger14.debug(
|
|
2395
2579
|
`[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
|
|
2396
2580
|
);
|
|
2397
2581
|
}
|
|
@@ -2421,7 +2605,7 @@ function resolveExplicitCommand(command, env) {
|
|
|
2421
2605
|
}
|
|
2422
2606
|
function buildExplicitDescriptor(id, command, entry, opts) {
|
|
2423
2607
|
const env = opts.env ?? process.env;
|
|
2424
|
-
|
|
2608
|
+
logger14.debug(
|
|
2425
2609
|
`[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
|
|
2426
2610
|
);
|
|
2427
2611
|
const caps = { ...CONSERVATIVE_CAPABILITIES };
|
|
@@ -2464,7 +2648,7 @@ function buildExplicitDescriptor(id, command, entry, opts) {
|
|
|
2464
2648
|
}
|
|
2465
2649
|
|
|
2466
2650
|
// src/execution/engine/routing.ts
|
|
2467
|
-
var
|
|
2651
|
+
var logger15 = getLogger("subagents");
|
|
2468
2652
|
var RESCAN_OPTS_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/pi-subagent-workflow.engineDiscoveryRescanOpts");
|
|
2469
2653
|
function setEngineDiscoveryRescanOptions(opts) {
|
|
2470
2654
|
Reflect.set(globalThis, RESCAN_OPTS_SLOT_KEY, { current: opts });
|
|
@@ -2480,7 +2664,7 @@ function hasEngineWithRescan(id) {
|
|
|
2480
2664
|
try {
|
|
2481
2665
|
return ensureEngineDiscovered(id, opts);
|
|
2482
2666
|
} catch (err) {
|
|
2483
|
-
|
|
2667
|
+
logger15.debug(
|
|
2484
2668
|
`[engine-routing] rescan for engine '${id}' failed (treated as not discovered): ${err instanceof Error ? err.message : String(err)}`
|
|
2485
2669
|
);
|
|
2486
2670
|
return false;
|
|
@@ -2504,7 +2688,7 @@ function resolveEngineRouting(input) {
|
|
|
2504
2688
|
function resolveDefaultEngineFallback(requestedId, available) {
|
|
2505
2689
|
if (available.length === 0) return void 0;
|
|
2506
2690
|
const target = available[0];
|
|
2507
|
-
|
|
2691
|
+
logger15.warn(
|
|
2508
2692
|
`[engine-routing] default engine '${requestedId}' is not in the discovered engines [${available.join(", ")}]; falling back to first available engine '${target}' (D4, recorded via engineFallback)`
|
|
2509
2693
|
);
|
|
2510
2694
|
return { engineId: target, fallback: { from: requestedId, reason: "engine_not_found" } };
|
|
@@ -2656,7 +2840,7 @@ var path6 = __toESM(require("path"), 1);
|
|
|
2656
2840
|
var import_subagent_engine_sdk10 = require("@zhushanwen/subagent-engine-sdk");
|
|
2657
2841
|
|
|
2658
2842
|
// src/shared/atomic-write.ts
|
|
2659
|
-
var
|
|
2843
|
+
var logger16 = getLogger("subagents");
|
|
2660
2844
|
var TMP_MARKER = ".tmp.";
|
|
2661
2845
|
var TMP_NAME_PATTERN = /^(.+)\.tmp\.(\d+)\.[0-9A-Za-z-]+$/;
|
|
2662
2846
|
var tmpSeq = 0;
|
|
@@ -2678,7 +2862,7 @@ function removeTmpBestEffortSync(tmpPath) {
|
|
|
2678
2862
|
try {
|
|
2679
2863
|
(0, import_node_fs2.unlinkSync)(tmpPath);
|
|
2680
2864
|
} catch (cleanupErr) {
|
|
2681
|
-
|
|
2865
|
+
logger16.debug("[subagent-core] atomic-write cleanup tmp failed", {
|
|
2682
2866
|
detail: (0, import_subagent_engine_sdk10.toErrorMessage)(cleanupErr),
|
|
2683
2867
|
tmpPath
|
|
2684
2868
|
});
|
|
@@ -2726,7 +2910,7 @@ async function writeAtomicFile(filePath, content, options = {}) {
|
|
|
2726
2910
|
await dirFh.close();
|
|
2727
2911
|
}
|
|
2728
2912
|
} catch (dirSyncErr) {
|
|
2729
|
-
|
|
2913
|
+
logger16.debug("[subagent-core] atomic-write fsync dir failed", {
|
|
2730
2914
|
detail: (0, import_subagent_engine_sdk10.toErrorMessage)(dirSyncErr),
|
|
2731
2915
|
dirPath
|
|
2732
2916
|
});
|
|
@@ -2737,7 +2921,7 @@ async function writeAtomicFile(filePath, content, options = {}) {
|
|
|
2737
2921
|
try {
|
|
2738
2922
|
await fsPromises.unlink(tmpPath);
|
|
2739
2923
|
} catch (cleanupErr) {
|
|
2740
|
-
|
|
2924
|
+
logger16.debug("[subagent-core] atomic-write cleanup tmp failed", {
|
|
2741
2925
|
detail: (0, import_subagent_engine_sdk10.toErrorMessage)(cleanupErr),
|
|
2742
2926
|
tmpPath
|
|
2743
2927
|
});
|
|
@@ -2781,7 +2965,7 @@ function cleanupStaleTmpFiles(dir, options = {}) {
|
|
|
2781
2965
|
result.removed.push(ref.tmpPath);
|
|
2782
2966
|
continue;
|
|
2783
2967
|
}
|
|
2784
|
-
|
|
2968
|
+
logger16.debug("[subagent-core] cleanupStaleTmpFiles stat failed", {
|
|
2785
2969
|
detail: (0, import_subagent_engine_sdk10.toErrorMessage)(statErr),
|
|
2786
2970
|
tmpPath: ref.tmpPath
|
|
2787
2971
|
});
|
|
@@ -2796,7 +2980,7 @@ function cleanupStaleTmpFiles(dir, options = {}) {
|
|
|
2796
2980
|
if (typeof unlinkErr.code === "string" && unlinkErr.code === "ENOENT") {
|
|
2797
2981
|
result.removed.push(ref.tmpPath);
|
|
2798
2982
|
} else {
|
|
2799
|
-
|
|
2983
|
+
logger16.debug("[subagent-core] cleanupStaleTmpFiles unlink failed", {
|
|
2800
2984
|
detail: (0, import_subagent_engine_sdk10.toErrorMessage)(unlinkErr),
|
|
2801
2985
|
tmpPath: ref.tmpPath
|
|
2802
2986
|
});
|
|
@@ -2836,72 +3020,6 @@ function syncEnginesFile(agentDir) {
|
|
|
2836
3020
|
}
|
|
2837
3021
|
}
|
|
2838
3022
|
|
|
2839
|
-
// src/execution/engine/host/spawned-children.ts
|
|
2840
|
-
var CoreSpawnedChildrenMirror = class {
|
|
2841
|
-
entries = /* @__PURE__ */ new Map();
|
|
2842
|
-
/** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
|
|
2843
|
-
register(recordId, child) {
|
|
2844
|
-
this.entries.set(recordId, {
|
|
2845
|
-
pid: child.pid,
|
|
2846
|
-
killed: child.killed,
|
|
2847
|
-
updatedAt: Date.now()
|
|
2848
|
-
});
|
|
2849
|
-
}
|
|
2850
|
-
/** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
|
|
2851
|
-
markKilled(recordId) {
|
|
2852
|
-
const entry = this.entries.get(recordId);
|
|
2853
|
-
if (entry !== void 0) {
|
|
2854
|
-
entry.killed = true;
|
|
2855
|
-
entry.updatedAt = Date.now();
|
|
2856
|
-
}
|
|
2857
|
-
}
|
|
2858
|
-
/** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
|
|
2859
|
-
killAll() {
|
|
2860
|
-
this.entries.clear();
|
|
2861
|
-
}
|
|
2862
|
-
getChildByRecord(recordId) {
|
|
2863
|
-
return this.entries.get(recordId);
|
|
2864
|
-
}
|
|
2865
|
-
/** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
|
|
2866
|
-
hasLiveProcessHandle(recordId) {
|
|
2867
|
-
const entry = this.entries.get(recordId);
|
|
2868
|
-
return entry !== void 0 && !entry.killed;
|
|
2869
|
-
}
|
|
2870
|
-
/** 快照(诊断/测试)。 */
|
|
2871
|
-
snapshot() {
|
|
2872
|
-
return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
|
|
2873
|
-
}
|
|
2874
|
-
/** 测试隔离专用(生产禁用——进程级全局状态)。 */
|
|
2875
|
-
clear() {
|
|
2876
|
-
this.entries.clear();
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
|
|
2880
|
-
"@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
|
|
2881
|
-
);
|
|
2882
|
-
function coreMirrorSlot() {
|
|
2883
|
-
let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
|
|
2884
|
-
if (!slot) {
|
|
2885
|
-
slot = new CoreSpawnedChildrenMirror();
|
|
2886
|
-
Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
|
|
2887
|
-
}
|
|
2888
|
-
return slot;
|
|
2889
|
-
}
|
|
2890
|
-
function registerSpawnedChildForRecord(recordId, child) {
|
|
2891
|
-
coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
|
|
2892
|
-
}
|
|
2893
|
-
function killRecordChildWithEscalation(recordId, _source) {
|
|
2894
|
-
coreMirrorSlot().markKilled(recordId);
|
|
2895
|
-
}
|
|
2896
|
-
function killAllSpawnedChildren(_signal = "SIGTERM") {
|
|
2897
|
-
const before = coreMirrorSlot().snapshot().length;
|
|
2898
|
-
coreMirrorSlot().killAll();
|
|
2899
|
-
return before;
|
|
2900
|
-
}
|
|
2901
|
-
function hasLiveProcessHandleCore(recordId) {
|
|
2902
|
-
return coreMirrorSlot().hasLiveProcessHandle(recordId);
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
3023
|
// src/execution/engine/d8-compat.ts
|
|
2906
3024
|
var fs5 = __toESM(require("fs"), 1);
|
|
2907
3025
|
var path8 = __toESM(require("path"), 1);
|
|
@@ -2912,7 +3030,7 @@ var import_subagent_engine_sdk11 = require("@zhushanwen/subagent-engine-sdk");
|
|
|
2912
3030
|
|
|
2913
3031
|
// src/execution/engine/d8-compat.ts
|
|
2914
3032
|
var import_meta = {};
|
|
2915
|
-
var
|
|
3033
|
+
var logger17 = getLogger("subagents");
|
|
2916
3034
|
var D8_ZCODE_ENGINE_ID = "zcode";
|
|
2917
3035
|
var D8_ZSW_HOST_KIND = "zsw";
|
|
2918
3036
|
var D8_PI_HOST_KIND = "pi";
|
|
@@ -2990,7 +3108,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
|
|
|
2990
3108
|
if (hasEngine(id)) return;
|
|
2991
3109
|
const located = locateVendoredEnginePkg(id);
|
|
2992
3110
|
if (located === void 0) {
|
|
2993
|
-
|
|
3111
|
+
logger17.warn(
|
|
2994
3112
|
`[d8-compat] engine '${id}' vendored package not located (enginePkg ${enginePkgName(id)}); left unregistered \u2014 dispatch will fail with engine_not_found + recovery guidance`
|
|
2995
3113
|
);
|
|
2996
3114
|
return;
|
|
@@ -2998,7 +3116,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
|
|
|
2998
3116
|
const syntheticEnv = { ...process.env, XYZ_AGENT_DATA_DIR: engineDataDir() };
|
|
2999
3117
|
const inspection = inspectEnginePackage(located.pkgDir, "d8-compat", D8_PI_HOST_KIND, syntheticEnv);
|
|
3000
3118
|
if (inspection.status !== "ok") {
|
|
3001
|
-
|
|
3119
|
+
logger17.warn(
|
|
3002
3120
|
`[d8-compat] engine '${id}' vendored package inspection failed (${inspection.reason}); left unregistered \u2014 dispatch will fail with engine_not_found + recovery guidance`
|
|
3003
3121
|
);
|
|
3004
3122
|
return;
|
|
@@ -3008,7 +3126,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
|
|
|
3008
3126
|
function warnSourcesIgnoredOnce(deps) {
|
|
3009
3127
|
if (deps.sources === void 0 || warnedSourcesIgnored) return;
|
|
3010
3128
|
warnedSourcesIgnored = true;
|
|
3011
|
-
|
|
3129
|
+
logger17.warn(
|
|
3012
3130
|
"[d8-compat] createZcodeEngine deps.sources is ignored (model/credential sources do not cross the process boundary \u2014 the engine resolves its own credentials, protocol invariant 5)"
|
|
3013
3131
|
);
|
|
3014
3132
|
}
|
|
@@ -3068,12 +3186,12 @@ function createZcodeEngine(deps) {
|
|
|
3068
3186
|
var PI_POOL_KEY = "shared";
|
|
3069
3187
|
var WATCHDOG_FLOOR_MINUTES = 30;
|
|
3070
3188
|
var WATCHDOG_MINUTES_PER_TURN = 5;
|
|
3071
|
-
var
|
|
3072
|
-
var
|
|
3189
|
+
var MS_PER_SECOND2 = 1e3;
|
|
3190
|
+
var SECONDS_PER_MINUTE2 = 60;
|
|
3073
3191
|
function maxTurnsToWatchdogMs(maxTurns) {
|
|
3074
3192
|
return Math.max(
|
|
3075
|
-
WATCHDOG_FLOOR_MINUTES *
|
|
3076
|
-
maxTurns * WATCHDOG_MINUTES_PER_TURN *
|
|
3193
|
+
WATCHDOG_FLOOR_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2,
|
|
3194
|
+
maxTurns * WATCHDOG_MINUTES_PER_TURN * SECONDS_PER_MINUTE2 * MS_PER_SECOND2
|
|
3077
3195
|
);
|
|
3078
3196
|
}
|
|
3079
3197
|
function resolveHostPiEnginePort(_getService) {
|
|
@@ -3212,7 +3330,7 @@ var import_subagent_engine_sdk14 = require("@zhushanwen/subagent-engine-sdk");
|
|
|
3212
3330
|
var ACTIVITY_LABEL_MAX = 60;
|
|
3213
3331
|
var TURN_SUMMARY_MAX = 80;
|
|
3214
3332
|
var TOOL_LABEL_MAX = 100;
|
|
3215
|
-
var
|
|
3333
|
+
var MS_PER_SECOND3 = 1e3;
|
|
3216
3334
|
function extractLabelFromArgs(toolName, args) {
|
|
3217
3335
|
if (typeof args !== "object" || args === null) return toolName;
|
|
3218
3336
|
const a = args;
|
|
@@ -3543,7 +3661,7 @@ function projectOutcome(record) {
|
|
|
3543
3661
|
}
|
|
3544
3662
|
function computeElapsedSeconds(record) {
|
|
3545
3663
|
const end = record.endedAt ?? Date.now();
|
|
3546
|
-
return Math.floor((end - record.startedAt) /
|
|
3664
|
+
return Math.floor((end - record.startedAt) / MS_PER_SECOND3);
|
|
3547
3665
|
}
|
|
3548
3666
|
function project(record) {
|
|
3549
3667
|
return {
|
|
@@ -3606,7 +3724,7 @@ var import_subagent_engine_sdk12 = require("@zhushanwen/subagent-engine-sdk");
|
|
|
3606
3724
|
var import_promises = require("fs/promises");
|
|
3607
3725
|
var import_node_path4 = require("path");
|
|
3608
3726
|
var import_subagent_engine_sdk13 = require("@zhushanwen/subagent-engine-sdk");
|
|
3609
|
-
var
|
|
3727
|
+
var logger18 = getLogger("subagents");
|
|
3610
3728
|
var FLUSH_THRESHOLD_LINES = 64;
|
|
3611
3729
|
var FLUSH_THRESHOLD_BYTES = 32 * 1024;
|
|
3612
3730
|
var defaultFs = {
|
|
@@ -3715,11 +3833,11 @@ var JournalWriter = class {
|
|
|
3715
3833
|
}
|
|
3716
3834
|
};
|
|
3717
3835
|
function defaultWarn2(msg) {
|
|
3718
|
-
|
|
3836
|
+
logger18.warn(msg);
|
|
3719
3837
|
}
|
|
3720
3838
|
|
|
3721
3839
|
// src/execution/engine/common/session-view-service.ts
|
|
3722
|
-
var
|
|
3840
|
+
var logger19 = getLogger("subagents");
|
|
3723
3841
|
var DEFAULT_ENGINE_ID2 = "pi";
|
|
3724
3842
|
var OUTCOME_PLACEHOLDER_TEXT = "(no outcome recorded)";
|
|
3725
3843
|
function extractEngineId(record) {
|
|
@@ -3746,18 +3864,18 @@ function lookupNativeSessionReader(engineId) {
|
|
|
3746
3864
|
function readJournalTier(record, handle, dataDir) {
|
|
3747
3865
|
const journalPath = handle.journalPath;
|
|
3748
3866
|
if (journalPath === void 0) {
|
|
3749
|
-
|
|
3867
|
+
logger19.debug("[session-view-service] tier2 skipped: no journalPath in handle");
|
|
3750
3868
|
return void 0;
|
|
3751
3869
|
}
|
|
3752
3870
|
if (!isStrictlyUnder((0, import_subagent_engine_sdk12.resolveEnginesRoot)(dataDir), journalPath)) {
|
|
3753
|
-
|
|
3871
|
+
logger19.warn(
|
|
3754
3872
|
`[session-view-service] journalPath escapes engines root, reject tier2: ${journalPath}`
|
|
3755
3873
|
);
|
|
3756
3874
|
return void 0;
|
|
3757
3875
|
}
|
|
3758
3876
|
const messages = replayEventsToHistory((0, import_subagent_engine_sdk13.replayJournal)(journalPath), record);
|
|
3759
3877
|
if (messages === void 0) {
|
|
3760
|
-
|
|
3878
|
+
logger19.debug(
|
|
3761
3879
|
`[session-view-service] tier2 journal replay produced no content, degrade to outcome-only (path=${journalPath})`
|
|
3762
3880
|
);
|
|
3763
3881
|
}
|
|
@@ -3927,7 +4045,7 @@ async function readSubagentHistoryMessages(record, dataDir) {
|
|
|
3927
4045
|
if (engineId === DEFAULT_ENGINE_ID2) return [];
|
|
3928
4046
|
const handle = parseEngineHandle(record.engineHandle);
|
|
3929
4047
|
if (handle === void 0) {
|
|
3930
|
-
|
|
4048
|
+
logger19.debug(
|
|
3931
4049
|
`[session-view-service] engine '${engineId}' record has no engineHandle, degrade to outcome-only (subagentId=${record.subagentId})`
|
|
3932
4050
|
);
|
|
3933
4051
|
return outcomeOnlyMessages(record);
|
|
@@ -3937,12 +4055,12 @@ async function readSubagentHistoryMessages(record, dataDir) {
|
|
|
3937
4055
|
const native = await reader(handle, dataDir);
|
|
3938
4056
|
if (native !== void 0) {
|
|
3939
4057
|
if (native.turns.some(hasTurnContent)) return sessionViewToMessages(native, record);
|
|
3940
|
-
|
|
4058
|
+
logger19.debug(
|
|
3941
4059
|
`[session-view-service] tier1 native view has no substantive content (turns=${native.turns.length}), degrade to journal tier (subagentId=${record.subagentId})`
|
|
3942
4060
|
);
|
|
3943
4061
|
}
|
|
3944
4062
|
} else {
|
|
3945
|
-
|
|
4063
|
+
logger19.debug(
|
|
3946
4064
|
`[session-view-service] engine '${engineId}' has no native reader tier, fall through to journal tier (subagentId=${record.subagentId})`
|
|
3947
4065
|
);
|
|
3948
4066
|
}
|
|
@@ -3987,21 +4105,6 @@ var DirtyWorktreeError = class extends Error {
|
|
|
3987
4105
|
// src/execution/subagent-service.ts
|
|
3988
4106
|
var import_node_async_hooks = require("async_hooks");
|
|
3989
4107
|
|
|
3990
|
-
// src/shared/timer-delay.ts
|
|
3991
|
-
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
3992
|
-
function assertSafeTimerDelay(ms, source) {
|
|
3993
|
-
if (!Number.isFinite(ms)) {
|
|
3994
|
-
throw new Error(
|
|
3995
|
-
`[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
|
|
3996
|
-
);
|
|
3997
|
-
}
|
|
3998
|
-
if (ms > MAX_TIMER_DELAY_MS) {
|
|
3999
|
-
throw new Error(
|
|
4000
|
-
`[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
|
|
4001
|
-
);
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
|
|
4005
4108
|
// src/execution/agent-result-mapper.ts
|
|
4006
4109
|
var TOOL_ARGS_JSON_MAX_CHARS = 500;
|
|
4007
4110
|
function mapToWorkflowAgentResult(r) {
|
|
@@ -4099,14 +4202,14 @@ function isErrnoException(err) {
|
|
|
4099
4202
|
}
|
|
4100
4203
|
|
|
4101
4204
|
// src/execution/best-effort.ts
|
|
4102
|
-
var
|
|
4205
|
+
var logger20 = getLogger("subagents");
|
|
4103
4206
|
function bestEffort(err, context, level = "debug") {
|
|
4104
4207
|
const detail = err instanceof Error ? err.message : err;
|
|
4105
4208
|
const msg = `[subagents] best-effort ${context} failed`;
|
|
4106
4209
|
if (level === "error") {
|
|
4107
|
-
|
|
4210
|
+
logger20.error(msg, { detail });
|
|
4108
4211
|
} else {
|
|
4109
|
-
|
|
4212
|
+
logger20.debug(msg, { detail });
|
|
4110
4213
|
}
|
|
4111
4214
|
}
|
|
4112
4215
|
|
|
@@ -4197,7 +4300,7 @@ var CollectCoordinator = class {
|
|
|
4197
4300
|
// src/execution/config.ts
|
|
4198
4301
|
var fs7 = __toESM(require("fs"), 1);
|
|
4199
4302
|
var path9 = __toESM(require("path"), 1);
|
|
4200
|
-
var
|
|
4303
|
+
var logger21 = getLogger("subagents");
|
|
4201
4304
|
var DEFAULT_CONFIG = {
|
|
4202
4305
|
version: 1,
|
|
4203
4306
|
maxConcurrent: 6
|
|
@@ -4240,7 +4343,7 @@ function readGlobalConfig(agentDir) {
|
|
|
4240
4343
|
}
|
|
4241
4344
|
function readFailure(configPath, err) {
|
|
4242
4345
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4243
|
-
|
|
4346
|
+
logger21.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
4244
4347
|
return { status: "failed", reason };
|
|
4245
4348
|
}
|
|
4246
4349
|
function errnoCodeOf(err) {
|
|
@@ -4279,7 +4382,7 @@ function sanitizeCollectSync(value) {
|
|
|
4279
4382
|
bad.push(`totalChars=${fmt(v.totalChars)}`);
|
|
4280
4383
|
}
|
|
4281
4384
|
if (bad.length > 0) {
|
|
4282
|
-
|
|
4385
|
+
logger21.warn(
|
|
4283
4386
|
`[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`
|
|
4284
4387
|
);
|
|
4285
4388
|
}
|
|
@@ -4304,56 +4407,6 @@ function sanitizeEngineRouting(value) {
|
|
|
4304
4407
|
return typeof strict === "boolean" ? { strict } : void 0;
|
|
4305
4408
|
}
|
|
4306
4409
|
|
|
4307
|
-
// src/execution/lifecycle-manager.ts
|
|
4308
|
-
var logger21 = getLogger("subagents");
|
|
4309
|
-
var MS_PER_SECOND3 = 1e3;
|
|
4310
|
-
var SECONDS_PER_MINUTE2 = 60;
|
|
4311
|
-
var IDLE_TIMEOUT_MINUTES = 5;
|
|
4312
|
-
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND3;
|
|
4313
|
-
function getEnvIdleTimeoutMs() {
|
|
4314
|
-
const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
|
|
4315
|
-
if (!raw) return void 0;
|
|
4316
|
-
const parsed = Number(raw);
|
|
4317
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4318
|
-
logger21.warn(
|
|
4319
|
-
`[lifecycle-manager] XYZ_SUBAGENT_IDLE_TIMEOUT_MS="${raw}" is invalid (expected a positive millisecond number) \u2014 falling back to DEFAULT_IDLE_TIMEOUT_MS (${DEFAULT_IDLE_TIMEOUT_MS}ms); set a plain ms value (e.g. 1800000) to override`
|
|
4320
|
-
);
|
|
4321
|
-
return void 0;
|
|
4322
|
-
}
|
|
4323
|
-
return parsed;
|
|
4324
|
-
}
|
|
4325
|
-
var idleTimers = /* @__PURE__ */ new Map();
|
|
4326
|
-
function armIdleTimer(recordId, onTimeout, timeoutMs) {
|
|
4327
|
-
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
4328
|
-
disarmIdleTimer(recordId);
|
|
4329
|
-
return;
|
|
4330
|
-
}
|
|
4331
|
-
const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
4332
|
-
assertSafeTimerDelay(resolved, "idleTimeoutMs");
|
|
4333
|
-
disarmIdleTimer(recordId);
|
|
4334
|
-
const timer = setTimeout(() => {
|
|
4335
|
-
if (idleTimers.get(recordId)?.timer === timer) {
|
|
4336
|
-
idleTimers.delete(recordId);
|
|
4337
|
-
}
|
|
4338
|
-
onTimeout();
|
|
4339
|
-
}, resolved);
|
|
4340
|
-
timer.unref?.();
|
|
4341
|
-
idleTimers.set(recordId, { timer, timeoutMs: resolved });
|
|
4342
|
-
}
|
|
4343
|
-
function disarmIdleTimer(recordId) {
|
|
4344
|
-
const entry = idleTimers.get(recordId);
|
|
4345
|
-
if (!entry) {
|
|
4346
|
-
return;
|
|
4347
|
-
}
|
|
4348
|
-
clearTimeout(entry.timer);
|
|
4349
|
-
idleTimers.delete(recordId);
|
|
4350
|
-
}
|
|
4351
|
-
function hasIdleTimer(recordId) {
|
|
4352
|
-
return idleTimers.has(recordId);
|
|
4353
|
-
}
|
|
4354
|
-
var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
|
|
4355
|
-
var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND3;
|
|
4356
|
-
|
|
4357
4410
|
// src/execution/concurrency-pool.ts
|
|
4358
4411
|
var DefaultConcurrencyPool = class {
|
|
4359
4412
|
_active = 0;
|
|
@@ -5571,17 +5624,6 @@ function displayAgentName(ref) {
|
|
|
5571
5624
|
return base.endsWith(AGENT_REF_EXT) ? base.slice(0, -AGENT_REF_EXT.length) : base;
|
|
5572
5625
|
}
|
|
5573
5626
|
|
|
5574
|
-
// src/execution/lifecycle-predicates.ts
|
|
5575
|
-
function hasLiveProcessHandle(recordId) {
|
|
5576
|
-
return hasLiveProcessHandleCore(recordId);
|
|
5577
|
-
}
|
|
5578
|
-
function isIdle(record) {
|
|
5579
|
-
return hasIdleTimer(record.id);
|
|
5580
|
-
}
|
|
5581
|
-
function isResumable(record) {
|
|
5582
|
-
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
5583
|
-
}
|
|
5584
|
-
|
|
5585
5627
|
// src/execution/notifier.ts
|
|
5586
5628
|
var import_node_crypto2 = require("crypto");
|
|
5587
5629
|
|
|
@@ -6977,10 +7019,10 @@ var RecordStore = class _RecordStore {
|
|
|
6977
7019
|
return out;
|
|
6978
7020
|
}
|
|
6979
7021
|
/** 订阅变更。返回取消订阅函数。 */
|
|
6980
|
-
onChange(
|
|
6981
|
-
this.listeners.add(
|
|
7022
|
+
onChange(listener2) {
|
|
7023
|
+
this.listeners.add(listener2);
|
|
6982
7024
|
return () => {
|
|
6983
|
-
this.listeners.delete(
|
|
7025
|
+
this.listeners.delete(listener2);
|
|
6984
7026
|
};
|
|
6985
7027
|
}
|
|
6986
7028
|
/** 触发所有监听器(TUI widget/list requestRender)。dispose 后短路。
|
|
@@ -6988,8 +7030,8 @@ var RecordStore = class _RecordStore {
|
|
|
6988
7030
|
* 内存事件(register/archive)不改变磁盘文件——旧实现整体失效是全量重扫的根因。 */
|
|
6989
7031
|
notifyChange() {
|
|
6990
7032
|
if (this._disposed) return;
|
|
6991
|
-
for (const
|
|
6992
|
-
|
|
7033
|
+
for (const listener2 of this.listeners) {
|
|
7034
|
+
listener2();
|
|
6993
7035
|
}
|
|
6994
7036
|
}
|
|
6995
7037
|
/** session 结束清理。 */
|
|
@@ -9421,9 +9463,11 @@ var WorktreeRegistry = class {
|
|
|
9421
9463
|
}
|
|
9422
9464
|
/**
|
|
9423
9465
|
* proper-lockfile 直用的跨进程锁(取代已删除的共享 file-lock 包装,抽包去依赖)。
|
|
9424
|
-
*
|
|
9425
|
-
*
|
|
9426
|
-
*
|
|
9466
|
+
* 锁协议对齐现存三方同协议实现——runtime 侧 packages/runtime/src/utils/file-lock.ts
|
|
9467
|
+
* 的 withFileLockAsync(范本 pi FileAuthStorageBackend,参数对齐 proper-lockfile
|
|
9468
|
+
* 内部 retry 库)与 extension 侧 @zhushanwen/pi-file-lock:
|
|
9469
|
+
* - lockfile 路径 = <目标文件>.lock(proper-lockfile 默认,与 runtime 侧/
|
|
9470
|
+
* extension 侧同一路径才互斥)
|
|
9427
9471
|
* - realpath:false —— 目标文件不存在也可锁(realpath 默认 true 时 ENOENT)
|
|
9428
9472
|
* - stale 30s:持锁进程崩溃后锁可被夺取
|
|
9429
9473
|
* - async retries 指数退避:10 次 / factor 2 / 100ms~10s / randomize,耗尽抛
|
|
@@ -10210,7 +10254,7 @@ var SubagentService = class {
|
|
|
10210
10254
|
lookupRecordAnyState: (id) => this.lookupRecordAnyState(id),
|
|
10211
10255
|
collectRecords: (limit, statusFilter) => this.collectRecords(limit, statusFilter),
|
|
10212
10256
|
getFullRecord: (id) => this.getFullRecord(id),
|
|
10213
|
-
onChange: (
|
|
10257
|
+
onChange: (listener2) => this.onChange(listener2)
|
|
10214
10258
|
};
|
|
10215
10259
|
/** [D4 对话 action 面聚合] chat 域 message/close 消费面(壳 subagent-actions 经此访问;
|
|
10216
10260
|
* 纯委托同上。PiEngineService 适配器不经此——引擎边界走 piEngineServiceAdapter)。 */
|
|
@@ -10463,6 +10507,7 @@ var SubagentService = class {
|
|
|
10463
10507
|
this.notifyHost.emitPendingUnregister(record.id, "closed");
|
|
10464
10508
|
count++;
|
|
10465
10509
|
}
|
|
10510
|
+
notifyInFlightChanged();
|
|
10466
10511
|
return count;
|
|
10467
10512
|
}
|
|
10468
10513
|
/** SP-4: /fork 新 session 时清理旧 record。
|
|
@@ -10890,6 +10935,7 @@ var SubagentService = class {
|
|
|
10890
10935
|
);
|
|
10891
10936
|
}
|
|
10892
10937
|
disarmIdleTimer(record.id);
|
|
10938
|
+
notifyInFlightChanged();
|
|
10893
10939
|
const engine = this.resolveChatEnginePort();
|
|
10894
10940
|
if (!this.chatRoundRoutes.has(record.id)) {
|
|
10895
10941
|
this.chatRoundRoutes.set(
|
|
@@ -11023,6 +11069,7 @@ var SubagentService = class {
|
|
|
11023
11069
|
`[subagents] settled watchdog (${fire.phase}) fired for ${record.id}: ${windowDesc}, terminating (LC-1 wedge recovery)`
|
|
11024
11070
|
);
|
|
11025
11071
|
killRecordChildWithEscalation(record.id, "settled watchdog (hot path)");
|
|
11072
|
+
notifyInFlightChanged();
|
|
11026
11073
|
this.terminateChatSession(record, "cancel", "settled watchdog (hot path)");
|
|
11027
11074
|
const failedResult = {
|
|
11028
11075
|
text: "",
|
|
@@ -11135,6 +11182,7 @@ var SubagentService = class {
|
|
|
11135
11182
|
*/
|
|
11136
11183
|
async closeChatIdle(record) {
|
|
11137
11184
|
disarmIdleTimer(record.id);
|
|
11185
|
+
notifyInFlightChanged();
|
|
11138
11186
|
disarmSettledWatchdog(record.id);
|
|
11139
11187
|
disarmRoundFromProtocol(record.id);
|
|
11140
11188
|
killRecordChildWithEscalation(record.id, "closeChatIdle");
|
|
@@ -11183,6 +11231,7 @@ var SubagentService = class {
|
|
|
11183
11231
|
*/
|
|
11184
11232
|
async closeAfterRoundSettled(record) {
|
|
11185
11233
|
disarmIdleTimer(record.id);
|
|
11234
|
+
notifyInFlightChanged();
|
|
11186
11235
|
disarmSettledWatchdog(record.id);
|
|
11187
11236
|
disarmRoundFromProtocol(record.id);
|
|
11188
11237
|
killRecordChildWithEscalation(record.id, "closeAfterRoundSettled");
|
|
@@ -11259,8 +11308,8 @@ var SubagentService = class {
|
|
|
11259
11308
|
}
|
|
11260
11309
|
// ── 状态查询(TUI 调)──────────────────────────────────
|
|
11261
11310
|
/** 订阅 store 变更(widget/list requestRender)。返回取消订阅。 */
|
|
11262
|
-
onChange(
|
|
11263
|
-
return this.store.onChange(
|
|
11311
|
+
onChange(listener2) {
|
|
11312
|
+
return this.store.onChange(listener2);
|
|
11264
11313
|
}
|
|
11265
11314
|
// [D4] listRunning 已删除:零生产调用方(TUI 计数经 collectRecords / notify-host 的
|
|
11266
11315
|
// piAdapter 直调 store.listRunning 覆盖),唯一消费是初始空态单测——保留 store 层方法。
|
|
@@ -11872,6 +11921,7 @@ var SubagentService = class {
|
|
|
11872
11921
|
bestEffort(fallbackErr, "armIdleTimer fallback (chat idle phase)", "error");
|
|
11873
11922
|
}
|
|
11874
11923
|
}
|
|
11924
|
+
notifyInFlightChanged();
|
|
11875
11925
|
}
|
|
11876
11926
|
/** [W3] idle 帧锚点回填(冷续锚点 = 引擎侧会话滚动/compaction 后的最新定位)。 */
|
|
11877
11927
|
backfillChatAnchor(record, anchor) {
|
|
@@ -12018,6 +12068,7 @@ var SubagentService = class {
|
|
|
12018
12068
|
record.controller?.abort();
|
|
12019
12069
|
killRecordChildWithEscalation(record.id, "cancelBackground");
|
|
12020
12070
|
disarmIdleTimer(record.id);
|
|
12071
|
+
notifyInFlightChanged();
|
|
12021
12072
|
disarmSettledWatchdog(record.id);
|
|
12022
12073
|
disarmRoundFromProtocol(record.id);
|
|
12023
12074
|
this.unregisterChatRoundRoute(record.id);
|
|
@@ -17294,7 +17345,7 @@ function boundedPrettySerialize(value, budget) {
|
|
|
17294
17345
|
}
|
|
17295
17346
|
|
|
17296
17347
|
// src/index.ts
|
|
17297
|
-
var CORE_PACKAGE_VERSION = "0.
|
|
17348
|
+
var CORE_PACKAGE_VERSION = "0.8.0";
|
|
17298
17349
|
// Annotate the CommonJS export names for ESM import in node:
|
|
17299
17350
|
0 && (module.exports = {
|
|
17300
17351
|
AGENT_REF_EXT,
|
|
@@ -17392,6 +17443,7 @@ var CORE_PACKAGE_VERSION = "0.7.0";
|
|
|
17392
17443
|
getCachedFileContent,
|
|
17393
17444
|
getCachedParsed,
|
|
17394
17445
|
getHostServices,
|
|
17446
|
+
getInFlightSnapshot,
|
|
17395
17447
|
getLogger,
|
|
17396
17448
|
getModelConfigService,
|
|
17397
17449
|
getOrCreateChannelRegistry,
|
|
@@ -17449,6 +17501,7 @@ var CORE_PACKAGE_VERSION = "0.7.0";
|
|
|
17449
17501
|
saveWorkflow,
|
|
17450
17502
|
scheduleTimeBudget,
|
|
17451
17503
|
setEngineDiscoveryRescanOptions,
|
|
17504
|
+
setInFlightListener,
|
|
17452
17505
|
setModelConfigService,
|
|
17453
17506
|
setSubagentService,
|
|
17454
17507
|
sortByCodepoint,
|