@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.bundle/index.cjs
CHANGED
|
@@ -7693,7 +7693,7 @@ var require_signal_exit = __commonJS({
|
|
|
7693
7693
|
};
|
|
7694
7694
|
sigListeners = {};
|
|
7695
7695
|
signals.forEach(function(sig) {
|
|
7696
|
-
sigListeners[sig] = function
|
|
7696
|
+
sigListeners[sig] = function listener2() {
|
|
7697
7697
|
if (!processOk(global.process)) {
|
|
7698
7698
|
return;
|
|
7699
7699
|
}
|
|
@@ -15579,6 +15579,7 @@ __export(src_exports, {
|
|
|
15579
15579
|
getCachedFileContent: () => getCachedFileContent,
|
|
15580
15580
|
getCachedParsed: () => getCachedParsed,
|
|
15581
15581
|
getHostServices: () => getHostServices,
|
|
15582
|
+
getInFlightSnapshot: () => getInFlightSnapshot,
|
|
15582
15583
|
getLogger: () => getLogger,
|
|
15583
15584
|
getModelConfigService: () => getModelConfigService,
|
|
15584
15585
|
getOrCreateChannelRegistry: () => getOrCreateChannelRegistry,
|
|
@@ -15636,6 +15637,7 @@ __export(src_exports, {
|
|
|
15636
15637
|
saveWorkflow: () => saveWorkflow,
|
|
15637
15638
|
scheduleTimeBudget: () => scheduleTimeBudget,
|
|
15638
15639
|
setEngineDiscoveryRescanOptions: () => setEngineDiscoveryRescanOptions,
|
|
15640
|
+
setInFlightListener: () => setInFlightListener,
|
|
15639
15641
|
setModelConfigService: () => setModelConfigService,
|
|
15640
15642
|
setSubagentService: () => setSubagentService,
|
|
15641
15643
|
sortByCodepoint: () => sortByCodepoint,
|
|
@@ -16733,16 +16735,16 @@ var SpawnedChildrenMirror = class {
|
|
|
16733
16735
|
return this.entries.size;
|
|
16734
16736
|
}
|
|
16735
16737
|
/** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
|
|
16736
|
-
onChange(
|
|
16737
|
-
this.listeners.add(
|
|
16738
|
+
onChange(listener2) {
|
|
16739
|
+
this.listeners.add(listener2);
|
|
16738
16740
|
return () => {
|
|
16739
|
-
this.listeners.delete(
|
|
16741
|
+
this.listeners.delete(listener2);
|
|
16740
16742
|
};
|
|
16741
16743
|
}
|
|
16742
16744
|
emit(event) {
|
|
16743
|
-
for (const
|
|
16745
|
+
for (const listener2 of this.listeners) {
|
|
16744
16746
|
try {
|
|
16745
|
-
|
|
16747
|
+
listener2(event);
|
|
16746
16748
|
} catch (err) {
|
|
16747
16749
|
logger9.debug(
|
|
16748
16750
|
`[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -17187,8 +17189,189 @@ function waitForChildExit(child, timeoutMs) {
|
|
|
17187
17189
|
});
|
|
17188
17190
|
}
|
|
17189
17191
|
|
|
17192
|
+
// src/execution/engine/host/spawned-children.ts
|
|
17193
|
+
var CoreSpawnedChildrenMirror = class {
|
|
17194
|
+
entries = /* @__PURE__ */ new Map();
|
|
17195
|
+
/** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
|
|
17196
|
+
register(recordId, child) {
|
|
17197
|
+
this.entries.set(recordId, {
|
|
17198
|
+
pid: child.pid,
|
|
17199
|
+
killed: child.killed,
|
|
17200
|
+
updatedAt: Date.now()
|
|
17201
|
+
});
|
|
17202
|
+
}
|
|
17203
|
+
/** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
|
|
17204
|
+
markKilled(recordId) {
|
|
17205
|
+
const entry = this.entries.get(recordId);
|
|
17206
|
+
if (entry !== void 0) {
|
|
17207
|
+
entry.killed = true;
|
|
17208
|
+
entry.updatedAt = Date.now();
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
17211
|
+
/** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
|
|
17212
|
+
killAll() {
|
|
17213
|
+
this.entries.clear();
|
|
17214
|
+
}
|
|
17215
|
+
getChildByRecord(recordId) {
|
|
17216
|
+
return this.entries.get(recordId);
|
|
17217
|
+
}
|
|
17218
|
+
/** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
|
|
17219
|
+
hasLiveProcessHandle(recordId) {
|
|
17220
|
+
const entry = this.entries.get(recordId);
|
|
17221
|
+
return entry !== void 0 && !entry.killed;
|
|
17222
|
+
}
|
|
17223
|
+
/** 快照(诊断/测试)。 */
|
|
17224
|
+
snapshot() {
|
|
17225
|
+
return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
|
|
17226
|
+
}
|
|
17227
|
+
/** 测试隔离专用(生产禁用——进程级全局状态)。 */
|
|
17228
|
+
clear() {
|
|
17229
|
+
this.entries.clear();
|
|
17230
|
+
}
|
|
17231
|
+
};
|
|
17232
|
+
var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
|
|
17233
|
+
"@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
|
|
17234
|
+
);
|
|
17235
|
+
function coreMirrorSlot() {
|
|
17236
|
+
let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
|
|
17237
|
+
if (!slot) {
|
|
17238
|
+
slot = new CoreSpawnedChildrenMirror();
|
|
17239
|
+
Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
|
|
17240
|
+
}
|
|
17241
|
+
return slot;
|
|
17242
|
+
}
|
|
17243
|
+
function coreSpawnedChildrenMirror() {
|
|
17244
|
+
return coreMirrorSlot();
|
|
17245
|
+
}
|
|
17246
|
+
function registerSpawnedChildForRecord(recordId, child) {
|
|
17247
|
+
coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
|
|
17248
|
+
}
|
|
17249
|
+
function killRecordChildWithEscalation(recordId, _source) {
|
|
17250
|
+
coreMirrorSlot().markKilled(recordId);
|
|
17251
|
+
}
|
|
17252
|
+
function killAllSpawnedChildren(_signal = "SIGTERM") {
|
|
17253
|
+
const before = coreMirrorSlot().snapshot().length;
|
|
17254
|
+
coreMirrorSlot().killAll();
|
|
17255
|
+
return before;
|
|
17256
|
+
}
|
|
17257
|
+
function hasLiveProcessHandleCore(recordId) {
|
|
17258
|
+
return coreMirrorSlot().hasLiveProcessHandle(recordId);
|
|
17259
|
+
}
|
|
17260
|
+
|
|
17261
|
+
// src/shared/timer-delay.ts
|
|
17262
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
17263
|
+
function assertSafeTimerDelay(ms, source) {
|
|
17264
|
+
if (!Number.isFinite(ms)) {
|
|
17265
|
+
throw new Error(
|
|
17266
|
+
`[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.`
|
|
17267
|
+
);
|
|
17268
|
+
}
|
|
17269
|
+
if (ms > MAX_TIMER_DELAY_MS) {
|
|
17270
|
+
throw new Error(
|
|
17271
|
+
`[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.`
|
|
17272
|
+
);
|
|
17273
|
+
}
|
|
17274
|
+
}
|
|
17275
|
+
|
|
17276
|
+
// src/execution/lifecycle-manager.ts
|
|
17277
|
+
var logger14 = getLogger("subagents");
|
|
17278
|
+
var MS_PER_SECOND2 = 1e3;
|
|
17279
|
+
var SECONDS_PER_MINUTE2 = 60;
|
|
17280
|
+
var IDLE_TIMEOUT_MINUTES = 5;
|
|
17281
|
+
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
|
|
17282
|
+
function getEnvIdleTimeoutMs() {
|
|
17283
|
+
const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
|
|
17284
|
+
if (!raw) return void 0;
|
|
17285
|
+
const parsed = Number(raw);
|
|
17286
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
17287
|
+
logger14.warn(
|
|
17288
|
+
`[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`
|
|
17289
|
+
);
|
|
17290
|
+
return void 0;
|
|
17291
|
+
}
|
|
17292
|
+
return parsed;
|
|
17293
|
+
}
|
|
17294
|
+
var idleTimers = /* @__PURE__ */ new Map();
|
|
17295
|
+
function armIdleTimer(recordId, onTimeout, timeoutMs) {
|
|
17296
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
17297
|
+
disarmIdleTimer(recordId);
|
|
17298
|
+
return;
|
|
17299
|
+
}
|
|
17300
|
+
const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
17301
|
+
assertSafeTimerDelay(resolved, "idleTimeoutMs");
|
|
17302
|
+
disarmIdleTimer(recordId);
|
|
17303
|
+
const timer = setTimeout(() => {
|
|
17304
|
+
if (idleTimers.get(recordId)?.timer === timer) {
|
|
17305
|
+
idleTimers.delete(recordId);
|
|
17306
|
+
}
|
|
17307
|
+
onTimeout();
|
|
17308
|
+
}, resolved);
|
|
17309
|
+
timer.unref?.();
|
|
17310
|
+
idleTimers.set(recordId, { timer, timeoutMs: resolved });
|
|
17311
|
+
}
|
|
17312
|
+
function disarmIdleTimer(recordId) {
|
|
17313
|
+
const entry = idleTimers.get(recordId);
|
|
17314
|
+
if (!entry) {
|
|
17315
|
+
return;
|
|
17316
|
+
}
|
|
17317
|
+
clearTimeout(entry.timer);
|
|
17318
|
+
idleTimers.delete(recordId);
|
|
17319
|
+
}
|
|
17320
|
+
function hasIdleTimer(recordId) {
|
|
17321
|
+
return idleTimers.has(recordId);
|
|
17322
|
+
}
|
|
17323
|
+
var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
|
|
17324
|
+
var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND2;
|
|
17325
|
+
|
|
17326
|
+
// src/execution/lifecycle-predicates.ts
|
|
17327
|
+
function hasLiveProcessHandle(recordId) {
|
|
17328
|
+
return hasLiveProcessHandleCore(recordId);
|
|
17329
|
+
}
|
|
17330
|
+
function isIdle(record) {
|
|
17331
|
+
return hasIdleTimer(record.id);
|
|
17332
|
+
}
|
|
17333
|
+
function isResumable(record) {
|
|
17334
|
+
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
17335
|
+
}
|
|
17336
|
+
|
|
17337
|
+
// src/execution/engine/inflight-snapshot.ts
|
|
17338
|
+
var listener = null;
|
|
17339
|
+
function setInFlightListener(next) {
|
|
17340
|
+
listener = next;
|
|
17341
|
+
}
|
|
17342
|
+
function getInFlightSnapshot() {
|
|
17343
|
+
return { inFlight: countInFlight() };
|
|
17344
|
+
}
|
|
17345
|
+
function notifyInFlightChanged() {
|
|
17346
|
+
if (listener === null) return;
|
|
17347
|
+
try {
|
|
17348
|
+
listener(getInFlightSnapshot());
|
|
17349
|
+
} catch {
|
|
17350
|
+
}
|
|
17351
|
+
}
|
|
17352
|
+
function countInFlight() {
|
|
17353
|
+
let count = 0;
|
|
17354
|
+
for (const entry of coreSpawnedChildrenMirror().snapshot()) {
|
|
17355
|
+
const recordId = entry.recordId;
|
|
17356
|
+
if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
|
|
17357
|
+
}
|
|
17358
|
+
return count;
|
|
17359
|
+
}
|
|
17360
|
+
|
|
17190
17361
|
// src/execution/engine/client/engine-client.ts
|
|
17191
|
-
var
|
|
17362
|
+
var logger15 = getLogger2("subagents");
|
|
17363
|
+
function bridgeMirrorEventToCoreMirror(event, mirror) {
|
|
17364
|
+
if (event.recordId === void 0 || event.pid === void 0) return;
|
|
17365
|
+
const entry = mirror.getEntry(event.pid);
|
|
17366
|
+
if (entry === void 0) return;
|
|
17367
|
+
const core = coreSpawnedChildrenMirror();
|
|
17368
|
+
if (entry.killed) {
|
|
17369
|
+
core.markKilled(event.recordId);
|
|
17370
|
+
} else {
|
|
17371
|
+
core.register(event.recordId, { pid: entry.pid, killed: false });
|
|
17372
|
+
}
|
|
17373
|
+
notifyInFlightChanged();
|
|
17374
|
+
}
|
|
17192
17375
|
var DISPOSE_GRACE_MS = 3e3;
|
|
17193
17376
|
var SIGKILL_REAP_TIMEOUT_MS = 1e4;
|
|
17194
17377
|
var FRAME_ECHO_MAX_CHARS = 200;
|
|
@@ -17251,6 +17434,7 @@ var EngineClient = class {
|
|
|
17251
17434
|
};
|
|
17252
17435
|
this.mirror.onChange((event) => {
|
|
17253
17436
|
opts.onMirrorChanged?.(event);
|
|
17437
|
+
bridgeMirrorEventToCoreMirror(event, this.mirror);
|
|
17254
17438
|
});
|
|
17255
17439
|
}
|
|
17256
17440
|
get currentState() {
|
|
@@ -17313,19 +17497,19 @@ var EngineClient = class {
|
|
|
17313
17497
|
matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
|
|
17314
17498
|
});
|
|
17315
17499
|
if (sweep.killed.length > 0) {
|
|
17316
|
-
|
|
17500
|
+
logger15.warn(
|
|
17317
17501
|
`[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
|
|
17318
17502
|
);
|
|
17319
17503
|
}
|
|
17320
17504
|
for (const removed of sweep.removed) {
|
|
17321
|
-
|
|
17505
|
+
logger15.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
|
|
17322
17506
|
}
|
|
17323
17507
|
}
|
|
17324
17508
|
let attempt = 0;
|
|
17325
17509
|
while (attempt <= CRASH_REBUILD_MAX_ATTEMPTS) {
|
|
17326
17510
|
if (attempt > 0) {
|
|
17327
17511
|
const backoff = CRASH_REBUILD_BACKOFF_MS[attempt - 1];
|
|
17328
|
-
|
|
17512
|
+
logger15.warn(
|
|
17329
17513
|
`[engine-client:${this.engineId}] rebuild attempt ${attempt}/${CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
|
|
17330
17514
|
);
|
|
17331
17515
|
await delay(backoff);
|
|
@@ -17358,7 +17542,7 @@ var EngineClient = class {
|
|
|
17358
17542
|
markUnavailable(reason) {
|
|
17359
17543
|
this.state = "unavailable";
|
|
17360
17544
|
this.unavailableReason = reason;
|
|
17361
|
-
|
|
17545
|
+
logger15.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
|
|
17362
17546
|
}
|
|
17363
17547
|
/** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
|
|
17364
17548
|
async spawnAndInitialize() {
|
|
@@ -17388,13 +17572,13 @@ var EngineClient = class {
|
|
|
17388
17572
|
});
|
|
17389
17573
|
const child = this.child;
|
|
17390
17574
|
child.on("error", (err) => {
|
|
17391
|
-
|
|
17575
|
+
logger15.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
|
|
17392
17576
|
this.appendStderrTail(`spawn error: ${err.message}
|
|
17393
17577
|
`);
|
|
17394
17578
|
});
|
|
17395
17579
|
for (const pipe of ["stdin", "stdout", "stderr"]) {
|
|
17396
17580
|
child[pipe]?.on("error", (err) => {
|
|
17397
|
-
|
|
17581
|
+
logger15.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
|
|
17398
17582
|
});
|
|
17399
17583
|
}
|
|
17400
17584
|
child.stdout?.setEncoding("utf-8");
|
|
@@ -17463,7 +17647,7 @@ var EngineClient = class {
|
|
|
17463
17647
|
try {
|
|
17464
17648
|
frame = JSON.parse(line);
|
|
17465
17649
|
} catch {
|
|
17466
|
-
|
|
17650
|
+
logger15.warn(
|
|
17467
17651
|
`[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
17468
17652
|
);
|
|
17469
17653
|
return;
|
|
@@ -17480,7 +17664,7 @@ var EngineClient = class {
|
|
|
17480
17664
|
this.onReverseRequest(frame);
|
|
17481
17665
|
return;
|
|
17482
17666
|
}
|
|
17483
|
-
|
|
17667
|
+
logger15.warn(
|
|
17484
17668
|
`[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
17485
17669
|
);
|
|
17486
17670
|
}
|
|
@@ -17604,7 +17788,7 @@ var EngineClient = class {
|
|
|
17604
17788
|
try {
|
|
17605
17789
|
await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
|
|
17606
17790
|
} catch (err) {
|
|
17607
|
-
|
|
17791
|
+
logger15.debug(
|
|
17608
17792
|
`[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
|
|
17609
17793
|
);
|
|
17610
17794
|
}
|
|
@@ -17616,9 +17800,9 @@ var EngineClient = class {
|
|
|
17616
17800
|
onEngineExit(code, signal) {
|
|
17617
17801
|
const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
|
|
17618
17802
|
if (this.intentionalKill) {
|
|
17619
|
-
|
|
17803
|
+
logger15.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
|
|
17620
17804
|
} else {
|
|
17621
|
-
|
|
17805
|
+
logger15.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
|
|
17622
17806
|
}
|
|
17623
17807
|
this.teardownProcess(detail);
|
|
17624
17808
|
this.state = "exited";
|
|
@@ -17667,7 +17851,7 @@ var EngineClient = class {
|
|
|
17667
17851
|
`);
|
|
17668
17852
|
return true;
|
|
17669
17853
|
} catch (err) {
|
|
17670
|
-
|
|
17854
|
+
logger15.debug(
|
|
17671
17855
|
`[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17672
17856
|
);
|
|
17673
17857
|
return false;
|
|
@@ -18025,7 +18209,7 @@ function getHostUiRequestEndpoint() {
|
|
|
18025
18209
|
}
|
|
18026
18210
|
|
|
18027
18211
|
// src/execution/engine/engine-inspect-package.ts
|
|
18028
|
-
var
|
|
18212
|
+
var logger16 = getLogger("subagents");
|
|
18029
18213
|
function readEnginePackageJson(pkgDir) {
|
|
18030
18214
|
let raw;
|
|
18031
18215
|
try {
|
|
@@ -18104,12 +18288,12 @@ function parseOptionalDisplayFields(m, id) {
|
|
|
18104
18288
|
if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
|
|
18105
18289
|
displayName = rawDisplayName;
|
|
18106
18290
|
} else {
|
|
18107
|
-
|
|
18291
|
+
logger16.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
|
|
18108
18292
|
}
|
|
18109
18293
|
}
|
|
18110
18294
|
const rawDescription = m["description"];
|
|
18111
18295
|
if (rawDescription !== void 0 && typeof rawDescription !== "string") {
|
|
18112
|
-
|
|
18296
|
+
logger16.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
|
|
18113
18297
|
}
|
|
18114
18298
|
return displayName;
|
|
18115
18299
|
}
|
|
@@ -18198,7 +18382,7 @@ function errorMessage(err) {
|
|
|
18198
18382
|
// src/execution/engine/engine-discovery-roots.ts
|
|
18199
18383
|
var fs3 = __toESM(require("fs"), 1);
|
|
18200
18384
|
var path3 = __toESM(require("path"), 1);
|
|
18201
|
-
var
|
|
18385
|
+
var logger17 = getLogger("subagents");
|
|
18202
18386
|
var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
|
|
18203
18387
|
function parseEngineRootsEnv(env) {
|
|
18204
18388
|
const raw = env[ENGINE_ROOTS_ENV];
|
|
@@ -18209,7 +18393,7 @@ function parseEngineRootsEnv(env) {
|
|
|
18209
18393
|
const dir = part.trim();
|
|
18210
18394
|
if (dir === "") continue;
|
|
18211
18395
|
if (!path3.isAbsolute(dir)) {
|
|
18212
|
-
|
|
18396
|
+
logger17.warn(
|
|
18213
18397
|
`[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
|
|
18214
18398
|
);
|
|
18215
18399
|
continue;
|
|
@@ -18278,7 +18462,7 @@ function isFile(p) {
|
|
|
18278
18462
|
// src/execution/engine/config.ts
|
|
18279
18463
|
var fs4 = __toESM(require("fs"), 1);
|
|
18280
18464
|
var path4 = __toESM(require("path"), 1);
|
|
18281
|
-
var
|
|
18465
|
+
var logger18 = getLogger("subagents");
|
|
18282
18466
|
function readExplicitEngines(agentDir) {
|
|
18283
18467
|
const configPath = path4.join(agentDir, "subagents", "config.json");
|
|
18284
18468
|
let parsed;
|
|
@@ -18291,7 +18475,7 @@ function readExplicitEngines(agentDir) {
|
|
|
18291
18475
|
const engines = parsed["engines"];
|
|
18292
18476
|
if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
|
|
18293
18477
|
if (engines !== void 0) {
|
|
18294
|
-
|
|
18478
|
+
logger18.warn(
|
|
18295
18479
|
`[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
|
|
18296
18480
|
);
|
|
18297
18481
|
}
|
|
@@ -18319,7 +18503,7 @@ function sanitizeExplicitEntry(id, raw) {
|
|
|
18319
18503
|
}
|
|
18320
18504
|
function requireEntryObject(id, raw) {
|
|
18321
18505
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
18322
|
-
|
|
18506
|
+
logger18.warn(
|
|
18323
18507
|
`[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
|
|
18324
18508
|
);
|
|
18325
18509
|
return void 0;
|
|
@@ -18328,7 +18512,7 @@ function requireEntryObject(id, raw) {
|
|
|
18328
18512
|
}
|
|
18329
18513
|
function requireCommand(id, command) {
|
|
18330
18514
|
if (typeof command !== "string" || command.trim() === "") {
|
|
18331
|
-
|
|
18515
|
+
logger18.warn(
|
|
18332
18516
|
`[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
|
|
18333
18517
|
);
|
|
18334
18518
|
return void 0;
|
|
@@ -18337,7 +18521,7 @@ function requireCommand(id, command) {
|
|
|
18337
18521
|
}
|
|
18338
18522
|
function normalizeArgs(id, args) {
|
|
18339
18523
|
if (args !== void 0 && !isStringArray(args)) {
|
|
18340
|
-
|
|
18524
|
+
logger18.warn(
|
|
18341
18525
|
`[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
|
|
18342
18526
|
);
|
|
18343
18527
|
}
|
|
@@ -18345,7 +18529,7 @@ function normalizeArgs(id, args) {
|
|
|
18345
18529
|
}
|
|
18346
18530
|
function normalizeConfig(id, config) {
|
|
18347
18531
|
if (config !== void 0 && !isStringRecord(config)) {
|
|
18348
|
-
|
|
18532
|
+
logger18.warn(
|
|
18349
18533
|
`[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
|
|
18350
18534
|
);
|
|
18351
18535
|
}
|
|
@@ -18353,7 +18537,7 @@ function normalizeConfig(id, config) {
|
|
|
18353
18537
|
}
|
|
18354
18538
|
function normalizeCwd(id, cwd) {
|
|
18355
18539
|
if (cwd !== void 0 && typeof cwd !== "string") {
|
|
18356
|
-
|
|
18540
|
+
logger18.warn(
|
|
18357
18541
|
`[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
|
|
18358
18542
|
);
|
|
18359
18543
|
}
|
|
@@ -18373,14 +18557,14 @@ function jsonKindOf(value) {
|
|
|
18373
18557
|
}
|
|
18374
18558
|
|
|
18375
18559
|
// src/execution/engine/engine-discovery-scan.ts
|
|
18376
|
-
var
|
|
18560
|
+
var logger19 = getLogger("subagents");
|
|
18377
18561
|
function scanEngines(opts) {
|
|
18378
18562
|
const env = opts.env ?? process.env;
|
|
18379
18563
|
const result = { discovered: [], skipped: [], unusable: [] };
|
|
18380
18564
|
const present = (entry) => {
|
|
18381
18565
|
const idx = result.discovered.findIndex((d) => d.id === entry.id);
|
|
18382
18566
|
if (idx >= 0) {
|
|
18383
|
-
|
|
18567
|
+
logger19.debug(
|
|
18384
18568
|
`[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
|
|
18385
18569
|
);
|
|
18386
18570
|
result.discovered[idx] = entry;
|
|
@@ -18392,13 +18576,13 @@ function scanEngines(opts) {
|
|
|
18392
18576
|
const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
|
|
18393
18577
|
if (inspection.status === "skip") {
|
|
18394
18578
|
if (!inspection.reason.includes("not an engine package")) {
|
|
18395
|
-
|
|
18579
|
+
logger19.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
|
|
18396
18580
|
result.skipped.push({ pkgDir, reason: inspection.reason });
|
|
18397
18581
|
}
|
|
18398
18582
|
return;
|
|
18399
18583
|
}
|
|
18400
18584
|
if (inspection.status === "unusable") {
|
|
18401
|
-
|
|
18585
|
+
logger19.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
|
|
18402
18586
|
result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
|
|
18403
18587
|
return;
|
|
18404
18588
|
}
|
|
@@ -18423,7 +18607,7 @@ function scanEngines(opts) {
|
|
|
18423
18607
|
const command = resolveExplicitCommand(entry.command, env);
|
|
18424
18608
|
if (command === void 0) {
|
|
18425
18609
|
const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
|
|
18426
|
-
|
|
18610
|
+
logger19.warn(`[engine-discovery] ${reason} \u2014 not registering`);
|
|
18427
18611
|
result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
|
|
18428
18612
|
continue;
|
|
18429
18613
|
}
|
|
@@ -18440,7 +18624,7 @@ function discoverAndRegisterEngines(opts) {
|
|
|
18440
18624
|
registerEngineDescriptor(entry.id, entry.descriptor);
|
|
18441
18625
|
loadedDiscoveryIdSet.add(entry.id);
|
|
18442
18626
|
if (existed) {
|
|
18443
|
-
|
|
18627
|
+
logger19.debug(
|
|
18444
18628
|
`[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
|
|
18445
18629
|
);
|
|
18446
18630
|
}
|
|
@@ -18470,7 +18654,7 @@ function resolveExplicitCommand(command, env) {
|
|
|
18470
18654
|
}
|
|
18471
18655
|
function buildExplicitDescriptor(id, command, entry, opts) {
|
|
18472
18656
|
const env = opts.env ?? process.env;
|
|
18473
|
-
|
|
18657
|
+
logger19.debug(
|
|
18474
18658
|
`[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
|
|
18475
18659
|
);
|
|
18476
18660
|
const caps = { ...CONSERVATIVE_CAPABILITIES };
|
|
@@ -18513,7 +18697,7 @@ function buildExplicitDescriptor(id, command, entry, opts) {
|
|
|
18513
18697
|
}
|
|
18514
18698
|
|
|
18515
18699
|
// src/execution/engine/routing.ts
|
|
18516
|
-
var
|
|
18700
|
+
var logger20 = getLogger("subagents");
|
|
18517
18701
|
var RESCAN_OPTS_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/pi-subagent-workflow.engineDiscoveryRescanOpts");
|
|
18518
18702
|
function setEngineDiscoveryRescanOptions(opts) {
|
|
18519
18703
|
Reflect.set(globalThis, RESCAN_OPTS_SLOT_KEY, { current: opts });
|
|
@@ -18529,7 +18713,7 @@ function hasEngineWithRescan(id) {
|
|
|
18529
18713
|
try {
|
|
18530
18714
|
return ensureEngineDiscovered(id, opts);
|
|
18531
18715
|
} catch (err) {
|
|
18532
|
-
|
|
18716
|
+
logger20.debug(
|
|
18533
18717
|
`[engine-routing] rescan for engine '${id}' failed (treated as not discovered): ${err instanceof Error ? err.message : String(err)}`
|
|
18534
18718
|
);
|
|
18535
18719
|
return false;
|
|
@@ -18553,7 +18737,7 @@ function resolveEngineRouting(input) {
|
|
|
18553
18737
|
function resolveDefaultEngineFallback(requestedId, available) {
|
|
18554
18738
|
if (available.length === 0) return void 0;
|
|
18555
18739
|
const target = available[0];
|
|
18556
|
-
|
|
18740
|
+
logger20.warn(
|
|
18557
18741
|
`[engine-routing] default engine '${requestedId}' is not in the discovered engines [${available.join(", ")}]; falling back to first available engine '${target}' (D4, recorded via engineFallback)`
|
|
18558
18742
|
);
|
|
18559
18743
|
return { engineId: target, fallback: { from: requestedId, reason: "engine_not_found" } };
|
|
@@ -18700,7 +18884,7 @@ var SUBAGENTS_ENGINES_FILENAME = "engines.json";
|
|
|
18700
18884
|
var import_node_fs3 = require("fs");
|
|
18701
18885
|
var fsPromises = __toESM(require("fs/promises"), 1);
|
|
18702
18886
|
var path6 = __toESM(require("path"), 1);
|
|
18703
|
-
var
|
|
18887
|
+
var logger21 = getLogger("subagents");
|
|
18704
18888
|
var TMP_MARKER = ".tmp.";
|
|
18705
18889
|
var TMP_NAME_PATTERN = /^(.+)\.tmp\.(\d+)\.[0-9A-Za-z-]+$/;
|
|
18706
18890
|
var tmpSeq = 0;
|
|
@@ -18722,7 +18906,7 @@ function removeTmpBestEffortSync(tmpPath) {
|
|
|
18722
18906
|
try {
|
|
18723
18907
|
(0, import_node_fs3.unlinkSync)(tmpPath);
|
|
18724
18908
|
} catch (cleanupErr) {
|
|
18725
|
-
|
|
18909
|
+
logger21.debug("[subagent-core] atomic-write cleanup tmp failed", {
|
|
18726
18910
|
detail: toErrorMessage(cleanupErr),
|
|
18727
18911
|
tmpPath
|
|
18728
18912
|
});
|
|
@@ -18770,7 +18954,7 @@ async function writeAtomicFile(filePath, content, options = {}) {
|
|
|
18770
18954
|
await dirFh.close();
|
|
18771
18955
|
}
|
|
18772
18956
|
} catch (dirSyncErr) {
|
|
18773
|
-
|
|
18957
|
+
logger21.debug("[subagent-core] atomic-write fsync dir failed", {
|
|
18774
18958
|
detail: toErrorMessage(dirSyncErr),
|
|
18775
18959
|
dirPath
|
|
18776
18960
|
});
|
|
@@ -18781,7 +18965,7 @@ async function writeAtomicFile(filePath, content, options = {}) {
|
|
|
18781
18965
|
try {
|
|
18782
18966
|
await fsPromises.unlink(tmpPath);
|
|
18783
18967
|
} catch (cleanupErr) {
|
|
18784
|
-
|
|
18968
|
+
logger21.debug("[subagent-core] atomic-write cleanup tmp failed", {
|
|
18785
18969
|
detail: toErrorMessage(cleanupErr),
|
|
18786
18970
|
tmpPath
|
|
18787
18971
|
});
|
|
@@ -18825,7 +19009,7 @@ function cleanupStaleTmpFiles(dir, options = {}) {
|
|
|
18825
19009
|
result.removed.push(ref.tmpPath);
|
|
18826
19010
|
continue;
|
|
18827
19011
|
}
|
|
18828
|
-
|
|
19012
|
+
logger21.debug("[subagent-core] cleanupStaleTmpFiles stat failed", {
|
|
18829
19013
|
detail: toErrorMessage(statErr),
|
|
18830
19014
|
tmpPath: ref.tmpPath
|
|
18831
19015
|
});
|
|
@@ -18840,7 +19024,7 @@ function cleanupStaleTmpFiles(dir, options = {}) {
|
|
|
18840
19024
|
if (typeof unlinkErr.code === "string" && unlinkErr.code === "ENOENT") {
|
|
18841
19025
|
result.removed.push(ref.tmpPath);
|
|
18842
19026
|
} else {
|
|
18843
|
-
|
|
19027
|
+
logger21.debug("[subagent-core] cleanupStaleTmpFiles unlink failed", {
|
|
18844
19028
|
detail: toErrorMessage(unlinkErr),
|
|
18845
19029
|
tmpPath: ref.tmpPath
|
|
18846
19030
|
});
|
|
@@ -18880,78 +19064,12 @@ function syncEnginesFile(agentDir) {
|
|
|
18880
19064
|
}
|
|
18881
19065
|
}
|
|
18882
19066
|
|
|
18883
|
-
// src/execution/engine/host/spawned-children.ts
|
|
18884
|
-
var CoreSpawnedChildrenMirror = class {
|
|
18885
|
-
entries = /* @__PURE__ */ new Map();
|
|
18886
|
-
/** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
|
|
18887
|
-
register(recordId, child) {
|
|
18888
|
-
this.entries.set(recordId, {
|
|
18889
|
-
pid: child.pid,
|
|
18890
|
-
killed: child.killed,
|
|
18891
|
-
updatedAt: Date.now()
|
|
18892
|
-
});
|
|
18893
|
-
}
|
|
18894
|
-
/** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
|
|
18895
|
-
markKilled(recordId) {
|
|
18896
|
-
const entry = this.entries.get(recordId);
|
|
18897
|
-
if (entry !== void 0) {
|
|
18898
|
-
entry.killed = true;
|
|
18899
|
-
entry.updatedAt = Date.now();
|
|
18900
|
-
}
|
|
18901
|
-
}
|
|
18902
|
-
/** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
|
|
18903
|
-
killAll() {
|
|
18904
|
-
this.entries.clear();
|
|
18905
|
-
}
|
|
18906
|
-
getChildByRecord(recordId) {
|
|
18907
|
-
return this.entries.get(recordId);
|
|
18908
|
-
}
|
|
18909
|
-
/** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
|
|
18910
|
-
hasLiveProcessHandle(recordId) {
|
|
18911
|
-
const entry = this.entries.get(recordId);
|
|
18912
|
-
return entry !== void 0 && !entry.killed;
|
|
18913
|
-
}
|
|
18914
|
-
/** 快照(诊断/测试)。 */
|
|
18915
|
-
snapshot() {
|
|
18916
|
-
return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
|
|
18917
|
-
}
|
|
18918
|
-
/** 测试隔离专用(生产禁用——进程级全局状态)。 */
|
|
18919
|
-
clear() {
|
|
18920
|
-
this.entries.clear();
|
|
18921
|
-
}
|
|
18922
|
-
};
|
|
18923
|
-
var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
|
|
18924
|
-
"@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
|
|
18925
|
-
);
|
|
18926
|
-
function coreMirrorSlot() {
|
|
18927
|
-
let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
|
|
18928
|
-
if (!slot) {
|
|
18929
|
-
slot = new CoreSpawnedChildrenMirror();
|
|
18930
|
-
Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
|
|
18931
|
-
}
|
|
18932
|
-
return slot;
|
|
18933
|
-
}
|
|
18934
|
-
function registerSpawnedChildForRecord(recordId, child) {
|
|
18935
|
-
coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
|
|
18936
|
-
}
|
|
18937
|
-
function killRecordChildWithEscalation(recordId, _source) {
|
|
18938
|
-
coreMirrorSlot().markKilled(recordId);
|
|
18939
|
-
}
|
|
18940
|
-
function killAllSpawnedChildren(_signal = "SIGTERM") {
|
|
18941
|
-
const before = coreMirrorSlot().snapshot().length;
|
|
18942
|
-
coreMirrorSlot().killAll();
|
|
18943
|
-
return before;
|
|
18944
|
-
}
|
|
18945
|
-
function hasLiveProcessHandleCore(recordId) {
|
|
18946
|
-
return coreMirrorSlot().hasLiveProcessHandle(recordId);
|
|
18947
|
-
}
|
|
18948
|
-
|
|
18949
19067
|
// src/execution/engine/d8-compat.ts
|
|
18950
19068
|
var fs6 = __toESM(require("fs"), 1);
|
|
18951
19069
|
var path8 = __toESM(require("path"), 1);
|
|
18952
19070
|
var import_node_url = require("url");
|
|
18953
19071
|
var import_meta = {};
|
|
18954
|
-
var
|
|
19072
|
+
var logger22 = getLogger("subagents");
|
|
18955
19073
|
var D8_ZCODE_ENGINE_ID = "zcode";
|
|
18956
19074
|
var D8_ZSW_HOST_KIND = "zsw";
|
|
18957
19075
|
var D8_PI_HOST_KIND = "pi";
|
|
@@ -19029,7 +19147,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
|
|
|
19029
19147
|
if (hasEngine(id)) return;
|
|
19030
19148
|
const located = locateVendoredEnginePkg(id);
|
|
19031
19149
|
if (located === void 0) {
|
|
19032
|
-
|
|
19150
|
+
logger22.warn(
|
|
19033
19151
|
`[d8-compat] engine '${id}' vendored package not located (enginePkg ${enginePkgName(id)}); left unregistered \u2014 dispatch will fail with engine_not_found + recovery guidance`
|
|
19034
19152
|
);
|
|
19035
19153
|
return;
|
|
@@ -19037,7 +19155,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
|
|
|
19037
19155
|
const syntheticEnv = { ...process.env, XYZ_AGENT_DATA_DIR: engineDataDir() };
|
|
19038
19156
|
const inspection = inspectEnginePackage(located.pkgDir, "d8-compat", D8_PI_HOST_KIND, syntheticEnv);
|
|
19039
19157
|
if (inspection.status !== "ok") {
|
|
19040
|
-
|
|
19158
|
+
logger22.warn(
|
|
19041
19159
|
`[d8-compat] engine '${id}' vendored package inspection failed (${inspection.reason}); left unregistered \u2014 dispatch will fail with engine_not_found + recovery guidance`
|
|
19042
19160
|
);
|
|
19043
19161
|
return;
|
|
@@ -19047,7 +19165,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
|
|
|
19047
19165
|
function warnSourcesIgnoredOnce(deps) {
|
|
19048
19166
|
if (deps.sources === void 0 || warnedSourcesIgnored) return;
|
|
19049
19167
|
warnedSourcesIgnored = true;
|
|
19050
|
-
|
|
19168
|
+
logger22.warn(
|
|
19051
19169
|
"[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)"
|
|
19052
19170
|
);
|
|
19053
19171
|
}
|
|
@@ -19107,12 +19225,12 @@ function createZcodeEngine(deps) {
|
|
|
19107
19225
|
var PI_POOL_KEY = "shared";
|
|
19108
19226
|
var WATCHDOG_FLOOR_MINUTES = 30;
|
|
19109
19227
|
var WATCHDOG_MINUTES_PER_TURN = 5;
|
|
19110
|
-
var
|
|
19111
|
-
var
|
|
19228
|
+
var MS_PER_SECOND3 = 1e3;
|
|
19229
|
+
var SECONDS_PER_MINUTE3 = 60;
|
|
19112
19230
|
function maxTurnsToWatchdogMs(maxTurns) {
|
|
19113
19231
|
return Math.max(
|
|
19114
|
-
WATCHDOG_FLOOR_MINUTES *
|
|
19115
|
-
maxTurns * WATCHDOG_MINUTES_PER_TURN *
|
|
19232
|
+
WATCHDOG_FLOOR_MINUTES * SECONDS_PER_MINUTE3 * MS_PER_SECOND3,
|
|
19233
|
+
maxTurns * WATCHDOG_MINUTES_PER_TURN * SECONDS_PER_MINUTE3 * MS_PER_SECOND3
|
|
19116
19234
|
);
|
|
19117
19235
|
}
|
|
19118
19236
|
function resolveHostPiEnginePort(_getService) {
|
|
@@ -19250,7 +19368,7 @@ var import_node_path7 = require("path");
|
|
|
19250
19368
|
var ACTIVITY_LABEL_MAX = 60;
|
|
19251
19369
|
var TURN_SUMMARY_MAX = 80;
|
|
19252
19370
|
var TOOL_LABEL_MAX = 100;
|
|
19253
|
-
var
|
|
19371
|
+
var MS_PER_SECOND4 = 1e3;
|
|
19254
19372
|
function extractLabelFromArgs(toolName, args) {
|
|
19255
19373
|
if (typeof args !== "object" || args === null) return toolName;
|
|
19256
19374
|
const a = args;
|
|
@@ -19581,7 +19699,7 @@ function projectOutcome(record) {
|
|
|
19581
19699
|
}
|
|
19582
19700
|
function computeElapsedSeconds(record) {
|
|
19583
19701
|
const end = record.endedAt ?? Date.now();
|
|
19584
|
-
return Math.floor((end - record.startedAt) /
|
|
19702
|
+
return Math.floor((end - record.startedAt) / MS_PER_SECOND4);
|
|
19585
19703
|
}
|
|
19586
19704
|
function project(record) {
|
|
19587
19705
|
return {
|
|
@@ -19640,7 +19758,7 @@ function snapshot(record) {
|
|
|
19640
19758
|
// src/execution/engine/common/event-journal.ts
|
|
19641
19759
|
var import_promises = require("fs/promises");
|
|
19642
19760
|
var import_node_path6 = require("path");
|
|
19643
|
-
var
|
|
19761
|
+
var logger23 = getLogger("subagents");
|
|
19644
19762
|
var FLUSH_THRESHOLD_LINES = 64;
|
|
19645
19763
|
var FLUSH_THRESHOLD_BYTES = 32 * 1024;
|
|
19646
19764
|
var defaultFs = {
|
|
@@ -19749,11 +19867,11 @@ var JournalWriter = class {
|
|
|
19749
19867
|
}
|
|
19750
19868
|
};
|
|
19751
19869
|
function defaultWarn2(msg) {
|
|
19752
|
-
|
|
19870
|
+
logger23.warn(msg);
|
|
19753
19871
|
}
|
|
19754
19872
|
|
|
19755
19873
|
// src/execution/engine/common/session-view-service.ts
|
|
19756
|
-
var
|
|
19874
|
+
var logger24 = getLogger("subagents");
|
|
19757
19875
|
var DEFAULT_ENGINE_ID2 = "pi";
|
|
19758
19876
|
var OUTCOME_PLACEHOLDER_TEXT = "(no outcome recorded)";
|
|
19759
19877
|
function extractEngineId(record) {
|
|
@@ -19780,18 +19898,18 @@ function lookupNativeSessionReader(engineId) {
|
|
|
19780
19898
|
function readJournalTier(record, handle, dataDir) {
|
|
19781
19899
|
const journalPath = handle.journalPath;
|
|
19782
19900
|
if (journalPath === void 0) {
|
|
19783
|
-
|
|
19901
|
+
logger24.debug("[session-view-service] tier2 skipped: no journalPath in handle");
|
|
19784
19902
|
return void 0;
|
|
19785
19903
|
}
|
|
19786
19904
|
if (!isStrictlyUnder(resolveEnginesRoot(dataDir), journalPath)) {
|
|
19787
|
-
|
|
19905
|
+
logger24.warn(
|
|
19788
19906
|
`[session-view-service] journalPath escapes engines root, reject tier2: ${journalPath}`
|
|
19789
19907
|
);
|
|
19790
19908
|
return void 0;
|
|
19791
19909
|
}
|
|
19792
19910
|
const messages = replayEventsToHistory(replayJournal(journalPath), record);
|
|
19793
19911
|
if (messages === void 0) {
|
|
19794
|
-
|
|
19912
|
+
logger24.debug(
|
|
19795
19913
|
`[session-view-service] tier2 journal replay produced no content, degrade to outcome-only (path=${journalPath})`
|
|
19796
19914
|
);
|
|
19797
19915
|
}
|
|
@@ -19961,7 +20079,7 @@ async function readSubagentHistoryMessages(record, dataDir) {
|
|
|
19961
20079
|
if (engineId === DEFAULT_ENGINE_ID2) return [];
|
|
19962
20080
|
const handle = parseEngineHandle(record.engineHandle);
|
|
19963
20081
|
if (handle === void 0) {
|
|
19964
|
-
|
|
20082
|
+
logger24.debug(
|
|
19965
20083
|
`[session-view-service] engine '${engineId}' record has no engineHandle, degrade to outcome-only (subagentId=${record.subagentId})`
|
|
19966
20084
|
);
|
|
19967
20085
|
return outcomeOnlyMessages(record);
|
|
@@ -19971,12 +20089,12 @@ async function readSubagentHistoryMessages(record, dataDir) {
|
|
|
19971
20089
|
const native = await reader(handle, dataDir);
|
|
19972
20090
|
if (native !== void 0) {
|
|
19973
20091
|
if (native.turns.some(hasTurnContent)) return sessionViewToMessages(native, record);
|
|
19974
|
-
|
|
20092
|
+
logger24.debug(
|
|
19975
20093
|
`[session-view-service] tier1 native view has no substantive content (turns=${native.turns.length}), degrade to journal tier (subagentId=${record.subagentId})`
|
|
19976
20094
|
);
|
|
19977
20095
|
}
|
|
19978
20096
|
} else {
|
|
19979
|
-
|
|
20097
|
+
logger24.debug(
|
|
19980
20098
|
`[session-view-service] engine '${engineId}' has no native reader tier, fall through to journal tier (subagentId=${record.subagentId})`
|
|
19981
20099
|
);
|
|
19982
20100
|
}
|
|
@@ -20021,21 +20139,6 @@ var DirtyWorktreeError = class extends Error {
|
|
|
20021
20139
|
// src/execution/subagent-service.ts
|
|
20022
20140
|
var import_node_async_hooks2 = require("async_hooks");
|
|
20023
20141
|
|
|
20024
|
-
// src/shared/timer-delay.ts
|
|
20025
|
-
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
20026
|
-
function assertSafeTimerDelay(ms, source) {
|
|
20027
|
-
if (!Number.isFinite(ms)) {
|
|
20028
|
-
throw new Error(
|
|
20029
|
-
`[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.`
|
|
20030
|
-
);
|
|
20031
|
-
}
|
|
20032
|
-
if (ms > MAX_TIMER_DELAY_MS) {
|
|
20033
|
-
throw new Error(
|
|
20034
|
-
`[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.`
|
|
20035
|
-
);
|
|
20036
|
-
}
|
|
20037
|
-
}
|
|
20038
|
-
|
|
20039
20142
|
// src/execution/agent-result-mapper.ts
|
|
20040
20143
|
var TOOL_ARGS_JSON_MAX_CHARS = 500;
|
|
20041
20144
|
function mapToWorkflowAgentResult(r) {
|
|
@@ -20133,14 +20236,14 @@ function isErrnoException(err) {
|
|
|
20133
20236
|
}
|
|
20134
20237
|
|
|
20135
20238
|
// src/execution/best-effort.ts
|
|
20136
|
-
var
|
|
20239
|
+
var logger25 = getLogger("subagents");
|
|
20137
20240
|
function bestEffort(err, context, level = "debug") {
|
|
20138
20241
|
const detail = err instanceof Error ? err.message : err;
|
|
20139
20242
|
const msg = `[subagents] best-effort ${context} failed`;
|
|
20140
20243
|
if (level === "error") {
|
|
20141
|
-
|
|
20244
|
+
logger25.error(msg, { detail });
|
|
20142
20245
|
} else {
|
|
20143
|
-
|
|
20246
|
+
logger25.debug(msg, { detail });
|
|
20144
20247
|
}
|
|
20145
20248
|
}
|
|
20146
20249
|
|
|
@@ -20231,7 +20334,7 @@ var CollectCoordinator = class {
|
|
|
20231
20334
|
// src/execution/config.ts
|
|
20232
20335
|
var fs8 = __toESM(require("fs"), 1);
|
|
20233
20336
|
var path9 = __toESM(require("path"), 1);
|
|
20234
|
-
var
|
|
20337
|
+
var logger26 = getLogger("subagents");
|
|
20235
20338
|
var DEFAULT_CONFIG = {
|
|
20236
20339
|
version: 1,
|
|
20237
20340
|
maxConcurrent: 6
|
|
@@ -20274,7 +20377,7 @@ function readGlobalConfig(agentDir) {
|
|
|
20274
20377
|
}
|
|
20275
20378
|
function readFailure(configPath, err) {
|
|
20276
20379
|
const reason = err instanceof Error ? err.message : String(err);
|
|
20277
|
-
|
|
20380
|
+
logger26.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
20278
20381
|
return { status: "failed", reason };
|
|
20279
20382
|
}
|
|
20280
20383
|
function errnoCodeOf(err) {
|
|
@@ -20313,7 +20416,7 @@ function sanitizeCollectSync(value) {
|
|
|
20313
20416
|
bad.push(`totalChars=${fmt(v.totalChars)}`);
|
|
20314
20417
|
}
|
|
20315
20418
|
if (bad.length > 0) {
|
|
20316
|
-
|
|
20419
|
+
logger26.warn(
|
|
20317
20420
|
`[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`
|
|
20318
20421
|
);
|
|
20319
20422
|
}
|
|
@@ -20338,56 +20441,6 @@ function sanitizeEngineRouting(value) {
|
|
|
20338
20441
|
return typeof strict === "boolean" ? { strict } : void 0;
|
|
20339
20442
|
}
|
|
20340
20443
|
|
|
20341
|
-
// src/execution/lifecycle-manager.ts
|
|
20342
|
-
var logger26 = getLogger("subagents");
|
|
20343
|
-
var MS_PER_SECOND4 = 1e3;
|
|
20344
|
-
var SECONDS_PER_MINUTE3 = 60;
|
|
20345
|
-
var IDLE_TIMEOUT_MINUTES = 5;
|
|
20346
|
-
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE3 * MS_PER_SECOND4;
|
|
20347
|
-
function getEnvIdleTimeoutMs() {
|
|
20348
|
-
const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
|
|
20349
|
-
if (!raw) return void 0;
|
|
20350
|
-
const parsed = Number(raw);
|
|
20351
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
20352
|
-
logger26.warn(
|
|
20353
|
-
`[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`
|
|
20354
|
-
);
|
|
20355
|
-
return void 0;
|
|
20356
|
-
}
|
|
20357
|
-
return parsed;
|
|
20358
|
-
}
|
|
20359
|
-
var idleTimers = /* @__PURE__ */ new Map();
|
|
20360
|
-
function armIdleTimer(recordId, onTimeout, timeoutMs) {
|
|
20361
|
-
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
20362
|
-
disarmIdleTimer(recordId);
|
|
20363
|
-
return;
|
|
20364
|
-
}
|
|
20365
|
-
const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
20366
|
-
assertSafeTimerDelay(resolved, "idleTimeoutMs");
|
|
20367
|
-
disarmIdleTimer(recordId);
|
|
20368
|
-
const timer = setTimeout(() => {
|
|
20369
|
-
if (idleTimers.get(recordId)?.timer === timer) {
|
|
20370
|
-
idleTimers.delete(recordId);
|
|
20371
|
-
}
|
|
20372
|
-
onTimeout();
|
|
20373
|
-
}, resolved);
|
|
20374
|
-
timer.unref?.();
|
|
20375
|
-
idleTimers.set(recordId, { timer, timeoutMs: resolved });
|
|
20376
|
-
}
|
|
20377
|
-
function disarmIdleTimer(recordId) {
|
|
20378
|
-
const entry = idleTimers.get(recordId);
|
|
20379
|
-
if (!entry) {
|
|
20380
|
-
return;
|
|
20381
|
-
}
|
|
20382
|
-
clearTimeout(entry.timer);
|
|
20383
|
-
idleTimers.delete(recordId);
|
|
20384
|
-
}
|
|
20385
|
-
function hasIdleTimer(recordId) {
|
|
20386
|
-
return idleTimers.has(recordId);
|
|
20387
|
-
}
|
|
20388
|
-
var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
|
|
20389
|
-
var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND4;
|
|
20390
|
-
|
|
20391
20444
|
// src/execution/concurrency-pool.ts
|
|
20392
20445
|
var DefaultConcurrencyPool = class {
|
|
20393
20446
|
_active = 0;
|
|
@@ -21602,17 +21655,6 @@ function displayAgentName(ref) {
|
|
|
21602
21655
|
return base.endsWith(AGENT_REF_EXT) ? base.slice(0, -AGENT_REF_EXT.length) : base;
|
|
21603
21656
|
}
|
|
21604
21657
|
|
|
21605
|
-
// src/execution/lifecycle-predicates.ts
|
|
21606
|
-
function hasLiveProcessHandle(recordId) {
|
|
21607
|
-
return hasLiveProcessHandleCore(recordId);
|
|
21608
|
-
}
|
|
21609
|
-
function isIdle(record) {
|
|
21610
|
-
return hasIdleTimer(record.id);
|
|
21611
|
-
}
|
|
21612
|
-
function isResumable(record) {
|
|
21613
|
-
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
21614
|
-
}
|
|
21615
|
-
|
|
21616
21658
|
// src/execution/notifier.ts
|
|
21617
21659
|
var import_node_crypto2 = require("crypto");
|
|
21618
21660
|
|
|
@@ -23008,10 +23050,10 @@ var RecordStore = class _RecordStore {
|
|
|
23008
23050
|
return out;
|
|
23009
23051
|
}
|
|
23010
23052
|
/** 订阅变更。返回取消订阅函数。 */
|
|
23011
|
-
onChange(
|
|
23012
|
-
this.listeners.add(
|
|
23053
|
+
onChange(listener2) {
|
|
23054
|
+
this.listeners.add(listener2);
|
|
23013
23055
|
return () => {
|
|
23014
|
-
this.listeners.delete(
|
|
23056
|
+
this.listeners.delete(listener2);
|
|
23015
23057
|
};
|
|
23016
23058
|
}
|
|
23017
23059
|
/** 触发所有监听器(TUI widget/list requestRender)。dispose 后短路。
|
|
@@ -23019,8 +23061,8 @@ var RecordStore = class _RecordStore {
|
|
|
23019
23061
|
* 内存事件(register/archive)不改变磁盘文件——旧实现整体失效是全量重扫的根因。 */
|
|
23020
23062
|
notifyChange() {
|
|
23021
23063
|
if (this._disposed) return;
|
|
23022
|
-
for (const
|
|
23023
|
-
|
|
23064
|
+
for (const listener2 of this.listeners) {
|
|
23065
|
+
listener2();
|
|
23024
23066
|
}
|
|
23025
23067
|
}
|
|
23026
23068
|
/** session 结束清理。 */
|
|
@@ -25448,9 +25490,11 @@ var WorktreeRegistry = class {
|
|
|
25448
25490
|
}
|
|
25449
25491
|
/**
|
|
25450
25492
|
* proper-lockfile 直用的跨进程锁(取代已删除的共享 file-lock 包装,抽包去依赖)。
|
|
25451
|
-
*
|
|
25452
|
-
*
|
|
25453
|
-
*
|
|
25493
|
+
* 锁协议对齐现存三方同协议实现——runtime 侧 packages/runtime/src/utils/file-lock.ts
|
|
25494
|
+
* 的 withFileLockAsync(范本 pi FileAuthStorageBackend,参数对齐 proper-lockfile
|
|
25495
|
+
* 内部 retry 库)与 extension 侧 @zhushanwen/pi-file-lock:
|
|
25496
|
+
* - lockfile 路径 = <目标文件>.lock(proper-lockfile 默认,与 runtime 侧/
|
|
25497
|
+
* extension 侧同一路径才互斥)
|
|
25454
25498
|
* - realpath:false —— 目标文件不存在也可锁(realpath 默认 true 时 ENOENT)
|
|
25455
25499
|
* - stale 30s:持锁进程崩溃后锁可被夺取
|
|
25456
25500
|
* - async retries 指数退避:10 次 / factor 2 / 100ms~10s / randomize,耗尽抛
|
|
@@ -26237,7 +26281,7 @@ var SubagentService = class {
|
|
|
26237
26281
|
lookupRecordAnyState: (id) => this.lookupRecordAnyState(id),
|
|
26238
26282
|
collectRecords: (limit, statusFilter) => this.collectRecords(limit, statusFilter),
|
|
26239
26283
|
getFullRecord: (id) => this.getFullRecord(id),
|
|
26240
|
-
onChange: (
|
|
26284
|
+
onChange: (listener2) => this.onChange(listener2)
|
|
26241
26285
|
};
|
|
26242
26286
|
/** [D4 对话 action 面聚合] chat 域 message/close 消费面(壳 subagent-actions 经此访问;
|
|
26243
26287
|
* 纯委托同上。PiEngineService 适配器不经此——引擎边界走 piEngineServiceAdapter)。 */
|
|
@@ -26490,6 +26534,7 @@ var SubagentService = class {
|
|
|
26490
26534
|
this.notifyHost.emitPendingUnregister(record.id, "closed");
|
|
26491
26535
|
count++;
|
|
26492
26536
|
}
|
|
26537
|
+
notifyInFlightChanged();
|
|
26493
26538
|
return count;
|
|
26494
26539
|
}
|
|
26495
26540
|
/** SP-4: /fork 新 session 时清理旧 record。
|
|
@@ -26917,6 +26962,7 @@ var SubagentService = class {
|
|
|
26917
26962
|
);
|
|
26918
26963
|
}
|
|
26919
26964
|
disarmIdleTimer(record.id);
|
|
26965
|
+
notifyInFlightChanged();
|
|
26920
26966
|
const engine = this.resolveChatEnginePort();
|
|
26921
26967
|
if (!this.chatRoundRoutes.has(record.id)) {
|
|
26922
26968
|
this.chatRoundRoutes.set(
|
|
@@ -27050,6 +27096,7 @@ var SubagentService = class {
|
|
|
27050
27096
|
`[subagents] settled watchdog (${fire.phase}) fired for ${record.id}: ${windowDesc}, terminating (LC-1 wedge recovery)`
|
|
27051
27097
|
);
|
|
27052
27098
|
killRecordChildWithEscalation(record.id, "settled watchdog (hot path)");
|
|
27099
|
+
notifyInFlightChanged();
|
|
27053
27100
|
this.terminateChatSession(record, "cancel", "settled watchdog (hot path)");
|
|
27054
27101
|
const failedResult = {
|
|
27055
27102
|
text: "",
|
|
@@ -27162,6 +27209,7 @@ var SubagentService = class {
|
|
|
27162
27209
|
*/
|
|
27163
27210
|
async closeChatIdle(record) {
|
|
27164
27211
|
disarmIdleTimer(record.id);
|
|
27212
|
+
notifyInFlightChanged();
|
|
27165
27213
|
disarmSettledWatchdog(record.id);
|
|
27166
27214
|
disarmRoundFromProtocol(record.id);
|
|
27167
27215
|
killRecordChildWithEscalation(record.id, "closeChatIdle");
|
|
@@ -27210,6 +27258,7 @@ var SubagentService = class {
|
|
|
27210
27258
|
*/
|
|
27211
27259
|
async closeAfterRoundSettled(record) {
|
|
27212
27260
|
disarmIdleTimer(record.id);
|
|
27261
|
+
notifyInFlightChanged();
|
|
27213
27262
|
disarmSettledWatchdog(record.id);
|
|
27214
27263
|
disarmRoundFromProtocol(record.id);
|
|
27215
27264
|
killRecordChildWithEscalation(record.id, "closeAfterRoundSettled");
|
|
@@ -27286,8 +27335,8 @@ var SubagentService = class {
|
|
|
27286
27335
|
}
|
|
27287
27336
|
// ── 状态查询(TUI 调)──────────────────────────────────
|
|
27288
27337
|
/** 订阅 store 变更(widget/list requestRender)。返回取消订阅。 */
|
|
27289
|
-
onChange(
|
|
27290
|
-
return this.store.onChange(
|
|
27338
|
+
onChange(listener2) {
|
|
27339
|
+
return this.store.onChange(listener2);
|
|
27291
27340
|
}
|
|
27292
27341
|
// [D4] listRunning 已删除:零生产调用方(TUI 计数经 collectRecords / notify-host 的
|
|
27293
27342
|
// piAdapter 直调 store.listRunning 覆盖),唯一消费是初始空态单测——保留 store 层方法。
|
|
@@ -27899,6 +27948,7 @@ var SubagentService = class {
|
|
|
27899
27948
|
bestEffort(fallbackErr, "armIdleTimer fallback (chat idle phase)", "error");
|
|
27900
27949
|
}
|
|
27901
27950
|
}
|
|
27951
|
+
notifyInFlightChanged();
|
|
27902
27952
|
}
|
|
27903
27953
|
/** [W3] idle 帧锚点回填(冷续锚点 = 引擎侧会话滚动/compaction 后的最新定位)。 */
|
|
27904
27954
|
backfillChatAnchor(record, anchor) {
|
|
@@ -28045,6 +28095,7 @@ var SubagentService = class {
|
|
|
28045
28095
|
record.controller?.abort();
|
|
28046
28096
|
killRecordChildWithEscalation(record.id, "cancelBackground");
|
|
28047
28097
|
disarmIdleTimer(record.id);
|
|
28098
|
+
notifyInFlightChanged();
|
|
28048
28099
|
disarmSettledWatchdog(record.id);
|
|
28049
28100
|
disarmRoundFromProtocol(record.id);
|
|
28050
28101
|
this.unregisterChatRoundRoute(record.id);
|
|
@@ -33314,7 +33365,7 @@ function boundedPrettySerialize(value, budget) {
|
|
|
33314
33365
|
}
|
|
33315
33366
|
|
|
33316
33367
|
// src/index.ts
|
|
33317
|
-
var CORE_PACKAGE_VERSION = "0.
|
|
33368
|
+
var CORE_PACKAGE_VERSION = "0.8.0";
|
|
33318
33369
|
// Annotate the CommonJS export names for ESM import in node:
|
|
33319
33370
|
0 && (module.exports = {
|
|
33320
33371
|
AGENT_REF_EXT,
|
|
@@ -33412,6 +33463,7 @@ var CORE_PACKAGE_VERSION = "0.7.0";
|
|
|
33412
33463
|
getCachedFileContent,
|
|
33413
33464
|
getCachedParsed,
|
|
33414
33465
|
getHostServices,
|
|
33466
|
+
getInFlightSnapshot,
|
|
33415
33467
|
getLogger,
|
|
33416
33468
|
getModelConfigService,
|
|
33417
33469
|
getOrCreateChannelRegistry,
|
|
@@ -33469,6 +33521,7 @@ var CORE_PACKAGE_VERSION = "0.7.0";
|
|
|
33469
33521
|
saveWorkflow,
|
|
33470
33522
|
scheduleTimeBudget,
|
|
33471
33523
|
setEngineDiscoveryRescanOptions,
|
|
33524
|
+
setInFlightListener,
|
|
33472
33525
|
setModelConfigService,
|
|
33473
33526
|
setSubagentService,
|
|
33474
33527
|
sortByCodepoint,
|