weacpx 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -19
- package/dist/bridge/bridge-main.js +163 -7
- package/dist/cli.js +2190 -869
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -360,7 +360,7 @@ var init_config_store = __esm(() => {
|
|
|
360
360
|
|
|
361
361
|
// src/config/ensure-config.ts
|
|
362
362
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
363
|
-
async function ensureConfigExists(path2) {
|
|
363
|
+
async function ensureConfigExists(path2, options = {}) {
|
|
364
364
|
try {
|
|
365
365
|
await loadConfig(path2);
|
|
366
366
|
} catch (error) {
|
|
@@ -368,16 +368,24 @@ async function ensureConfigExists(path2) {
|
|
|
368
368
|
throw error;
|
|
369
369
|
}
|
|
370
370
|
const store = new ConfigStore(path2);
|
|
371
|
-
await store.save(await loadDefaultConfigTemplate());
|
|
371
|
+
await store.save(await loadDefaultConfigTemplate(options));
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
|
-
async function loadDefaultConfigTemplate() {
|
|
374
|
+
async function loadDefaultConfigTemplate(options = {}) {
|
|
375
|
+
if (options.readDefaultConfigTemplate) {
|
|
376
|
+
try {
|
|
377
|
+
return normalizeDefaultConfigTemplate(await options.readDefaultConfigTemplate());
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (!isMissingFileError(error)) {
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
375
384
|
const candidates = [
|
|
376
385
|
new URL("../../config.example.json", import.meta.url),
|
|
377
386
|
new URL("../config.example.json", import.meta.url)
|
|
378
387
|
];
|
|
379
388
|
let raw;
|
|
380
|
-
let lastError;
|
|
381
389
|
for (const candidate of candidates) {
|
|
382
390
|
try {
|
|
383
391
|
raw = await readFile2(candidate, "utf8");
|
|
@@ -386,11 +394,10 @@ async function loadDefaultConfigTemplate() {
|
|
|
386
394
|
if (!isMissingFileError(error)) {
|
|
387
395
|
throw error;
|
|
388
396
|
}
|
|
389
|
-
lastError = error;
|
|
390
397
|
}
|
|
391
398
|
}
|
|
392
399
|
if (!raw) {
|
|
393
|
-
|
|
400
|
+
return normalizeDefaultConfigTemplate(BUILTIN_DEFAULT_CONFIG_TEMPLATE);
|
|
394
401
|
}
|
|
395
402
|
return normalizeDefaultConfigTemplate(JSON.parse(raw));
|
|
396
403
|
}
|
|
@@ -411,9 +418,32 @@ function normalizeDefaultConfigTemplate(raw) {
|
|
|
411
418
|
function isMissingFileError(error) {
|
|
412
419
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
413
420
|
}
|
|
421
|
+
var BUILTIN_DEFAULT_CONFIG_TEMPLATE;
|
|
414
422
|
var init_ensure_config = __esm(() => {
|
|
415
423
|
init_config_store();
|
|
416
424
|
init_load_config();
|
|
425
|
+
BUILTIN_DEFAULT_CONFIG_TEMPLATE = {
|
|
426
|
+
transport: {
|
|
427
|
+
type: "acpx-bridge",
|
|
428
|
+
sessionInitTimeoutMs: 120000,
|
|
429
|
+
permissionMode: "approve-all",
|
|
430
|
+
nonInteractivePermissions: "deny"
|
|
431
|
+
},
|
|
432
|
+
logging: {
|
|
433
|
+
level: "info",
|
|
434
|
+
maxSizeBytes: 2 * 1024 * 1024,
|
|
435
|
+
maxFiles: 5,
|
|
436
|
+
retentionDays: 7
|
|
437
|
+
},
|
|
438
|
+
wechat: {
|
|
439
|
+
replyMode: "stream"
|
|
440
|
+
},
|
|
441
|
+
agents: {
|
|
442
|
+
codex: { driver: "codex" },
|
|
443
|
+
claude: { driver: "claude" }
|
|
444
|
+
},
|
|
445
|
+
workspaces: {}
|
|
446
|
+
};
|
|
417
447
|
});
|
|
418
448
|
|
|
419
449
|
// src/daemon/daemon-status.ts
|
|
@@ -7518,6 +7548,13 @@ function renderTaskHeartbeat(task, elapsedSeconds) {
|
|
|
7518
7548
|
return `⏳ 任务「${task.taskId}」已运行 ${minutes} 分钟,等待中...`;
|
|
7519
7549
|
}
|
|
7520
7550
|
|
|
7551
|
+
// src/orchestration/task-wait-timeouts.ts
|
|
7552
|
+
var DEFAULT_TASK_WAIT_TIMEOUT_MS, MAX_TASK_WAIT_TIMEOUT_MS, DEFAULT_TASK_WAIT_POLL_INTERVAL_MS = 1000, MAX_TASK_WAIT_POLL_INTERVAL_MS = 1e4, TASK_WAIT_RPC_TIMEOUT_PADDING_MS = 5000;
|
|
7553
|
+
var init_task_wait_timeouts = __esm(() => {
|
|
7554
|
+
DEFAULT_TASK_WAIT_TIMEOUT_MS = 5 * 60000;
|
|
7555
|
+
MAX_TASK_WAIT_TIMEOUT_MS = 20 * 60000;
|
|
7556
|
+
});
|
|
7557
|
+
|
|
7521
7558
|
// src/weixin/messaging/quota-errors.ts
|
|
7522
7559
|
function isQuotaDeferredError(error2) {
|
|
7523
7560
|
return error2 instanceof QuotaDeferredError;
|
|
@@ -7534,8 +7571,337 @@ var init_quota_errors = __esm(() => {
|
|
|
7534
7571
|
};
|
|
7535
7572
|
});
|
|
7536
7573
|
|
|
7574
|
+
// src/orchestration/orchestration-types.ts
|
|
7575
|
+
function createEmptyOrchestrationState() {
|
|
7576
|
+
return {
|
|
7577
|
+
tasks: {},
|
|
7578
|
+
workerBindings: {},
|
|
7579
|
+
groups: {},
|
|
7580
|
+
humanQuestionPackages: {},
|
|
7581
|
+
coordinatorQuestionState: {},
|
|
7582
|
+
coordinatorRoutes: {},
|
|
7583
|
+
externalCoordinators: {}
|
|
7584
|
+
};
|
|
7585
|
+
}
|
|
7586
|
+
|
|
7587
|
+
// src/state/types.ts
|
|
7588
|
+
function createEmptyState() {
|
|
7589
|
+
return {
|
|
7590
|
+
sessions: {},
|
|
7591
|
+
chat_contexts: {},
|
|
7592
|
+
orchestration: createEmptyOrchestrationState()
|
|
7593
|
+
};
|
|
7594
|
+
}
|
|
7595
|
+
var init_types = () => {};
|
|
7596
|
+
|
|
7597
|
+
// src/state/state-store.ts
|
|
7598
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
7599
|
+
function isRecord2(value) {
|
|
7600
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7601
|
+
}
|
|
7602
|
+
function isString(value) {
|
|
7603
|
+
return typeof value === "string";
|
|
7604
|
+
}
|
|
7605
|
+
function isOptionalString(value) {
|
|
7606
|
+
return value === undefined || typeof value === "string";
|
|
7607
|
+
}
|
|
7608
|
+
function isOptionalBoolean(value) {
|
|
7609
|
+
return value === undefined || typeof value === "boolean";
|
|
7610
|
+
}
|
|
7611
|
+
function isTaskStatus(value) {
|
|
7612
|
+
return value === "pending" || value === "needs_confirmation" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
|
|
7613
|
+
}
|
|
7614
|
+
function isSourceKind(value) {
|
|
7615
|
+
return value === "human" || value === "coordinator" || value === "worker";
|
|
7616
|
+
}
|
|
7617
|
+
function isOpenQuestionRecord(value) {
|
|
7618
|
+
if (!isRecord2(value)) {
|
|
7619
|
+
return false;
|
|
7620
|
+
}
|
|
7621
|
+
return isString(value.questionId) && isString(value.question) && isString(value.whyBlocked) && isString(value.whatIsNeeded) && isString(value.askedAt) && (value.status === "open" || value.status === "answered" || value.status === "superseded") && isOptionalString(value.answeredAt) && (value.answerSource === undefined || value.answerSource === "coordinator" || value.answerSource === "human") && isOptionalString(value.answerText) && isOptionalString(value.packageId) && isOptionalString(value.lastWakeError) && isOptionalString(value.lastResumeError);
|
|
7622
|
+
}
|
|
7623
|
+
function isReviewPendingRecord(value) {
|
|
7624
|
+
if (!isRecord2(value)) {
|
|
7625
|
+
return false;
|
|
7626
|
+
}
|
|
7627
|
+
return isString(value.reviewId) && value.reason === "misrouted_answer" && isString(value.createdAt) && isString(value.resultId) && isString(value.resultText);
|
|
7628
|
+
}
|
|
7629
|
+
function isCorrectionPendingRecord(value) {
|
|
7630
|
+
if (!isRecord2(value)) {
|
|
7631
|
+
return false;
|
|
7632
|
+
}
|
|
7633
|
+
return isString(value.requestedAt) && value.reason === "misrouted_answer";
|
|
7634
|
+
}
|
|
7635
|
+
function isTaskRecord(value) {
|
|
7636
|
+
if (!isRecord2(value)) {
|
|
7637
|
+
return false;
|
|
7638
|
+
}
|
|
7639
|
+
return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending));
|
|
7640
|
+
}
|
|
7641
|
+
function isExternalCoordinatorRecord(value) {
|
|
7642
|
+
if (!isRecord2(value)) {
|
|
7643
|
+
return false;
|
|
7644
|
+
}
|
|
7645
|
+
return isString(value.coordinatorSession) && isOptionalString(value.workspace) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.defaultTargetAgent);
|
|
7646
|
+
}
|
|
7647
|
+
function isWorkerBindingRecord(value) {
|
|
7648
|
+
if (!isRecord2(value)) {
|
|
7649
|
+
return false;
|
|
7650
|
+
}
|
|
7651
|
+
return isString(value.sourceHandle) && isString(value.coordinatorSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role);
|
|
7652
|
+
}
|
|
7653
|
+
function isGroupRecord(value) {
|
|
7654
|
+
if (!isRecord2(value)) {
|
|
7655
|
+
return false;
|
|
7656
|
+
}
|
|
7657
|
+
return isString(value.groupId) && isString(value.coordinatorSession) && isString(value.title) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.coordinatorInjectedAt) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError);
|
|
7658
|
+
}
|
|
7659
|
+
function isQueuedQuestionRecord(value) {
|
|
7660
|
+
if (!isRecord2(value)) {
|
|
7661
|
+
return false;
|
|
7662
|
+
}
|
|
7663
|
+
return isString(value.taskId) && isString(value.questionId) && isString(value.enqueuedAt);
|
|
7664
|
+
}
|
|
7665
|
+
function isCoordinatorQuestionStateRecord(value) {
|
|
7666
|
+
if (!isRecord2(value)) {
|
|
7667
|
+
return false;
|
|
7668
|
+
}
|
|
7669
|
+
const queuedQuestions = value.queuedQuestions;
|
|
7670
|
+
if (queuedQuestions !== undefined && !Array.isArray(queuedQuestions)) {
|
|
7671
|
+
return false;
|
|
7672
|
+
}
|
|
7673
|
+
return (value.activePackageId === undefined || isString(value.activePackageId)) && (queuedQuestions === undefined || queuedQuestions.every(isQueuedQuestionRecord));
|
|
7674
|
+
}
|
|
7675
|
+
function isCoordinatorRouteContextRecord(value) {
|
|
7676
|
+
if (!isRecord2(value)) {
|
|
7677
|
+
return false;
|
|
7678
|
+
}
|
|
7679
|
+
return isString(value.coordinatorSession) && isString(value.chatKey) && isOptionalString(value.accountId) && isOptionalString(value.replyContextToken) && isString(value.updatedAt);
|
|
7680
|
+
}
|
|
7681
|
+
function isHumanQuestionPackageMessageRecord(value) {
|
|
7682
|
+
if (!isRecord2(value)) {
|
|
7683
|
+
return false;
|
|
7684
|
+
}
|
|
7685
|
+
return isString(value.messageId) && (value.kind === "initial" || value.kind === "follow_up") && isString(value.promptText) && isString(value.createdAt) && isOptionalString(value.deliveredAt) && isOptionalString(value.deliveredChatKey) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.lastDeliveryError);
|
|
7686
|
+
}
|
|
7687
|
+
function isHumanQuestionPackageRecord(value) {
|
|
7688
|
+
if (!isRecord2(value)) {
|
|
7689
|
+
return false;
|
|
7690
|
+
}
|
|
7691
|
+
const initialTaskIds = value.initialTaskIds;
|
|
7692
|
+
const openTaskIds = value.openTaskIds;
|
|
7693
|
+
const resolvedTaskIds = value.resolvedTaskIds;
|
|
7694
|
+
const messages = value.messages;
|
|
7695
|
+
return isString(value.packageId) && isString(value.coordinatorSession) && (value.status === "active" || value.status === "closed") && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.closedAt) && Array.isArray(initialTaskIds) && initialTaskIds.every(isString) && Array.isArray(openTaskIds) && openTaskIds.every(isString) && Array.isArray(resolvedTaskIds) && resolvedTaskIds.every(isString) && Array.isArray(messages) && messages.every(isHumanQuestionPackageMessageRecord) && isOptionalString(value.awaitingReplyMessageId);
|
|
7696
|
+
}
|
|
7697
|
+
function parseOrchestrationState(raw, path3) {
|
|
7698
|
+
if (raw === undefined) {
|
|
7699
|
+
return createEmptyOrchestrationState();
|
|
7700
|
+
}
|
|
7701
|
+
if (!isRecord2(raw)) {
|
|
7702
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration"`);
|
|
7703
|
+
}
|
|
7704
|
+
const tasks = raw.tasks;
|
|
7705
|
+
if (tasks !== undefined && !isRecord2(tasks)) {
|
|
7706
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.tasks"`);
|
|
7707
|
+
}
|
|
7708
|
+
const workerBindings = raw.workerBindings;
|
|
7709
|
+
if (workerBindings !== undefined && !isRecord2(workerBindings)) {
|
|
7710
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.workerBindings"`);
|
|
7711
|
+
}
|
|
7712
|
+
const groups = raw.groups;
|
|
7713
|
+
if (groups !== undefined && !isRecord2(groups)) {
|
|
7714
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.groups"`);
|
|
7715
|
+
}
|
|
7716
|
+
const humanQuestionPackages = raw.humanQuestionPackages;
|
|
7717
|
+
if (humanQuestionPackages !== undefined && !isRecord2(humanQuestionPackages)) {
|
|
7718
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.humanQuestionPackages"`);
|
|
7719
|
+
}
|
|
7720
|
+
const coordinatorQuestionState = raw.coordinatorQuestionState;
|
|
7721
|
+
if (coordinatorQuestionState !== undefined && !isRecord2(coordinatorQuestionState)) {
|
|
7722
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.coordinatorQuestionState"`);
|
|
7723
|
+
}
|
|
7724
|
+
const coordinatorRoutes = raw.coordinatorRoutes;
|
|
7725
|
+
if (coordinatorRoutes !== undefined && !isRecord2(coordinatorRoutes)) {
|
|
7726
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.coordinatorRoutes"`);
|
|
7727
|
+
}
|
|
7728
|
+
const externalCoordinators = raw.externalCoordinators;
|
|
7729
|
+
if (externalCoordinators !== undefined && !isRecord2(externalCoordinators)) {
|
|
7730
|
+
throw new Error(`state file "${path3}" must contain an object field "orchestration.externalCoordinators"`);
|
|
7731
|
+
}
|
|
7732
|
+
const parsedTasks = {};
|
|
7733
|
+
for (const [taskId, task] of Object.entries(tasks ?? {})) {
|
|
7734
|
+
if (!isTaskRecord(task)) {
|
|
7735
|
+
throw new Error(`state file "${path3}" contains an invalid orchestration task at "${taskId}"`);
|
|
7736
|
+
}
|
|
7737
|
+
parsedTasks[taskId] = task;
|
|
7738
|
+
}
|
|
7739
|
+
const parsedWorkerBindings = {};
|
|
7740
|
+
for (const [workerSession, binding] of Object.entries(workerBindings ?? {})) {
|
|
7741
|
+
if (!isWorkerBindingRecord(binding)) {
|
|
7742
|
+
throw new Error(`state file "${path3}" contains an invalid orchestration worker binding at "${workerSession}"`);
|
|
7743
|
+
}
|
|
7744
|
+
parsedWorkerBindings[workerSession] = binding;
|
|
7745
|
+
}
|
|
7746
|
+
const parsedGroups = {};
|
|
7747
|
+
for (const [groupId, group] of Object.entries(groups ?? {})) {
|
|
7748
|
+
if (!isGroupRecord(group)) {
|
|
7749
|
+
throw new Error(`state file "${path3}" contains an invalid orchestration group at "${groupId}"`);
|
|
7750
|
+
}
|
|
7751
|
+
parsedGroups[groupId] = group;
|
|
7752
|
+
}
|
|
7753
|
+
const parsedHumanQuestionPackages = {};
|
|
7754
|
+
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages ?? {})) {
|
|
7755
|
+
if (!isHumanQuestionPackageRecord(packageRecord)) {
|
|
7756
|
+
throw new Error(`state file "${path3}" contains an invalid human question package at "${packageId}"`);
|
|
7757
|
+
}
|
|
7758
|
+
parsedHumanQuestionPackages[packageId] = packageRecord;
|
|
7759
|
+
}
|
|
7760
|
+
const parsedCoordinatorQuestionState = {};
|
|
7761
|
+
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState ?? {})) {
|
|
7762
|
+
if (!isCoordinatorQuestionStateRecord(questionState)) {
|
|
7763
|
+
throw new Error(`state file "${path3}" contains an invalid coordinator question state at "${coordinatorSession}"`);
|
|
7764
|
+
}
|
|
7765
|
+
parsedCoordinatorQuestionState[coordinatorSession] = {
|
|
7766
|
+
activePackageId: questionState.activePackageId,
|
|
7767
|
+
queuedQuestions: (questionState.queuedQuestions ?? []).map((question) => ({ ...question }))
|
|
7768
|
+
};
|
|
7769
|
+
}
|
|
7770
|
+
const parsedCoordinatorRoutes = {};
|
|
7771
|
+
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes ?? {})) {
|
|
7772
|
+
if (!isCoordinatorRouteContextRecord(route)) {
|
|
7773
|
+
throw new Error(`state file "${path3}" contains an invalid coordinator route at "${coordinatorSession}"`);
|
|
7774
|
+
}
|
|
7775
|
+
parsedCoordinatorRoutes[coordinatorSession] = route;
|
|
7776
|
+
}
|
|
7777
|
+
const parsedExternalCoordinators = {};
|
|
7778
|
+
for (const [coordinatorSession, externalCoordinator] of Object.entries(externalCoordinators ?? {})) {
|
|
7779
|
+
if (!isExternalCoordinatorRecord(externalCoordinator)) {
|
|
7780
|
+
throw new Error(`state file "${path3}" contains an invalid external coordinator at "${coordinatorSession}"`);
|
|
7781
|
+
}
|
|
7782
|
+
if (externalCoordinator.coordinatorSession !== coordinatorSession) {
|
|
7783
|
+
throw new Error(`state file "${path3}" contains an external coordinator key mismatch at "${coordinatorSession}"`);
|
|
7784
|
+
}
|
|
7785
|
+
parsedExternalCoordinators[coordinatorSession] = externalCoordinator;
|
|
7786
|
+
}
|
|
7787
|
+
return {
|
|
7788
|
+
tasks: parsedTasks,
|
|
7789
|
+
workerBindings: parsedWorkerBindings,
|
|
7790
|
+
groups: parsedGroups,
|
|
7791
|
+
humanQuestionPackages: parsedHumanQuestionPackages,
|
|
7792
|
+
coordinatorQuestionState: parsedCoordinatorQuestionState,
|
|
7793
|
+
coordinatorRoutes: parsedCoordinatorRoutes,
|
|
7794
|
+
externalCoordinators: parsedExternalCoordinators
|
|
7795
|
+
};
|
|
7796
|
+
}
|
|
7797
|
+
function isReplyMode(value) {
|
|
7798
|
+
return value === "stream" || value === "final" || value === "verbose";
|
|
7799
|
+
}
|
|
7800
|
+
function isSessionRecord(value) {
|
|
7801
|
+
if (!isRecord2(value)) {
|
|
7802
|
+
return false;
|
|
7803
|
+
}
|
|
7804
|
+
return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
|
|
7805
|
+
}
|
|
7806
|
+
function parseSessions(raw, path3) {
|
|
7807
|
+
const sessions = {};
|
|
7808
|
+
for (const [alias, value] of Object.entries(raw)) {
|
|
7809
|
+
if (!isSessionRecord(value)) {
|
|
7810
|
+
throw new Error(`state file "${path3}" contains malformed session record "${alias}"`);
|
|
7811
|
+
}
|
|
7812
|
+
sessions[alias] = value;
|
|
7813
|
+
}
|
|
7814
|
+
return sessions;
|
|
7815
|
+
}
|
|
7816
|
+
function isChatContextRecord(value) {
|
|
7817
|
+
return isRecord2(value) && isString(value.current_session);
|
|
7818
|
+
}
|
|
7819
|
+
function parseChatContexts(raw, path3) {
|
|
7820
|
+
const chatContexts = {};
|
|
7821
|
+
for (const [chatKey, value] of Object.entries(raw)) {
|
|
7822
|
+
if (!isChatContextRecord(value)) {
|
|
7823
|
+
throw new Error(`state file "${path3}" contains malformed chat context record "${chatKey}"`);
|
|
7824
|
+
}
|
|
7825
|
+
chatContexts[chatKey] = value;
|
|
7826
|
+
}
|
|
7827
|
+
return chatContexts;
|
|
7828
|
+
}
|
|
7829
|
+
function parseState(raw, path3) {
|
|
7830
|
+
if (!isRecord2(raw)) {
|
|
7831
|
+
throw new Error(`state file "${path3}" must contain a JSON object`);
|
|
7832
|
+
}
|
|
7833
|
+
const sessions = raw.sessions;
|
|
7834
|
+
if (!isRecord2(sessions)) {
|
|
7835
|
+
throw new Error(`state file "${path3}" must contain an object field "sessions"`);
|
|
7836
|
+
}
|
|
7837
|
+
const chatContexts = raw.chat_contexts;
|
|
7838
|
+
if (!isRecord2(chatContexts)) {
|
|
7839
|
+
throw new Error(`state file "${path3}" must contain an object field "chat_contexts"`);
|
|
7840
|
+
}
|
|
7841
|
+
const parsedSessions = parseSessions(sessions, path3);
|
|
7842
|
+
const orchestration = parseOrchestrationState(raw.orchestration, path3);
|
|
7843
|
+
validateExternalCoordinatorIdentityCollisions(parsedSessions, orchestration, path3);
|
|
7844
|
+
return {
|
|
7845
|
+
sessions: parsedSessions,
|
|
7846
|
+
chat_contexts: parseChatContexts(chatContexts, path3),
|
|
7847
|
+
orchestration
|
|
7848
|
+
};
|
|
7849
|
+
}
|
|
7850
|
+
function validateExternalCoordinatorIdentityCollisions(sessions, orchestration, path3) {
|
|
7851
|
+
for (const coordinatorSession of Object.keys(orchestration.externalCoordinators)) {
|
|
7852
|
+
if (Object.values(sessions).some((session) => session.transport_session === coordinatorSession)) {
|
|
7853
|
+
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with a logical session`);
|
|
7854
|
+
}
|
|
7855
|
+
if (orchestration.workerBindings[coordinatorSession]) {
|
|
7856
|
+
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with a worker binding`);
|
|
7857
|
+
}
|
|
7858
|
+
if (Object.values(orchestration.tasks).some((task) => task.workerSession === coordinatorSession && (!isTerminalTaskStatus(task.status) || task.reviewPending !== undefined))) {
|
|
7859
|
+
throw new Error(`state file "${path3}" contains external coordinator "${coordinatorSession}" that conflicts with an active task worker session`);
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
function isTerminalTaskStatus(status) {
|
|
7864
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
7865
|
+
}
|
|
7866
|
+
|
|
7867
|
+
class StateStore {
|
|
7868
|
+
path;
|
|
7869
|
+
constructor(path3) {
|
|
7870
|
+
this.path = path3;
|
|
7871
|
+
}
|
|
7872
|
+
async load() {
|
|
7873
|
+
try {
|
|
7874
|
+
const content = await readFile5(this.path, "utf8");
|
|
7875
|
+
if (content.trim() === "") {
|
|
7876
|
+
return createEmptyState();
|
|
7877
|
+
}
|
|
7878
|
+
let parsed;
|
|
7879
|
+
try {
|
|
7880
|
+
parsed = JSON.parse(content);
|
|
7881
|
+
} catch (error2) {
|
|
7882
|
+
throw new Error(`failed to parse state file "${this.path}"`, {
|
|
7883
|
+
cause: error2
|
|
7884
|
+
});
|
|
7885
|
+
}
|
|
7886
|
+
return parseState(parsed, this.path);
|
|
7887
|
+
} catch (error2) {
|
|
7888
|
+
if (error2.code === "ENOENT") {
|
|
7889
|
+
return createEmptyState();
|
|
7890
|
+
}
|
|
7891
|
+
throw error2;
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7894
|
+
async save(state) {
|
|
7895
|
+
await writePrivateFileAtomic(this.path, JSON.stringify(state, null, 2));
|
|
7896
|
+
}
|
|
7897
|
+
}
|
|
7898
|
+
var init_state_store = __esm(() => {
|
|
7899
|
+
init_private_file();
|
|
7900
|
+
init_types();
|
|
7901
|
+
});
|
|
7902
|
+
|
|
7537
7903
|
// src/weixin/monitor/consumer-lock.ts
|
|
7538
|
-
import { mkdir as mkdir6, open as open2, readFile as
|
|
7904
|
+
import { mkdir as mkdir6, open as open2, readFile as readFile6, rm as rm4 } from "node:fs/promises";
|
|
7539
7905
|
import { dirname as dirname6, join as join4 } from "node:path";
|
|
7540
7906
|
import { homedir as homedir3 } from "node:os";
|
|
7541
7907
|
function createWeixinConsumerLock(options = {}) {
|
|
@@ -7617,7 +7983,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
7617
7983
|
}
|
|
7618
7984
|
async function loadLockMetadata(path3) {
|
|
7619
7985
|
try {
|
|
7620
|
-
const raw = await
|
|
7986
|
+
const raw = await readFile6(path3, "utf8");
|
|
7621
7987
|
const parsed = JSON.parse(raw);
|
|
7622
7988
|
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
7623
7989
|
return null;
|
|
@@ -9752,7 +10118,7 @@ function createConversationExecutor() {
|
|
|
9752
10118
|
|
|
9753
10119
|
// src/weixin/api/types.ts
|
|
9754
10120
|
var UploadMediaType, MessageType, MessageItemType, MessageState, TypingStatus;
|
|
9755
|
-
var
|
|
10121
|
+
var init_types2 = __esm(() => {
|
|
9756
10122
|
UploadMediaType = {
|
|
9757
10123
|
IMAGE: 1,
|
|
9758
10124
|
VIDEO: 2,
|
|
@@ -9871,7 +10237,7 @@ function buildCdnUploadUrl(params) {
|
|
|
9871
10237
|
}
|
|
9872
10238
|
|
|
9873
10239
|
// src/weixin/cdn/pic-decrypt.ts
|
|
9874
|
-
async function fetchCdnBytes(url, label) {
|
|
10240
|
+
async function fetchCdnBytes(url, label, maxBytes) {
|
|
9875
10241
|
let res;
|
|
9876
10242
|
try {
|
|
9877
10243
|
res = await fetch(url);
|
|
@@ -9887,7 +10253,41 @@ async function fetchCdnBytes(url, label) {
|
|
|
9887
10253
|
logger.error(msg);
|
|
9888
10254
|
throw new Error(msg);
|
|
9889
10255
|
}
|
|
9890
|
-
|
|
10256
|
+
const contentLength = res.headers.get("content-length");
|
|
10257
|
+
if (maxBytes !== undefined && contentLength) {
|
|
10258
|
+
const parsedLength = Number(contentLength);
|
|
10259
|
+
if (Number.isFinite(parsedLength) && parsedLength > maxBytes) {
|
|
10260
|
+
await res.body?.cancel().catch(() => {});
|
|
10261
|
+
throw new Error(`${label}: CDN download exceeds ${maxBytes} bytes`);
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
if (!res.body) {
|
|
10265
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
10266
|
+
if (maxBytes !== undefined && buffer.byteLength > maxBytes) {
|
|
10267
|
+
throw new Error(`${label}: CDN download exceeds ${maxBytes} bytes`);
|
|
10268
|
+
}
|
|
10269
|
+
return buffer;
|
|
10270
|
+
}
|
|
10271
|
+
const reader = res.body.getReader();
|
|
10272
|
+
const chunks = [];
|
|
10273
|
+
let total = 0;
|
|
10274
|
+
try {
|
|
10275
|
+
while (true) {
|
|
10276
|
+
const { done, value } = await reader.read();
|
|
10277
|
+
if (done)
|
|
10278
|
+
break;
|
|
10279
|
+
const chunk = Buffer.from(value);
|
|
10280
|
+
total += chunk.byteLength;
|
|
10281
|
+
if (maxBytes !== undefined && total > maxBytes) {
|
|
10282
|
+
await reader.cancel().catch(() => {});
|
|
10283
|
+
throw new Error(`${label}: CDN download exceeds ${maxBytes} bytes`);
|
|
10284
|
+
}
|
|
10285
|
+
chunks.push(chunk);
|
|
10286
|
+
}
|
|
10287
|
+
} finally {
|
|
10288
|
+
reader.releaseLock();
|
|
10289
|
+
}
|
|
10290
|
+
return Buffer.concat(chunks, total);
|
|
9891
10291
|
}
|
|
9892
10292
|
function parseAesKey(aesKeyBase64, label) {
|
|
9893
10293
|
const decoded = Buffer.from(aesKeyBase64, "base64");
|
|
@@ -9901,20 +10301,24 @@ function parseAesKey(aesKeyBase64, label) {
|
|
|
9901
10301
|
logger.error(msg);
|
|
9902
10302
|
throw new Error(msg);
|
|
9903
10303
|
}
|
|
9904
|
-
async function downloadAndDecryptBuffer(encryptedQueryParam, aesKeyBase64, cdnBaseUrl, label, fullUrl) {
|
|
10304
|
+
async function downloadAndDecryptBuffer(encryptedQueryParam, aesKeyBase64, cdnBaseUrl, label, fullUrl, maxBytes) {
|
|
9905
10305
|
const key = parseAesKey(aesKeyBase64, label);
|
|
9906
10306
|
const url = fullUrl || buildCdnDownloadUrl(encryptedQueryParam, cdnBaseUrl);
|
|
9907
10307
|
logger.debug(`${label}: fetching url=${url}`);
|
|
9908
|
-
const
|
|
10308
|
+
const encryptedMaxBytes = maxBytes === undefined ? undefined : maxBytes + 16;
|
|
10309
|
+
const encrypted = await fetchCdnBytes(url, label, encryptedMaxBytes);
|
|
9909
10310
|
logger.debug(`${label}: downloaded ${encrypted.byteLength} bytes, decrypting`);
|
|
9910
10311
|
const decrypted = decryptAesEcb(encrypted, key);
|
|
10312
|
+
if (maxBytes !== undefined && decrypted.byteLength > maxBytes) {
|
|
10313
|
+
throw new Error(`${label}: decrypted media exceeds ${maxBytes} bytes`);
|
|
10314
|
+
}
|
|
9911
10315
|
logger.debug(`${label}: decrypted ${decrypted.length} bytes`);
|
|
9912
10316
|
return decrypted;
|
|
9913
10317
|
}
|
|
9914
|
-
async function downloadPlainCdnBuffer(encryptedQueryParam, cdnBaseUrl, label, fullUrl) {
|
|
10318
|
+
async function downloadPlainCdnBuffer(encryptedQueryParam, cdnBaseUrl, label, fullUrl, maxBytes) {
|
|
9915
10319
|
const url = fullUrl || buildCdnDownloadUrl(encryptedQueryParam, cdnBaseUrl);
|
|
9916
10320
|
logger.debug(`${label}: fetching url=${url}`);
|
|
9917
|
-
return fetchCdnBytes(url, label);
|
|
10321
|
+
return fetchCdnBytes(url, label, maxBytes);
|
|
9918
10322
|
}
|
|
9919
10323
|
var init_pic_decrypt = __esm(() => {
|
|
9920
10324
|
init_aes_ecb();
|
|
@@ -9986,20 +10390,21 @@ async function downloadMediaFromItem(item, deps) {
|
|
|
9986
10390
|
const aesKeyBase64 = img.aeskey ? Buffer.from(img.aeskey, "hex").toString("base64") : img.media.aes_key;
|
|
9987
10391
|
logger.debug(`${label} image: encrypt_query_param=${(img.media.encrypt_query_param ?? "").slice(0, 40)}... hasAesKey=${Boolean(aesKeyBase64)} aeskeySource=${img.aeskey ? "image_item.aeskey" : "media.aes_key"} full_url=${Boolean(img.media.full_url)}`);
|
|
9988
10392
|
try {
|
|
9989
|
-
const buf = aesKeyBase64 ? await downloadAndDecryptBuffer(img.media.encrypt_query_param ?? "", aesKeyBase64, cdnBaseUrl, `${label} image`, img.media.full_url) : await downloadPlainCdnBuffer(img.media.encrypt_query_param ?? "", cdnBaseUrl, `${label} image-plain`, img.media.full_url);
|
|
10393
|
+
const buf = aesKeyBase64 ? await downloadAndDecryptBuffer(img.media.encrypt_query_param ?? "", aesKeyBase64, cdnBaseUrl, `${label} image`, img.media.full_url, WEIXIN_MEDIA_MAX_BYTES) : await downloadPlainCdnBuffer(img.media.encrypt_query_param ?? "", cdnBaseUrl, `${label} image-plain`, img.media.full_url, WEIXIN_MEDIA_MAX_BYTES);
|
|
9990
10394
|
const saved = await saveMedia(buf, undefined, "inbound", WEIXIN_MEDIA_MAX_BYTES);
|
|
9991
10395
|
result.decryptedPicPath = saved.path;
|
|
9992
10396
|
logger.debug(`${label} image saved: ${saved.path}`);
|
|
9993
10397
|
} catch (err) {
|
|
9994
10398
|
logger.error(`${label} image download/decrypt failed: ${String(err)}`);
|
|
9995
10399
|
errLog(`weixin ${label} image download/decrypt failed: ${String(err)}`);
|
|
10400
|
+
throw err;
|
|
9996
10401
|
}
|
|
9997
10402
|
} else if (item.type === MessageItemType.VOICE) {
|
|
9998
10403
|
const voice = item.voice_item;
|
|
9999
10404
|
if (!voice?.media?.encrypt_query_param && !voice?.media?.full_url || !voice?.media?.aes_key)
|
|
10000
10405
|
return result;
|
|
10001
10406
|
try {
|
|
10002
|
-
const silkBuf = await downloadAndDecryptBuffer(voice.media.encrypt_query_param ?? "", voice.media.aes_key, cdnBaseUrl, `${label} voice`, voice.media.full_url);
|
|
10407
|
+
const silkBuf = await downloadAndDecryptBuffer(voice.media.encrypt_query_param ?? "", voice.media.aes_key, cdnBaseUrl, `${label} voice`, voice.media.full_url, WEIXIN_MEDIA_MAX_BYTES);
|
|
10003
10408
|
logger.debug(`${label} voice: decrypted ${silkBuf.length} bytes, attempting silk transcode`);
|
|
10004
10409
|
const wavBuf = await silkToWav(silkBuf);
|
|
10005
10410
|
if (wavBuf) {
|
|
@@ -10022,7 +10427,7 @@ async function downloadMediaFromItem(item, deps) {
|
|
|
10022
10427
|
if (!fileItem?.media?.encrypt_query_param && !fileItem?.media?.full_url || !fileItem?.media?.aes_key)
|
|
10023
10428
|
return result;
|
|
10024
10429
|
try {
|
|
10025
|
-
const buf = await downloadAndDecryptBuffer(fileItem.media.encrypt_query_param ?? "", fileItem.media.aes_key, cdnBaseUrl, `${label} file`, fileItem.media.full_url);
|
|
10430
|
+
const buf = await downloadAndDecryptBuffer(fileItem.media.encrypt_query_param ?? "", fileItem.media.aes_key, cdnBaseUrl, `${label} file`, fileItem.media.full_url, WEIXIN_MEDIA_MAX_BYTES);
|
|
10026
10431
|
const mime = getMimeFromFilename(fileItem.file_name ?? "file.bin");
|
|
10027
10432
|
const saved = await saveMedia(buf, mime, "inbound", WEIXIN_MEDIA_MAX_BYTES, fileItem.file_name ?? undefined);
|
|
10028
10433
|
result.decryptedFilePath = saved.path;
|
|
@@ -10037,7 +10442,7 @@ async function downloadMediaFromItem(item, deps) {
|
|
|
10037
10442
|
if (!videoItem?.media?.encrypt_query_param && !videoItem?.media?.full_url || !videoItem?.media?.aes_key)
|
|
10038
10443
|
return result;
|
|
10039
10444
|
try {
|
|
10040
|
-
const buf = await downloadAndDecryptBuffer(videoItem.media.encrypt_query_param ?? "", videoItem.media.aes_key, cdnBaseUrl, `${label} video`, videoItem.media.full_url);
|
|
10445
|
+
const buf = await downloadAndDecryptBuffer(videoItem.media.encrypt_query_param ?? "", videoItem.media.aes_key, cdnBaseUrl, `${label} video`, videoItem.media.full_url, WEIXIN_MEDIA_MAX_BYTES);
|
|
10041
10446
|
const saved = await saveMedia(buf, "video/mp4", "inbound", WEIXIN_MEDIA_MAX_BYTES);
|
|
10042
10447
|
result.decryptedVideoPath = saved.path;
|
|
10043
10448
|
logger.debug(`${label} video: saved to ${saved.path}`);
|
|
@@ -10054,7 +10459,7 @@ var init_media_download = __esm(() => {
|
|
|
10054
10459
|
init_mime();
|
|
10055
10460
|
init_pic_decrypt();
|
|
10056
10461
|
init_silk_transcode();
|
|
10057
|
-
|
|
10462
|
+
init_types2();
|
|
10058
10463
|
WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
|
|
10059
10464
|
});
|
|
10060
10465
|
|
|
@@ -10144,7 +10549,7 @@ var contextTokenStore;
|
|
|
10144
10549
|
var init_inbound = __esm(() => {
|
|
10145
10550
|
init_logger();
|
|
10146
10551
|
init_random();
|
|
10147
|
-
|
|
10552
|
+
init_types2();
|
|
10148
10553
|
contextTokenStore = new Map;
|
|
10149
10554
|
});
|
|
10150
10555
|
|
|
@@ -10306,7 +10711,7 @@ var init_send = __esm(() => {
|
|
|
10306
10711
|
init_api();
|
|
10307
10712
|
init_logger();
|
|
10308
10713
|
init_random();
|
|
10309
|
-
|
|
10714
|
+
init_types2();
|
|
10310
10715
|
});
|
|
10311
10716
|
|
|
10312
10717
|
// src/weixin/messaging/error-notice.ts
|
|
@@ -10468,7 +10873,7 @@ var init_upload = __esm(() => {
|
|
|
10468
10873
|
init_logger();
|
|
10469
10874
|
init_mime();
|
|
10470
10875
|
init_random();
|
|
10471
|
-
|
|
10876
|
+
init_types2();
|
|
10472
10877
|
});
|
|
10473
10878
|
|
|
10474
10879
|
// src/weixin/messaging/send-media.ts
|
|
@@ -10819,7 +11224,10 @@ function isPathInside(candidate, root) {
|
|
|
10819
11224
|
return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
|
|
10820
11225
|
}
|
|
10821
11226
|
function createSaveMediaBuffer(mediaTempDir) {
|
|
10822
|
-
return async function saveMediaBuffer(buffer, contentType, subdir,
|
|
11227
|
+
return async function saveMediaBuffer(buffer, contentType, subdir, maxBytes, originalFilename) {
|
|
11228
|
+
if (maxBytes !== undefined && buffer.byteLength > maxBytes) {
|
|
11229
|
+
throw new Error(`media exceeds ${maxBytes} bytes`);
|
|
11230
|
+
}
|
|
10823
11231
|
const dir = path9.join(resolveMediaTempDir(mediaTempDir), subdir ?? "");
|
|
10824
11232
|
await fs6.mkdir(dir, { recursive: true });
|
|
10825
11233
|
let ext = ".bin";
|
|
@@ -10834,6 +11242,33 @@ function createSaveMediaBuffer(mediaTempDir) {
|
|
|
10834
11242
|
return { path: filePath };
|
|
10835
11243
|
};
|
|
10836
11244
|
}
|
|
11245
|
+
function inboundMediaUnavailableMessage(item) {
|
|
11246
|
+
if (item.type === MessageItemType.IMAGE) {
|
|
11247
|
+
return "图片读取失败,请重试。";
|
|
11248
|
+
}
|
|
11249
|
+
return "暂不支持处理该类型消息,请发送文字或图片。";
|
|
11250
|
+
}
|
|
11251
|
+
function inboundImageFailureMessage(error2) {
|
|
11252
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
11253
|
+
return message.includes("exceeds 104857600 bytes") || message.includes("exceeds 100MB") ? "图片超过 100MB,无法处理。" : "图片读取失败,请重试。";
|
|
11254
|
+
}
|
|
11255
|
+
async function sendInboundMediaUnavailableNotice(input) {
|
|
11256
|
+
const { to, notice, contextToken, deps } = input;
|
|
11257
|
+
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
11258
|
+
if (!reserved) {
|
|
11259
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=media_unavailable chatKey=${to}`);
|
|
11260
|
+
return;
|
|
11261
|
+
}
|
|
11262
|
+
try {
|
|
11263
|
+
await sendMessageWeixin({
|
|
11264
|
+
to,
|
|
11265
|
+
text: notice,
|
|
11266
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
11267
|
+
});
|
|
11268
|
+
} catch (err) {
|
|
11269
|
+
deps.errLog(`media unavailable notice failed: ${String(err)}`);
|
|
11270
|
+
}
|
|
11271
|
+
}
|
|
10837
11272
|
function extractTextBody(itemList) {
|
|
10838
11273
|
if (!itemList?.length)
|
|
10839
11274
|
return "";
|
|
@@ -10854,13 +11289,22 @@ function getWeixinMessageTurnLane(full) {
|
|
|
10854
11289
|
const textBody = extractTextBody(full.item_list).trim().toLowerCase();
|
|
10855
11290
|
return textBody === "/cancel" || textBody === "/stop" || textBody === "/jx" ? "control" : "normal";
|
|
10856
11291
|
}
|
|
10857
|
-
function
|
|
11292
|
+
function findUnsupportedMediaItem(itemList) {
|
|
10858
11293
|
if (!itemList?.length)
|
|
10859
11294
|
return;
|
|
10860
|
-
const direct = itemList.find((item) => item.type === MessageItemType.
|
|
11295
|
+
const direct = itemList.find((item) => item.type === MessageItemType.VIDEO || item.type === MessageItemType.FILE || item.type === MessageItemType.VOICE);
|
|
10861
11296
|
if (direct)
|
|
10862
11297
|
return direct;
|
|
10863
|
-
const refItem = itemList.find((item) => item.type === MessageItemType.TEXT && item.ref_msg?.message_item && isMediaItem(item.ref_msg.message_item));
|
|
11298
|
+
const refItem = itemList.find((item) => item.type === MessageItemType.TEXT && item.ref_msg?.message_item && item.ref_msg.message_item.type !== MessageItemType.IMAGE && isMediaItem(item.ref_msg.message_item));
|
|
11299
|
+
return refItem?.ref_msg?.message_item ?? undefined;
|
|
11300
|
+
}
|
|
11301
|
+
function findImageMediaItem(itemList) {
|
|
11302
|
+
if (!itemList?.length)
|
|
11303
|
+
return;
|
|
11304
|
+
const direct = itemList.find((item) => item.type === MessageItemType.IMAGE);
|
|
11305
|
+
if (direct)
|
|
11306
|
+
return direct;
|
|
11307
|
+
const refItem = itemList.find((item) => item.type === MessageItemType.TEXT && item.ref_msg?.message_item?.type === MessageItemType.IMAGE);
|
|
10864
11308
|
return refItem?.ref_msg?.message_item ?? undefined;
|
|
10865
11309
|
}
|
|
10866
11310
|
async function handleWeixinMessageTurn(full, deps) {
|
|
@@ -10906,6 +11350,20 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
10906
11350
|
}
|
|
10907
11351
|
}).catch(() => {});
|
|
10908
11352
|
};
|
|
11353
|
+
const contextToken = full.context_token;
|
|
11354
|
+
if (contextToken) {
|
|
11355
|
+
setContextToken(deps.accountId, full.from_user_id ?? "", contextToken);
|
|
11356
|
+
}
|
|
11357
|
+
const unsupportedMediaItem = findUnsupportedMediaItem(full.item_list);
|
|
11358
|
+
if (unsupportedMediaItem) {
|
|
11359
|
+
await sendInboundMediaUnavailableNotice({
|
|
11360
|
+
to,
|
|
11361
|
+
notice: inboundMediaUnavailableMessage(unsupportedMediaItem),
|
|
11362
|
+
contextToken,
|
|
11363
|
+
deps
|
|
11364
|
+
});
|
|
11365
|
+
return;
|
|
11366
|
+
}
|
|
10909
11367
|
if (textBody.startsWith("/")) {
|
|
10910
11368
|
const shouldTypeForSlash = isClearSlashCommand(textBody);
|
|
10911
11369
|
if (shouldTypeForSlash) {
|
|
@@ -10936,14 +11394,12 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
10936
11394
|
}
|
|
10937
11395
|
}
|
|
10938
11396
|
}
|
|
10939
|
-
const contextToken = full.context_token;
|
|
10940
|
-
if (contextToken) {
|
|
10941
|
-
setContextToken(deps.accountId, full.from_user_id ?? "", contextToken);
|
|
10942
|
-
}
|
|
10943
11397
|
startTypingIndicator();
|
|
10944
11398
|
let media;
|
|
10945
|
-
|
|
11399
|
+
let inboundImagePath;
|
|
11400
|
+
const mediaItem = findImageMediaItem(full.item_list);
|
|
10946
11401
|
if (mediaItem) {
|
|
11402
|
+
let mediaUnavailableNotice;
|
|
10947
11403
|
try {
|
|
10948
11404
|
const downloaded = await downloadMediaFromItem(mediaItem, {
|
|
10949
11405
|
cdnBaseUrl: deps.cdnBaseUrl,
|
|
@@ -10953,24 +11409,24 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
10953
11409
|
label: "inbound"
|
|
10954
11410
|
});
|
|
10955
11411
|
if (downloaded.decryptedPicPath) {
|
|
11412
|
+
inboundImagePath = downloaded.decryptedPicPath;
|
|
10956
11413
|
media = { type: "image", filePath: downloaded.decryptedPicPath, mimeType: "image/*" };
|
|
10957
|
-
} else
|
|
10958
|
-
|
|
10959
|
-
} else if (downloaded.decryptedFilePath) {
|
|
10960
|
-
media = {
|
|
10961
|
-
type: "file",
|
|
10962
|
-
filePath: downloaded.decryptedFilePath,
|
|
10963
|
-
mimeType: downloaded.fileMediaType ?? "application/octet-stream"
|
|
10964
|
-
};
|
|
10965
|
-
} else if (downloaded.decryptedVoicePath) {
|
|
10966
|
-
media = {
|
|
10967
|
-
type: "audio",
|
|
10968
|
-
filePath: downloaded.decryptedVoicePath,
|
|
10969
|
-
mimeType: downloaded.voiceMediaType ?? "audio/wav"
|
|
10970
|
-
};
|
|
11414
|
+
} else {
|
|
11415
|
+
mediaUnavailableNotice = inboundMediaUnavailableMessage(mediaItem);
|
|
10971
11416
|
}
|
|
10972
11417
|
} catch (err) {
|
|
10973
11418
|
deps.errLog(`media download failed: ${String(err)}`);
|
|
11419
|
+
mediaUnavailableNotice = inboundImageFailureMessage(err);
|
|
11420
|
+
}
|
|
11421
|
+
if (!media) {
|
|
11422
|
+
await sendInboundMediaUnavailableNotice({
|
|
11423
|
+
to,
|
|
11424
|
+
notice: mediaUnavailableNotice ?? inboundMediaUnavailableMessage(mediaItem),
|
|
11425
|
+
contextToken,
|
|
11426
|
+
deps
|
|
11427
|
+
});
|
|
11428
|
+
stopTypingIndicator();
|
|
11429
|
+
return;
|
|
10974
11430
|
}
|
|
10975
11431
|
}
|
|
10976
11432
|
const sendReplySegment = async (text) => {
|
|
@@ -11108,13 +11564,18 @@ ${buildFinalHeadsUp({
|
|
|
11108
11564
|
});
|
|
11109
11565
|
}
|
|
11110
11566
|
} finally {
|
|
11567
|
+
if (inboundImagePath) {
|
|
11568
|
+
await fs6.rm(inboundImagePath, { force: true }).catch((err) => {
|
|
11569
|
+
deps.errLog(`inbound image cleanup failed: ${String(err)}`);
|
|
11570
|
+
});
|
|
11571
|
+
}
|
|
11111
11572
|
stopTypingIndicator();
|
|
11112
11573
|
}
|
|
11113
11574
|
}
|
|
11114
|
-
var MAX_FINAL_CHUNK_BYTES = 1800
|
|
11575
|
+
var MAX_FINAL_CHUNK_BYTES = 1800;
|
|
11115
11576
|
var init_handle_weixin_message_turn = __esm(() => {
|
|
11116
11577
|
init_api();
|
|
11117
|
-
|
|
11578
|
+
init_types2();
|
|
11118
11579
|
init_media_download();
|
|
11119
11580
|
init_mime();
|
|
11120
11581
|
init_inbound();
|
|
@@ -11344,7 +11805,7 @@ var init_monitor = __esm(() => {
|
|
|
11344
11805
|
init_config_cache();
|
|
11345
11806
|
init_session_guard();
|
|
11346
11807
|
init_handle_weixin_message_turn();
|
|
11347
|
-
|
|
11808
|
+
init_types2();
|
|
11348
11809
|
init_sync_buf();
|
|
11349
11810
|
init_logger();
|
|
11350
11811
|
});
|
|
@@ -11630,7 +12091,7 @@ var init_app_logger = __esm(() => {
|
|
|
11630
12091
|
});
|
|
11631
12092
|
|
|
11632
12093
|
// src/transport/acpx-session-index.ts
|
|
11633
|
-
import { readFile as
|
|
12094
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
11634
12095
|
import { homedir as homedir4 } from "node:os";
|
|
11635
12096
|
import { resolve } from "node:path";
|
|
11636
12097
|
async function resolveSessionAgentCommandFromIndex(session) {
|
|
@@ -11639,7 +12100,7 @@ async function resolveSessionAgentCommandFromIndex(session) {
|
|
|
11639
12100
|
return;
|
|
11640
12101
|
}
|
|
11641
12102
|
try {
|
|
11642
|
-
const raw = await
|
|
12103
|
+
const raw = await readFile7(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
|
|
11643
12104
|
const parsed = JSON.parse(raw);
|
|
11644
12105
|
const targetCwd = resolve(session.cwd);
|
|
11645
12106
|
const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
|
|
@@ -12853,50 +13314,60 @@ async function handleSessions(context, chatKey) {
|
|
|
12853
13314
|
}
|
|
12854
13315
|
async function handleSessionNew(context, chatKey, alias, agent, workspace) {
|
|
12855
13316
|
const session = context.lifecycle.resolveSession(alias, agent, workspace, `${workspace}:${alias}`);
|
|
13317
|
+
const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session.transportSession);
|
|
12856
13318
|
try {
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
13319
|
+
try {
|
|
13320
|
+
await context.lifecycle.ensureTransportSession(session);
|
|
13321
|
+
const exists = await context.lifecycle.checkTransportSession(session);
|
|
13322
|
+
if (!exists) {
|
|
13323
|
+
return context.recovery.renderSessionCreationVerificationError(session);
|
|
13324
|
+
}
|
|
13325
|
+
} catch (error2) {
|
|
13326
|
+
return context.recovery.renderSessionCreationError(session, error2);
|
|
12861
13327
|
}
|
|
12862
|
-
|
|
12863
|
-
|
|
13328
|
+
await context.sessions.attachSession(alias, agent, workspace, session.transportSession);
|
|
13329
|
+
await context.lifecycle.refreshSessionTransportAgentCommand(alias);
|
|
13330
|
+
await context.sessions.useSession(chatKey, alias);
|
|
13331
|
+
await context.logger.info("session.created", "created and selected logical session", {
|
|
13332
|
+
alias,
|
|
13333
|
+
agent,
|
|
13334
|
+
workspace
|
|
13335
|
+
});
|
|
13336
|
+
return { text: `会话「${alias}」已创建并切换` };
|
|
13337
|
+
} finally {
|
|
13338
|
+
await releaseTransportReservation();
|
|
12864
13339
|
}
|
|
12865
|
-
await context.sessions.attachSession(alias, agent, workspace, session.transportSession);
|
|
12866
|
-
await context.lifecycle.refreshSessionTransportAgentCommand(alias);
|
|
12867
|
-
await context.sessions.useSession(chatKey, alias);
|
|
12868
|
-
await context.logger.info("session.created", "created and selected logical session", {
|
|
12869
|
-
alias,
|
|
12870
|
-
agent,
|
|
12871
|
-
workspace
|
|
12872
|
-
});
|
|
12873
|
-
return { text: `会话「${alias}」已创建并切换` };
|
|
12874
13340
|
}
|
|
12875
13341
|
async function handleSessionShortcut(context, chatKey, agent, target, createNew) {
|
|
12876
13342
|
return await context.lifecycle.handleSessionShortcut(chatKey, agent, target, createNew);
|
|
12877
13343
|
}
|
|
12878
13344
|
async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
|
|
12879
13345
|
const attached = context.lifecycle.resolveSession(alias, agent, workspace, transportSession);
|
|
12880
|
-
const
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
13346
|
+
const releaseTransportReservation = await context.lifecycle.reserveTransportSession(attached.transportSession);
|
|
13347
|
+
try {
|
|
13348
|
+
const exists = await context.lifecycle.checkTransportSession(attached);
|
|
13349
|
+
if (!exists) {
|
|
13350
|
+
return {
|
|
13351
|
+
text: [
|
|
13352
|
+
"没有找到可绑定的已有会话。",
|
|
13353
|
+
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent} --ws ${workspace} --name <会话名>`
|
|
13354
|
+
].join(`
|
|
12887
13355
|
`)
|
|
12888
|
-
|
|
13356
|
+
};
|
|
13357
|
+
}
|
|
13358
|
+
await context.sessions.attachSession(alias, agent, workspace, transportSession);
|
|
13359
|
+
await context.lifecycle.refreshSessionTransportAgentCommand(alias);
|
|
13360
|
+
await context.sessions.useSession(chatKey, alias);
|
|
13361
|
+
await context.logger.info("session.attached", "attached existing transport session", {
|
|
13362
|
+
alias,
|
|
13363
|
+
agent,
|
|
13364
|
+
workspace,
|
|
13365
|
+
transportSession
|
|
13366
|
+
});
|
|
13367
|
+
return { text: `会话「${alias}」已绑定并切换` };
|
|
13368
|
+
} finally {
|
|
13369
|
+
await releaseTransportReservation();
|
|
12889
13370
|
}
|
|
12890
|
-
await context.sessions.attachSession(alias, agent, workspace, transportSession);
|
|
12891
|
-
await context.lifecycle.refreshSessionTransportAgentCommand(alias);
|
|
12892
|
-
await context.sessions.useSession(chatKey, alias);
|
|
12893
|
-
await context.logger.info("session.attached", "attached existing transport session", {
|
|
12894
|
-
alias,
|
|
12895
|
-
agent,
|
|
12896
|
-
workspace,
|
|
12897
|
-
transportSession
|
|
12898
|
-
});
|
|
12899
|
-
return { text: `会话「${alias}」已绑定并切换` };
|
|
12900
13371
|
}
|
|
12901
13372
|
async function handleSessionUse(context, chatKey, alias) {
|
|
12902
13373
|
await context.sessions.useSession(chatKey, alias);
|
|
@@ -13069,7 +13540,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
13069
13540
|
return { text: lines.join(`
|
|
13070
13541
|
`) };
|
|
13071
13542
|
}
|
|
13072
|
-
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId) {
|
|
13543
|
+
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media) {
|
|
13073
13544
|
const effectiveReplyMode = session.replyMode ?? context.config?.wechat.replyMode ?? "verbose";
|
|
13074
13545
|
const transportReply = effectiveReplyMode !== "final" ? reply : undefined;
|
|
13075
13546
|
if (context.orchestration) {
|
|
@@ -13092,7 +13563,7 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
13092
13563
|
const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
|
|
13093
13564
|
try {
|
|
13094
13565
|
const replyContext = transportReply && context.quota ? { chatKey, quota: context.quota } : undefined;
|
|
13095
|
-
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext);
|
|
13566
|
+
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media);
|
|
13096
13567
|
if (claimHumanReply) {
|
|
13097
13568
|
try {
|
|
13098
13569
|
await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
|
|
@@ -13112,17 +13583,17 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
13112
13583
|
throw error2;
|
|
13113
13584
|
}
|
|
13114
13585
|
}
|
|
13115
|
-
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId) {
|
|
13586
|
+
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media) {
|
|
13116
13587
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
13117
13588
|
if (!session) {
|
|
13118
13589
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
13119
13590
|
}
|
|
13120
13591
|
try {
|
|
13121
|
-
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId);
|
|
13592
|
+
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media);
|
|
13122
13593
|
} catch (error2) {
|
|
13123
13594
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
|
|
13124
13595
|
if (recovered) {
|
|
13125
|
-
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId);
|
|
13596
|
+
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media);
|
|
13126
13597
|
}
|
|
13127
13598
|
return context.recovery.renderTransportError(session, error2);
|
|
13128
13599
|
}
|
|
@@ -13850,34 +14321,39 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target
|
|
|
13850
14321
|
};
|
|
13851
14322
|
}
|
|
13852
14323
|
const session = ops.resolveSession(alias, agent, workspace.name, alias);
|
|
14324
|
+
const releaseTransportReservation = await ops.reserveTransportSession(session.transportSession);
|
|
13853
14325
|
try {
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
14326
|
+
try {
|
|
14327
|
+
await ops.ensureTransportSession(session);
|
|
14328
|
+
const exists = await ops.checkTransportSession(session);
|
|
14329
|
+
if (!exists) {
|
|
14330
|
+
return renderShortcutSessionCreationError(workspace, alias);
|
|
14331
|
+
}
|
|
14332
|
+
} catch (err) {
|
|
14333
|
+
if (err instanceof AutoInstallFailedError)
|
|
14334
|
+
throw err;
|
|
13857
14335
|
return renderShortcutSessionCreationError(workspace, alias);
|
|
13858
14336
|
}
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
text: [
|
|
13875
|
-
`已创建并切换到会话「${alias}」`,
|
|
13876
|
-
workspace.reused ? `- 复用工作区:${workspace.name}` : `- 新增工作区:${workspace.name} -> ${workspace.cwd}`,
|
|
13877
|
-
`- 新增会话:${alias}`
|
|
13878
|
-
].join(`
|
|
14337
|
+
await context.sessions.attachSession(alias, agent, workspace.name, session.transportSession);
|
|
14338
|
+
await ops.refreshSessionTransportAgentCommand(alias);
|
|
14339
|
+
await context.sessions.useSession(chatKey, alias);
|
|
14340
|
+
await context.logger.info("session.shortcut.created", "created new logical session from shortcut", {
|
|
14341
|
+
alias,
|
|
14342
|
+
workspace: workspace.name,
|
|
14343
|
+
agent,
|
|
14344
|
+
workspaceReused: workspace.reused
|
|
14345
|
+
});
|
|
14346
|
+
return {
|
|
14347
|
+
text: [
|
|
14348
|
+
`已创建并切换到会话「${alias}」`,
|
|
14349
|
+
workspace.reused ? `- 复用工作区:${workspace.name}` : `- 新增工作区:${workspace.name} -> ${workspace.cwd}`,
|
|
14350
|
+
`- 新增会话:${alias}`
|
|
14351
|
+
].join(`
|
|
13879
14352
|
`)
|
|
13880
|
-
|
|
14353
|
+
};
|
|
14354
|
+
} finally {
|
|
14355
|
+
await releaseTransportReservation();
|
|
14356
|
+
}
|
|
13881
14357
|
}
|
|
13882
14358
|
async function resolveShortcutWorkspace(context, target) {
|
|
13883
14359
|
if (target.workspace) {
|
|
@@ -14357,31 +14833,36 @@ async function handleSessionResetCommand(context, ops, chatKey) {
|
|
|
14357
14833
|
return { text: NO_CURRENT_SESSION_TEXT3 };
|
|
14358
14834
|
}
|
|
14359
14835
|
const resetSession = ops.resolveSession(session.alias, session.agent, session.workspace, buildResetTransportSessionName(session, ops.now()));
|
|
14836
|
+
const releaseTransportReservation = await ops.reserveTransportSession(resetSession.transportSession);
|
|
14360
14837
|
try {
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14838
|
+
try {
|
|
14839
|
+
await ops.ensureTransportSession(resetSession);
|
|
14840
|
+
const exists = await ops.checkTransportSession(resetSession);
|
|
14841
|
+
if (!exists) {
|
|
14842
|
+
return {
|
|
14843
|
+
text: [
|
|
14844
|
+
`会话「${session.alias}」重置失败。`,
|
|
14845
|
+
"新的后端会话未创建成功,请稍后重试。"
|
|
14846
|
+
].join(`
|
|
14369
14847
|
`)
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14848
|
+
};
|
|
14849
|
+
}
|
|
14850
|
+
} catch (error2) {
|
|
14851
|
+
return renderTransportError(resetSession, error2);
|
|
14852
|
+
}
|
|
14853
|
+
await context.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
|
|
14854
|
+
await ops.refreshSessionTransportAgentCommand(resetSession.alias);
|
|
14855
|
+
await context.sessions.useSession(chatKey, resetSession.alias);
|
|
14856
|
+
await context.logger.info("session.reset", "reset current logical session", {
|
|
14857
|
+
alias: resetSession.alias,
|
|
14858
|
+
agent: resetSession.agent,
|
|
14859
|
+
workspace: resetSession.workspace,
|
|
14860
|
+
transportSession: resetSession.transportSession,
|
|
14861
|
+
chatKey
|
|
14862
|
+
});
|
|
14863
|
+
} finally {
|
|
14864
|
+
await releaseTransportReservation();
|
|
14865
|
+
}
|
|
14385
14866
|
return { text: `会话「${resetSession.alias}」已重置` };
|
|
14386
14867
|
}
|
|
14387
14868
|
function buildResetTransportSessionName(session, now) {
|
|
@@ -14420,7 +14901,7 @@ class CommandRouter {
|
|
|
14420
14901
|
this.quota = quota;
|
|
14421
14902
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
14422
14903
|
}
|
|
14423
|
-
async handle(chatKey, input, reply, replyContextToken, accountId) {
|
|
14904
|
+
async handle(chatKey, input, reply, replyContextToken, accountId, media) {
|
|
14424
14905
|
const startedAt = Date.now();
|
|
14425
14906
|
const command = parseCommand(input);
|
|
14426
14907
|
await this.logger.debug("command.parsed", "parsed inbound command", {
|
|
@@ -14523,7 +15004,7 @@ class CommandRouter {
|
|
|
14523
15004
|
case "task.cancel":
|
|
14524
15005
|
return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
|
|
14525
15006
|
case "prompt":
|
|
14526
|
-
return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId);
|
|
15007
|
+
return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media);
|
|
14527
15008
|
}
|
|
14528
15009
|
});
|
|
14529
15010
|
}
|
|
@@ -14555,6 +15036,7 @@ class CommandRouter {
|
|
|
14555
15036
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
14556
15037
|
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
14557
15038
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
15039
|
+
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
14558
15040
|
handleSessionShortcut: async (chatKey, agent, target, createNew, replyOverride) => {
|
|
14559
15041
|
try {
|
|
14560
15042
|
return await handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(replyOverride ?? reply), chatKey, agent, target, createNew);
|
|
@@ -14574,7 +15056,7 @@ class CommandRouter {
|
|
|
14574
15056
|
return {
|
|
14575
15057
|
setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
|
|
14576
15058
|
cancelTransportSession: (session) => this.cancelTransportSession(session),
|
|
14577
|
-
promptTransportSession: (session, text, reply, replyContext) => this.promptTransportSession(session, text, reply, replyContext)
|
|
15059
|
+
promptTransportSession: (session, text, reply, replyContext, media) => this.promptTransportSession(session, text, reply, replyContext, media)
|
|
14578
15060
|
};
|
|
14579
15061
|
}
|
|
14580
15062
|
createSessionRenderRecoveryOps() {
|
|
@@ -14589,6 +15071,7 @@ class CommandRouter {
|
|
|
14589
15071
|
return {
|
|
14590
15072
|
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
14591
15073
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
15074
|
+
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
14592
15075
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
14593
15076
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias),
|
|
14594
15077
|
now: () => Date.now()
|
|
@@ -14606,9 +15089,16 @@ class CommandRouter {
|
|
|
14606
15089
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
14607
15090
|
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
14608
15091
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
15092
|
+
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
14609
15093
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
14610
15094
|
};
|
|
14611
15095
|
}
|
|
15096
|
+
async reserveLogicalTransportSession(transportSession) {
|
|
15097
|
+
if (this.orchestration?.reserveLogicalTransportSession) {
|
|
15098
|
+
return await this.orchestration.reserveLogicalTransportSession(transportSession);
|
|
15099
|
+
}
|
|
15100
|
+
return async () => {};
|
|
15101
|
+
}
|
|
14612
15102
|
replaceConfig(updated) {
|
|
14613
15103
|
if (!this.config) {
|
|
14614
15104
|
return;
|
|
@@ -14722,9 +15212,9 @@ class CommandRouter {
|
|
|
14722
15212
|
async checkTransportSession(session) {
|
|
14723
15213
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
14724
15214
|
}
|
|
14725
|
-
async promptTransportSession(session, text, reply, replyContext) {
|
|
15215
|
+
async promptTransportSession(session, text, reply, replyContext, media) {
|
|
14726
15216
|
session.mcpCoordinatorSession ??= session.transportSession;
|
|
14727
|
-
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext));
|
|
15217
|
+
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, media ? { media } : undefined));
|
|
14728
15218
|
}
|
|
14729
15219
|
async setModeTransportSession(session, modeId) {
|
|
14730
15220
|
return await this.measureTransportCall("set_mode", session, () => this.transport.setMode(session, modeId));
|
|
@@ -14852,15 +15342,21 @@ class ConsoleAgent {
|
|
|
14852
15342
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
14853
15343
|
}
|
|
14854
15344
|
async chat(request) {
|
|
14855
|
-
|
|
15345
|
+
const hasText = request.text.trim().length > 0;
|
|
15346
|
+
if (!hasText && !request.media) {
|
|
14856
15347
|
return { text: "消息内容为空。" };
|
|
14857
15348
|
}
|
|
15349
|
+
if (request.media && request.media.type !== "image") {
|
|
15350
|
+
return {
|
|
15351
|
+
text: hasText ? "暂不支持处理该类型附件;请发送文字或图片。" : "暂不支持处理该类型消息,请发送文字或图片。"
|
|
15352
|
+
};
|
|
15353
|
+
}
|
|
14858
15354
|
await this.logger.info("chat.received", "received inbound chat message", {
|
|
14859
15355
|
chatKey: request.conversationId,
|
|
14860
15356
|
kind: request.text.trim().startsWith("/") ? "command" : "prompt",
|
|
14861
15357
|
text: summarizeText(request.text)
|
|
14862
15358
|
});
|
|
14863
|
-
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId);
|
|
15359
|
+
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, request.media);
|
|
14864
15360
|
}
|
|
14865
15361
|
async clearSession(conversationId) {
|
|
14866
15362
|
await this.router.clearSession?.(conversationId);
|
|
@@ -14877,6 +15373,24 @@ var init_console_agent = __esm(() => {
|
|
|
14877
15373
|
init_app_logger();
|
|
14878
15374
|
});
|
|
14879
15375
|
|
|
15376
|
+
// src/orchestration/async-mutex.ts
|
|
15377
|
+
class AsyncMutex {
|
|
15378
|
+
tail = Promise.resolve();
|
|
15379
|
+
async run(critical) {
|
|
15380
|
+
const previous = this.tail;
|
|
15381
|
+
let release;
|
|
15382
|
+
this.tail = new Promise((resolve2) => {
|
|
15383
|
+
release = resolve2;
|
|
15384
|
+
});
|
|
15385
|
+
await previous;
|
|
15386
|
+
try {
|
|
15387
|
+
return await critical();
|
|
15388
|
+
} finally {
|
|
15389
|
+
release();
|
|
15390
|
+
}
|
|
15391
|
+
}
|
|
15392
|
+
}
|
|
15393
|
+
|
|
14880
15394
|
// src/orchestration/orchestration-server.ts
|
|
14881
15395
|
import { rm as rm6 } from "node:fs/promises";
|
|
14882
15396
|
import { createConnection as createConnection2, createServer } from "node:net";
|
|
@@ -14976,37 +15490,52 @@ class OrchestrationServer {
|
|
|
14976
15490
|
}
|
|
14977
15491
|
async dispatch(method, params) {
|
|
14978
15492
|
switch (method) {
|
|
15493
|
+
case "coordinator.register_external":
|
|
15494
|
+
return await this.handlers.registerExternalCoordinator(this.parseRegisterExternalCoordinatorInput(params));
|
|
14979
15495
|
case "delegate.request":
|
|
14980
|
-
return await this.handlers.requestDelegate(params);
|
|
15496
|
+
return await this.handlers.requestDelegate(this.parseRequestDelegateRpcInput(params));
|
|
14981
15497
|
case "task.get":
|
|
14982
15498
|
return await this.dispatchTaskGet(params);
|
|
14983
15499
|
case "task.list":
|
|
14984
|
-
return await this.handlers.listTasks(
|
|
15500
|
+
return await this.handlers.listTasks(this.parseTaskListFilter(params));
|
|
15501
|
+
case "task.wait":
|
|
15502
|
+
return await this.handlers.waitTask(this.parseWaitTaskInput(params));
|
|
14985
15503
|
case "task.approve":
|
|
15504
|
+
requireOnlyKeys(params, ["taskId", "coordinatorSession"], "params");
|
|
14986
15505
|
return await this.handlers.approveTask({
|
|
14987
15506
|
taskId: requireString(params, "taskId"),
|
|
14988
15507
|
coordinatorSession: requireString(params, "coordinatorSession")
|
|
14989
15508
|
});
|
|
14990
15509
|
case "task.reject":
|
|
15510
|
+
requireOnlyKeys(params, ["taskId", "coordinatorSession"], "params");
|
|
14991
15511
|
return await this.handlers.rejectTask({
|
|
14992
15512
|
taskId: requireString(params, "taskId"),
|
|
14993
15513
|
coordinatorSession: requireString(params, "coordinatorSession")
|
|
14994
15514
|
});
|
|
14995
15515
|
case "task.cancel":
|
|
14996
|
-
return await this.handlers.cancelTask(params);
|
|
15516
|
+
return await this.handlers.cancelTask(this.parseCancelTaskInput(params));
|
|
14997
15517
|
case "worker.reply":
|
|
14998
|
-
await this.handlers.recordWorkerReply(params);
|
|
15518
|
+
await this.handlers.recordWorkerReply(this.parseRecordWorkerReplyInput(params));
|
|
14999
15519
|
return { accepted: true };
|
|
15000
15520
|
case "worker.raise_question":
|
|
15001
15521
|
return await this.handlers.workerRaiseQuestion(this.parseWorkerRaiseQuestionInput(params));
|
|
15002
15522
|
case "coordinator.answer_question":
|
|
15523
|
+
requireOnlyKeys(params, ["coordinatorSession", "taskId", "questionId", "answer"], "params");
|
|
15003
15524
|
return await this.handlers.coordinatorAnswerQuestion({
|
|
15004
15525
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15005
15526
|
taskId: requireString(params, "taskId"),
|
|
15006
15527
|
questionId: requireString(params, "questionId"),
|
|
15007
15528
|
answer: requireString(params, "answer")
|
|
15008
15529
|
});
|
|
15530
|
+
case "coordinator.retract_answer":
|
|
15531
|
+
requireOnlyKeys(params, ["coordinatorSession", "taskId", "questionId"], "params");
|
|
15532
|
+
return await this.handlers.coordinatorRetractAnswer({
|
|
15533
|
+
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15534
|
+
taskId: requireString(params, "taskId"),
|
|
15535
|
+
questionId: requireString(params, "questionId")
|
|
15536
|
+
});
|
|
15009
15537
|
case "coordinator.request_human_input": {
|
|
15538
|
+
requireOnlyKeys(params, ["coordinatorSession", "taskQuestions", "promptText", "expectedActivePackageId"], "params");
|
|
15010
15539
|
const expectedActivePackageId = requireOptionalString(params, "expectedActivePackageId");
|
|
15011
15540
|
return await this.handlers.coordinatorRequestHumanInput({
|
|
15012
15541
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
@@ -15016,6 +15545,7 @@ class OrchestrationServer {
|
|
|
15016
15545
|
});
|
|
15017
15546
|
}
|
|
15018
15547
|
case "coordinator.follow_up_human_package":
|
|
15548
|
+
requireOnlyKeys(params, ["coordinatorSession", "packageId", "priorMessageId", "taskQuestions", "promptText"], "params");
|
|
15019
15549
|
return await this.handlers.coordinatorFollowUpHumanPackage({
|
|
15020
15550
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15021
15551
|
packageId: requireString(params, "packageId"),
|
|
@@ -15024,6 +15554,7 @@ class OrchestrationServer {
|
|
|
15024
15554
|
promptText: requireString(params, "promptText")
|
|
15025
15555
|
});
|
|
15026
15556
|
case "coordinator.review_contested_result":
|
|
15557
|
+
requireOnlyKeys(params, ["coordinatorSession", "taskId", "reviewId", "decision"], "params");
|
|
15027
15558
|
return await this.handlers.coordinatorReviewContestedResult({
|
|
15028
15559
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15029
15560
|
taskId: requireString(params, "taskId"),
|
|
@@ -15031,11 +15562,13 @@ class OrchestrationServer {
|
|
|
15031
15562
|
decision: requireEnum(params, "decision", ["accept", "discard"])
|
|
15032
15563
|
});
|
|
15033
15564
|
case "group.new":
|
|
15565
|
+
requireOnlyKeys(params, ["coordinatorSession", "title"], "params");
|
|
15034
15566
|
return await this.handlers.createGroup({
|
|
15035
15567
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15036
15568
|
title: requireString(params, "title")
|
|
15037
15569
|
});
|
|
15038
15570
|
case "group.get":
|
|
15571
|
+
requireOnlyKeys(params, ["coordinatorSession", "groupId"], "params");
|
|
15039
15572
|
return await this.handlers.getGroupSummary({
|
|
15040
15573
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15041
15574
|
groupId: requireString(params, "groupId")
|
|
@@ -15043,6 +15576,7 @@ class OrchestrationServer {
|
|
|
15043
15576
|
case "group.list":
|
|
15044
15577
|
return await this.handlers.listGroupSummaries(this.parseGroupListFilter(params));
|
|
15045
15578
|
case "group.cancel":
|
|
15579
|
+
requireOnlyKeys(params, ["coordinatorSession", "groupId"], "params");
|
|
15046
15580
|
return await this.handlers.cancelGroup({
|
|
15047
15581
|
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15048
15582
|
groupId: requireString(params, "groupId")
|
|
@@ -15051,19 +15585,110 @@ class OrchestrationServer {
|
|
|
15051
15585
|
throw new OrchestrationInvalidRequestError(`unsupported orchestration method: ${method}`);
|
|
15052
15586
|
}
|
|
15053
15587
|
}
|
|
15588
|
+
parseRegisterExternalCoordinatorInput(params) {
|
|
15589
|
+
requireOnlyKeys(params, ["coordinatorSession", "workspace", "defaultTargetAgent"], "params");
|
|
15590
|
+
const workspace = requireOptionalString(params, "workspace");
|
|
15591
|
+
const defaultTargetAgent = requireOptionalString(params, "defaultTargetAgent");
|
|
15592
|
+
return {
|
|
15593
|
+
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15594
|
+
...workspace !== undefined ? { workspace } : {},
|
|
15595
|
+
...defaultTargetAgent !== undefined ? { defaultTargetAgent } : {}
|
|
15596
|
+
};
|
|
15597
|
+
}
|
|
15054
15598
|
async dispatchTaskGet(params) {
|
|
15599
|
+
requireOnlyKeys(params, ["taskId", "coordinatorSession"], "params");
|
|
15055
15600
|
const taskId = requireString(params, "taskId");
|
|
15056
|
-
const coordinatorSession =
|
|
15601
|
+
const coordinatorSession = requireString(params, "coordinatorSession");
|
|
15057
15602
|
const task = await this.handlers.getTask(taskId);
|
|
15058
15603
|
if (!task) {
|
|
15059
15604
|
return null;
|
|
15060
15605
|
}
|
|
15061
|
-
if (
|
|
15606
|
+
if (task.coordinatorSession !== coordinatorSession) {
|
|
15062
15607
|
return null;
|
|
15063
15608
|
}
|
|
15064
15609
|
return task;
|
|
15065
15610
|
}
|
|
15611
|
+
parseRequestDelegateRpcInput(params) {
|
|
15612
|
+
requireOnlyKeys(params, ["sourceHandle", "targetAgent", "task", "cwd", "role", "groupId"], "params");
|
|
15613
|
+
const cwd = requireOptionalString(params, "cwd");
|
|
15614
|
+
const role = requireOptionalString(params, "role");
|
|
15615
|
+
const groupId = requireOptionalString(params, "groupId");
|
|
15616
|
+
return {
|
|
15617
|
+
sourceHandle: requireString(params, "sourceHandle"),
|
|
15618
|
+
targetAgent: requireString(params, "targetAgent"),
|
|
15619
|
+
task: requireString(params, "task"),
|
|
15620
|
+
...cwd !== undefined ? { cwd } : {},
|
|
15621
|
+
...role !== undefined ? { role } : {},
|
|
15622
|
+
...groupId !== undefined ? { groupId } : {}
|
|
15623
|
+
};
|
|
15624
|
+
}
|
|
15625
|
+
parseTaskListFilter(params) {
|
|
15626
|
+
requireOnlyKeys(params, ["filter"], "params");
|
|
15627
|
+
const filter = requireOptionalObject(params, "filter");
|
|
15628
|
+
if (!filter) {
|
|
15629
|
+
throw new OrchestrationInvalidRequestError("filter must include coordinatorSession");
|
|
15630
|
+
}
|
|
15631
|
+
requireOnlyKeys(filter, ["coordinatorSession", "status", "stuck", "sort", "order"], "filter");
|
|
15632
|
+
const status = requireOptionalEnum(filter, "status", [
|
|
15633
|
+
"pending",
|
|
15634
|
+
"needs_confirmation",
|
|
15635
|
+
"running",
|
|
15636
|
+
"blocked",
|
|
15637
|
+
"waiting_for_human",
|
|
15638
|
+
"completed",
|
|
15639
|
+
"failed",
|
|
15640
|
+
"cancelled"
|
|
15641
|
+
]);
|
|
15642
|
+
const stuck = requireOptionalBoolean(filter, "stuck");
|
|
15643
|
+
const sort = requireOptionalEnum(filter, "sort", ["updatedAt", "createdAt"]);
|
|
15644
|
+
const order = requireOptionalEnum(filter, "order", ["asc", "desc"]);
|
|
15645
|
+
return {
|
|
15646
|
+
coordinatorSession: requireString(filter, "coordinatorSession"),
|
|
15647
|
+
...status !== undefined ? { status } : {},
|
|
15648
|
+
...stuck !== undefined ? { stuck } : {},
|
|
15649
|
+
...sort !== undefined ? { sort } : {},
|
|
15650
|
+
...order !== undefined ? { order } : {}
|
|
15651
|
+
};
|
|
15652
|
+
}
|
|
15653
|
+
parseCancelTaskInput(params) {
|
|
15654
|
+
requireOnlyKeys(params, ["taskId", "sourceHandle", "coordinatorSession"], "params");
|
|
15655
|
+
const sourceHandle = requireOptionalString(params, "sourceHandle");
|
|
15656
|
+
const coordinatorSession = requireOptionalString(params, "coordinatorSession");
|
|
15657
|
+
if (sourceHandle === undefined && coordinatorSession === undefined) {
|
|
15658
|
+
throw new OrchestrationInvalidRequestError("task.cancel requires sourceHandle or coordinatorSession");
|
|
15659
|
+
}
|
|
15660
|
+
return {
|
|
15661
|
+
taskId: requireString(params, "taskId"),
|
|
15662
|
+
...sourceHandle !== undefined ? { sourceHandle } : {},
|
|
15663
|
+
...coordinatorSession !== undefined ? { coordinatorSession } : {}
|
|
15664
|
+
};
|
|
15665
|
+
}
|
|
15666
|
+
parseRecordWorkerReplyInput(params) {
|
|
15667
|
+
requireOnlyKeys(params, ["taskId", "sourceHandle", "status", "summary", "resultText"], "params");
|
|
15668
|
+
const status = requireOptionalEnum(params, "status", ["completed", "failed", "cancelled"]);
|
|
15669
|
+
const summary = requireOptionalString(params, "summary");
|
|
15670
|
+
const resultText = requireOptionalString(params, "resultText");
|
|
15671
|
+
return {
|
|
15672
|
+
taskId: requireString(params, "taskId"),
|
|
15673
|
+
sourceHandle: requireString(params, "sourceHandle"),
|
|
15674
|
+
...status !== undefined ? { status } : {},
|
|
15675
|
+
...summary !== undefined ? { summary } : {},
|
|
15676
|
+
...resultText !== undefined ? { resultText } : {}
|
|
15677
|
+
};
|
|
15678
|
+
}
|
|
15679
|
+
parseWaitTaskInput(params) {
|
|
15680
|
+
requireOnlyKeys(params, ["coordinatorSession", "taskId", "timeoutMs", "pollIntervalMs"], "params");
|
|
15681
|
+
const timeoutMs = requireOptionalIntegerInRange(params, "timeoutMs", 0, MAX_TASK_WAIT_TIMEOUT_MS);
|
|
15682
|
+
const pollIntervalMs = requireOptionalIntegerInRange(params, "pollIntervalMs", 1, MAX_TASK_WAIT_POLL_INTERVAL_MS);
|
|
15683
|
+
return {
|
|
15684
|
+
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
15685
|
+
taskId: requireString(params, "taskId"),
|
|
15686
|
+
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
15687
|
+
...pollIntervalMs !== undefined ? { pollIntervalMs } : {}
|
|
15688
|
+
};
|
|
15689
|
+
}
|
|
15066
15690
|
parseWorkerRaiseQuestionInput(params) {
|
|
15691
|
+
requireOnlyKeys(params, ["taskId", "sourceHandle", "question", "whyBlocked", "whatIsNeeded"], "params");
|
|
15067
15692
|
return {
|
|
15068
15693
|
taskId: requireString(params, "taskId"),
|
|
15069
15694
|
sourceHandle: requireString(params, "sourceHandle"),
|
|
@@ -15073,6 +15698,7 @@ class OrchestrationServer {
|
|
|
15073
15698
|
};
|
|
15074
15699
|
}
|
|
15075
15700
|
parseGroupListFilter(params) {
|
|
15701
|
+
requireOnlyKeys(params, ["coordinatorSession", "status", "stuck", "sort", "order"], "params");
|
|
15076
15702
|
const status = requireOptionalEnum(params, "status", ["pending", "running", "terminal"]);
|
|
15077
15703
|
const stuck = requireOptionalBoolean(params, "stuck");
|
|
15078
15704
|
const sort = requireOptionalEnum(params, "sort", ["updatedAt", "createdAt"]);
|
|
@@ -15161,6 +15787,26 @@ function requireString(params, key) {
|
|
|
15161
15787
|
}
|
|
15162
15788
|
return value;
|
|
15163
15789
|
}
|
|
15790
|
+
function requireOptionalNumber(params, key) {
|
|
15791
|
+
const value = params[key];
|
|
15792
|
+
if (value === undefined) {
|
|
15793
|
+
return;
|
|
15794
|
+
}
|
|
15795
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
15796
|
+
throw new OrchestrationInvalidRequestError(`${key} must be a finite number when provided`);
|
|
15797
|
+
}
|
|
15798
|
+
return value;
|
|
15799
|
+
}
|
|
15800
|
+
function requireOptionalIntegerInRange(params, key, min, max) {
|
|
15801
|
+
const value = requireOptionalNumber(params, key);
|
|
15802
|
+
if (value === undefined) {
|
|
15803
|
+
return;
|
|
15804
|
+
}
|
|
15805
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
15806
|
+
throw new OrchestrationInvalidRequestError(`${key} must be an integer between ${min} and ${max} when provided`);
|
|
15807
|
+
}
|
|
15808
|
+
return value;
|
|
15809
|
+
}
|
|
15164
15810
|
function requireOptionalString(params, key) {
|
|
15165
15811
|
const value = params[key];
|
|
15166
15812
|
if (value === undefined) {
|
|
@@ -15208,6 +15854,14 @@ function requireOptionalObject(params, key) {
|
|
|
15208
15854
|
}
|
|
15209
15855
|
return value;
|
|
15210
15856
|
}
|
|
15857
|
+
function requireOnlyKeys(params, allowed, label) {
|
|
15858
|
+
const allowedSet = new Set(allowed);
|
|
15859
|
+
for (const key of Object.keys(params)) {
|
|
15860
|
+
if (!allowedSet.has(key)) {
|
|
15861
|
+
throw new OrchestrationInvalidRequestError(`${label}.${key} is not supported`);
|
|
15862
|
+
}
|
|
15863
|
+
}
|
|
15864
|
+
}
|
|
15211
15865
|
function requireTaskQuestions(params, key) {
|
|
15212
15866
|
const value = params[key];
|
|
15213
15867
|
if (!Array.isArray(value) || value.length === 0) {
|
|
@@ -15267,18 +15921,22 @@ function isServerNotRunningError(error2) {
|
|
|
15267
15921
|
var OrchestrationInvalidRequestError, ORCHESTRATION_RPC_METHODS;
|
|
15268
15922
|
var init_orchestration_server = __esm(() => {
|
|
15269
15923
|
init_orchestration_ipc();
|
|
15924
|
+
init_task_wait_timeouts();
|
|
15270
15925
|
OrchestrationInvalidRequestError = class OrchestrationInvalidRequestError extends Error {
|
|
15271
15926
|
};
|
|
15272
15927
|
ORCHESTRATION_RPC_METHODS = new Set([
|
|
15928
|
+
"coordinator.register_external",
|
|
15273
15929
|
"delegate.request",
|
|
15274
15930
|
"task.get",
|
|
15275
15931
|
"task.list",
|
|
15932
|
+
"task.wait",
|
|
15276
15933
|
"task.approve",
|
|
15277
15934
|
"task.reject",
|
|
15278
15935
|
"task.cancel",
|
|
15279
15936
|
"worker.reply",
|
|
15280
15937
|
"worker.raise_question",
|
|
15281
15938
|
"coordinator.answer_question",
|
|
15939
|
+
"coordinator.retract_answer",
|
|
15282
15940
|
"coordinator.request_human_input",
|
|
15283
15941
|
"coordinator.follow_up_human_package",
|
|
15284
15942
|
"coordinator.review_contested_result",
|
|
@@ -15289,24 +15947,6 @@ var init_orchestration_server = __esm(() => {
|
|
|
15289
15947
|
]);
|
|
15290
15948
|
});
|
|
15291
15949
|
|
|
15292
|
-
// src/orchestration/async-mutex.ts
|
|
15293
|
-
class AsyncMutex {
|
|
15294
|
-
tail = Promise.resolve();
|
|
15295
|
-
async run(critical) {
|
|
15296
|
-
const previous = this.tail;
|
|
15297
|
-
let release;
|
|
15298
|
-
this.tail = new Promise((resolve2) => {
|
|
15299
|
-
release = resolve2;
|
|
15300
|
-
});
|
|
15301
|
-
await previous;
|
|
15302
|
-
try {
|
|
15303
|
-
return await critical();
|
|
15304
|
-
} finally {
|
|
15305
|
-
release();
|
|
15306
|
-
}
|
|
15307
|
-
}
|
|
15308
|
-
}
|
|
15309
|
-
|
|
15310
15950
|
// src/orchestration/progress-line-parser.ts
|
|
15311
15951
|
class ProgressLineBuffer {
|
|
15312
15952
|
feed(segment) {
|
|
@@ -15331,15 +15971,67 @@ function stripProgressLines(text) {
|
|
|
15331
15971
|
var PROGRESS_PREFIX = "[PROGRESS]";
|
|
15332
15972
|
|
|
15333
15973
|
// src/orchestration/orchestration-service.ts
|
|
15974
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
15975
|
+
import { basename as basename3, isAbsolute, normalize } from "node:path";
|
|
15976
|
+
|
|
15334
15977
|
class OrchestrationService {
|
|
15335
15978
|
deps;
|
|
15336
|
-
stateMutex
|
|
15979
|
+
stateMutex;
|
|
15980
|
+
pendingWorkerSessions = new Map;
|
|
15981
|
+
pendingLogicalTransportSessions = new Map;
|
|
15337
15982
|
constructor(deps) {
|
|
15338
15983
|
this.deps = deps;
|
|
15984
|
+
this.stateMutex = deps.stateMutex ?? new AsyncMutex;
|
|
15339
15985
|
}
|
|
15340
15986
|
async mutate(critical) {
|
|
15341
15987
|
return await this.stateMutex.run(critical);
|
|
15342
15988
|
}
|
|
15989
|
+
async registerExternalCoordinator(input) {
|
|
15990
|
+
const coordinatorSession = input.coordinatorSession.trim();
|
|
15991
|
+
const workspace = input.workspace?.trim();
|
|
15992
|
+
const defaultTargetAgent = input.defaultTargetAgent?.trim();
|
|
15993
|
+
if (!coordinatorSession) {
|
|
15994
|
+
throw new Error("coordinatorSession must be a non-empty string");
|
|
15995
|
+
}
|
|
15996
|
+
if (workspace && !this.deps.config.workspaces[workspace]) {
|
|
15997
|
+
throw new Error(`workspace "${workspace}" is not configured`);
|
|
15998
|
+
}
|
|
15999
|
+
return await this.mutate(async () => {
|
|
16000
|
+
const state = await this.deps.loadState();
|
|
16001
|
+
const externalCoordinators = this.ensureExternalCoordinators(state);
|
|
16002
|
+
const existing = externalCoordinators[coordinatorSession];
|
|
16003
|
+
if ((this.pendingWorkerSessions.get(coordinatorSession) ?? 0) > 0) {
|
|
16004
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" conflicts with an existing worker session`);
|
|
16005
|
+
}
|
|
16006
|
+
if (state.orchestration.workerBindings[coordinatorSession]) {
|
|
16007
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" conflicts with an existing worker session`);
|
|
16008
|
+
}
|
|
16009
|
+
if (this.hasActiveTaskWorkerSession(state, coordinatorSession)) {
|
|
16010
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" conflicts with an existing worker session`);
|
|
16011
|
+
}
|
|
16012
|
+
if ((this.pendingLogicalTransportSessions.get(coordinatorSession) ?? 0) > 0) {
|
|
16013
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" conflicts with an existing logical session`);
|
|
16014
|
+
}
|
|
16015
|
+
if (Object.values(state.sessions).some((session) => session.transport_session === coordinatorSession)) {
|
|
16016
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" conflicts with an existing logical session`);
|
|
16017
|
+
}
|
|
16018
|
+
if (existing?.workspace && workspace && existing.workspace !== workspace) {
|
|
16019
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" is already bound to workspace "${existing.workspace}"; use a new coordinator session for workspace "${workspace}"`);
|
|
16020
|
+
}
|
|
16021
|
+
const now = this.deps.now().toISOString();
|
|
16022
|
+
const effectiveDefaultTargetAgent = defaultTargetAgent || existing?.defaultTargetAgent;
|
|
16023
|
+
const record3 = {
|
|
16024
|
+
coordinatorSession,
|
|
16025
|
+
...workspace ? { workspace } : existing?.workspace ? { workspace: existing.workspace } : {},
|
|
16026
|
+
createdAt: existing?.createdAt ?? now,
|
|
16027
|
+
updatedAt: now,
|
|
16028
|
+
...effectiveDefaultTargetAgent ? { defaultTargetAgent: effectiveDefaultTargetAgent } : {}
|
|
16029
|
+
};
|
|
16030
|
+
externalCoordinators[coordinatorSession] = record3;
|
|
16031
|
+
await this.deps.saveState(state);
|
|
16032
|
+
return { ...record3 };
|
|
16033
|
+
});
|
|
16034
|
+
}
|
|
15343
16035
|
async createGroup(input) {
|
|
15344
16036
|
if (input.coordinatorSession.trim().length === 0) {
|
|
15345
16037
|
throw new Error("coordinatorSession must be a non-empty string");
|
|
@@ -15460,70 +16152,89 @@ class OrchestrationService {
|
|
|
15460
16152
|
const normalizedGroupId = this.normalizeGroupId(input.groupId);
|
|
15461
16153
|
const taskId = this.deps.createId();
|
|
15462
16154
|
const workerSession = await this.resolveWorkerSession(input);
|
|
15463
|
-
const
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
targetAgent: input.targetAgent,
|
|
15470
|
-
role
|
|
15471
|
-
});
|
|
15472
|
-
const prepared = await this.mutate(async () => {
|
|
15473
|
-
const state = await this.deps.loadState();
|
|
15474
|
-
const now = this.deps.now().toISOString();
|
|
15475
|
-
if (normalizedGroupId) {
|
|
15476
|
-
this.assertGroupOwnership(this.ensureGroups(state)[normalizedGroupId], normalizedGroupId, input.coordinatorSession);
|
|
15477
|
-
}
|
|
15478
|
-
const task = {
|
|
15479
|
-
taskId,
|
|
16155
|
+
const releaseWorkerReservation = await this.reserveProposedWorkerSession(workerSession);
|
|
16156
|
+
let ensuredWorkerSession = workerSession;
|
|
16157
|
+
let prepared;
|
|
16158
|
+
try {
|
|
16159
|
+
ensuredWorkerSession = await this.ensureReservedWorkerSession({
|
|
16160
|
+
workerSession,
|
|
15480
16161
|
sourceHandle: input.sourceHandle,
|
|
15481
16162
|
sourceKind: input.sourceKind,
|
|
15482
16163
|
coordinatorSession: input.coordinatorSession,
|
|
15483
|
-
workerSession: ensuredWorkerSession,
|
|
15484
|
-
workspace: input.workspace,
|
|
15485
|
-
targetAgent: input.targetAgent,
|
|
15486
|
-
...role ? { role } : {},
|
|
15487
|
-
...normalizedGroupId ? { groupId: normalizedGroupId } : {},
|
|
15488
|
-
task: input.task,
|
|
15489
|
-
status: "running",
|
|
15490
|
-
summary: "",
|
|
15491
|
-
resultText: "",
|
|
15492
|
-
createdAt: now,
|
|
15493
|
-
updatedAt: now,
|
|
15494
|
-
...input.chatKey ? { chatKey: input.chatKey } : {},
|
|
15495
|
-
...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
|
|
15496
|
-
...input.accountId ? { accountId: input.accountId } : {}
|
|
15497
|
-
};
|
|
15498
|
-
state.orchestration.tasks[taskId] = task;
|
|
15499
|
-
if (normalizedGroupId) {
|
|
15500
|
-
const group = this.ensureGroups(state)[normalizedGroupId];
|
|
15501
|
-
group.updatedAt = now;
|
|
15502
|
-
group.coordinatorInjectedAt = undefined;
|
|
15503
|
-
group.injectionPending = undefined;
|
|
15504
|
-
group.injectionAppliedAt = undefined;
|
|
15505
|
-
group.lastInjectionError = undefined;
|
|
15506
|
-
}
|
|
15507
|
-
const previousBinding = state.orchestration.workerBindings[ensuredWorkerSession];
|
|
15508
|
-
state.orchestration.workerBindings[ensuredWorkerSession] = {
|
|
15509
|
-
sourceHandle: ensuredWorkerSession,
|
|
15510
|
-
coordinatorSession: input.coordinatorSession,
|
|
15511
16164
|
workspace: input.workspace,
|
|
16165
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
15512
16166
|
targetAgent: input.targetAgent,
|
|
15513
16167
|
role
|
|
15514
|
-
};
|
|
15515
|
-
await this.
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
16168
|
+
});
|
|
16169
|
+
prepared = await this.mutate(async () => {
|
|
16170
|
+
const state = await this.deps.loadState();
|
|
16171
|
+
const now = this.deps.now().toISOString();
|
|
16172
|
+
if (normalizedGroupId) {
|
|
16173
|
+
this.assertGroupOwnership(this.ensureGroups(state)[normalizedGroupId], normalizedGroupId, input.coordinatorSession);
|
|
16174
|
+
}
|
|
16175
|
+
const task = {
|
|
16176
|
+
taskId,
|
|
16177
|
+
sourceHandle: input.sourceHandle,
|
|
16178
|
+
sourceKind: input.sourceKind,
|
|
16179
|
+
coordinatorSession: input.coordinatorSession,
|
|
16180
|
+
workerSession: ensuredWorkerSession,
|
|
16181
|
+
workspace: input.workspace,
|
|
16182
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
16183
|
+
targetAgent: input.targetAgent,
|
|
16184
|
+
...role ? { role } : {},
|
|
16185
|
+
...normalizedGroupId ? { groupId: normalizedGroupId } : {},
|
|
16186
|
+
task: input.task,
|
|
16187
|
+
status: "running",
|
|
16188
|
+
summary: "",
|
|
16189
|
+
resultText: "",
|
|
16190
|
+
createdAt: now,
|
|
16191
|
+
updatedAt: now,
|
|
16192
|
+
...input.chatKey ? { chatKey: input.chatKey } : {},
|
|
16193
|
+
...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
|
|
16194
|
+
...input.accountId ? { accountId: input.accountId } : {}
|
|
16195
|
+
};
|
|
16196
|
+
let previousGroup;
|
|
16197
|
+
if (normalizedGroupId) {
|
|
16198
|
+
const group = this.ensureGroups(state)[normalizedGroupId];
|
|
16199
|
+
previousGroup = { ...group };
|
|
16200
|
+
group.updatedAt = now;
|
|
16201
|
+
group.coordinatorInjectedAt = undefined;
|
|
16202
|
+
group.injectionPending = undefined;
|
|
16203
|
+
group.injectionAppliedAt = undefined;
|
|
16204
|
+
group.lastInjectionError = undefined;
|
|
16205
|
+
}
|
|
16206
|
+
const previousBinding = state.orchestration.workerBindings[ensuredWorkerSession];
|
|
16207
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, ensuredWorkerSession);
|
|
16208
|
+
this.assertWorkerSessionAvailable(state, ensuredWorkerSession, undefined, { allowCurrentReservation: true });
|
|
16209
|
+
state.orchestration.tasks[taskId] = task;
|
|
16210
|
+
state.orchestration.workerBindings[ensuredWorkerSession] = {
|
|
16211
|
+
sourceHandle: ensuredWorkerSession,
|
|
16212
|
+
coordinatorSession: input.coordinatorSession,
|
|
16213
|
+
workspace: input.workspace,
|
|
16214
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
16215
|
+
targetAgent: input.targetAgent,
|
|
16216
|
+
role
|
|
16217
|
+
};
|
|
16218
|
+
await this.deps.saveState(state);
|
|
16219
|
+
return {
|
|
16220
|
+
task: { ...task },
|
|
16221
|
+
previousBinding,
|
|
16222
|
+
previousGroup,
|
|
16223
|
+
normalizedGroupId
|
|
16224
|
+
};
|
|
16225
|
+
});
|
|
16226
|
+
} catch (error2) {
|
|
16227
|
+
await releaseWorkerReservation();
|
|
16228
|
+
throw error2;
|
|
16229
|
+
}
|
|
16230
|
+
await releaseWorkerReservation();
|
|
15521
16231
|
try {
|
|
15522
16232
|
await this.deps.dispatchWorkerTask({
|
|
15523
16233
|
taskId,
|
|
15524
16234
|
workerSession: ensuredWorkerSession,
|
|
15525
16235
|
coordinatorSession: input.coordinatorSession,
|
|
15526
16236
|
workspace: input.workspace,
|
|
16237
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
15527
16238
|
targetAgent: input.targetAgent,
|
|
15528
16239
|
...role ? { role } : {},
|
|
15529
16240
|
task: input.task
|
|
@@ -15537,6 +16248,9 @@ class OrchestrationService {
|
|
|
15537
16248
|
} else {
|
|
15538
16249
|
delete state.orchestration.workerBindings[ensuredWorkerSession];
|
|
15539
16250
|
}
|
|
16251
|
+
if (prepared.normalizedGroupId && prepared.previousGroup) {
|
|
16252
|
+
this.ensureGroups(state)[prepared.normalizedGroupId] = prepared.previousGroup;
|
|
16253
|
+
}
|
|
15540
16254
|
await this.deps.saveState(state);
|
|
15541
16255
|
});
|
|
15542
16256
|
throw error2;
|
|
@@ -15553,115 +16267,288 @@ class OrchestrationService {
|
|
|
15553
16267
|
const preflight = await this.mutate(async () => {
|
|
15554
16268
|
const state = await this.deps.loadState();
|
|
15555
16269
|
const sourceContext = this.resolveRpcSourceContext(state, input.sourceHandle);
|
|
16270
|
+
const targetLocation = this.resolveRpcTargetLocation(sourceContext, input.cwd);
|
|
15556
16271
|
const role = this.normalizeRole(input.role);
|
|
15557
16272
|
this.assertRpcRequestAllowed(state, sourceContext.sourceKind, sourceContext.coordinatorSession, input.targetAgent, role);
|
|
15558
16273
|
const normalizedGroupId = this.normalizeGroupId(input.groupId);
|
|
15559
16274
|
if (normalizedGroupId) {
|
|
15560
16275
|
this.assertGroupOwnership(this.ensureGroups(state)[normalizedGroupId], normalizedGroupId, sourceContext.coordinatorSession);
|
|
15561
16276
|
}
|
|
15562
|
-
return { sourceContext, role, normalizedGroupId };
|
|
16277
|
+
return { sourceContext, targetLocation, role, normalizedGroupId };
|
|
15563
16278
|
});
|
|
15564
16279
|
const autoRun = preflight.sourceContext.sourceKind === "coordinator";
|
|
15565
16280
|
const workerSessionName = await this.resolveWorkerSession({
|
|
15566
16281
|
sourceHandle: input.sourceHandle,
|
|
15567
16282
|
sourceKind: preflight.sourceContext.sourceKind,
|
|
15568
16283
|
coordinatorSession: preflight.sourceContext.coordinatorSession,
|
|
15569
|
-
workspace: preflight.
|
|
16284
|
+
workspace: preflight.targetLocation.workspace,
|
|
16285
|
+
...preflight.targetLocation.cwd ? { cwd: preflight.targetLocation.cwd } : {},
|
|
15570
16286
|
targetAgent: input.targetAgent,
|
|
15571
16287
|
task: input.task,
|
|
15572
16288
|
...preflight.role ? { role: preflight.role } : {}
|
|
15573
16289
|
});
|
|
15574
|
-
const
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
const status = autoRun ? "running" : "needs_confirmation";
|
|
15588
|
-
const task = {
|
|
15589
|
-
taskId,
|
|
15590
|
-
sourceHandle: input.sourceHandle,
|
|
15591
|
-
sourceKind: preflight.sourceContext.sourceKind,
|
|
15592
|
-
coordinatorSession: preflight.sourceContext.coordinatorSession,
|
|
15593
|
-
workerSession: ensuredWorkerSession,
|
|
15594
|
-
workspace: preflight.sourceContext.workspace,
|
|
15595
|
-
targetAgent: input.targetAgent,
|
|
15596
|
-
...preflight.role ? { role: preflight.role } : {},
|
|
15597
|
-
...preflight.normalizedGroupId ? { groupId: preflight.normalizedGroupId } : {},
|
|
15598
|
-
task: input.task,
|
|
15599
|
-
status,
|
|
15600
|
-
summary: "",
|
|
15601
|
-
resultText: "",
|
|
15602
|
-
createdAt: now,
|
|
15603
|
-
updatedAt: now
|
|
15604
|
-
};
|
|
15605
|
-
state.orchestration.tasks[taskId] = task;
|
|
15606
|
-
let previousGroup;
|
|
15607
|
-
if (preflight.normalizedGroupId) {
|
|
15608
|
-
const group = this.ensureGroups(state)[preflight.normalizedGroupId];
|
|
15609
|
-
previousGroup = { ...group };
|
|
15610
|
-
group.updatedAt = now;
|
|
15611
|
-
group.coordinatorInjectedAt = undefined;
|
|
15612
|
-
group.injectionPending = undefined;
|
|
15613
|
-
group.injectionAppliedAt = undefined;
|
|
15614
|
-
group.lastInjectionError = undefined;
|
|
15615
|
-
}
|
|
15616
|
-
let previousBinding;
|
|
15617
|
-
if (autoRun) {
|
|
15618
|
-
previousBinding = state.orchestration.workerBindings[ensuredWorkerSession];
|
|
15619
|
-
state.orchestration.workerBindings[ensuredWorkerSession] = {
|
|
15620
|
-
sourceHandle: ensuredWorkerSession,
|
|
16290
|
+
const releaseWorkerReservation = await this.reserveProposedWorkerSession(workerSessionName);
|
|
16291
|
+
let prepared;
|
|
16292
|
+
try {
|
|
16293
|
+
prepared = await this.mutate(async () => {
|
|
16294
|
+
const state = await this.deps.loadState();
|
|
16295
|
+
this.assertRpcRequestAllowed(state, preflight.sourceContext.sourceKind, preflight.sourceContext.coordinatorSession, input.targetAgent, preflight.role);
|
|
16296
|
+
const now = this.deps.now().toISOString();
|
|
16297
|
+
const taskId = this.deps.createId();
|
|
16298
|
+
const status = autoRun ? "running" : "needs_confirmation";
|
|
16299
|
+
const task = {
|
|
16300
|
+
taskId,
|
|
16301
|
+
sourceHandle: input.sourceHandle,
|
|
16302
|
+
sourceKind: preflight.sourceContext.sourceKind,
|
|
15621
16303
|
coordinatorSession: preflight.sourceContext.coordinatorSession,
|
|
15622
|
-
|
|
16304
|
+
workerSession: workerSessionName,
|
|
16305
|
+
workspace: preflight.targetLocation.workspace,
|
|
16306
|
+
...preflight.targetLocation.cwd ? { cwd: preflight.targetLocation.cwd } : {},
|
|
15623
16307
|
targetAgent: input.targetAgent,
|
|
15624
|
-
role: preflight.role
|
|
16308
|
+
...preflight.role ? { role: preflight.role } : {},
|
|
16309
|
+
...preflight.normalizedGroupId ? { groupId: preflight.normalizedGroupId } : {},
|
|
16310
|
+
task: input.task,
|
|
16311
|
+
status,
|
|
16312
|
+
summary: "",
|
|
16313
|
+
resultText: "",
|
|
16314
|
+
createdAt: now,
|
|
16315
|
+
updatedAt: now
|
|
15625
16316
|
};
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
16317
|
+
if (preflight.normalizedGroupId) {
|
|
16318
|
+
const group = this.ensureGroups(state)[preflight.normalizedGroupId];
|
|
16319
|
+
group.updatedAt = now;
|
|
16320
|
+
group.coordinatorInjectedAt = undefined;
|
|
16321
|
+
group.injectionPending = undefined;
|
|
16322
|
+
group.injectionAppliedAt = undefined;
|
|
16323
|
+
group.lastInjectionError = undefined;
|
|
16324
|
+
}
|
|
16325
|
+
let previousBinding;
|
|
16326
|
+
if (autoRun) {
|
|
16327
|
+
previousBinding = state.orchestration.workerBindings[workerSessionName];
|
|
16328
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSessionName);
|
|
16329
|
+
this.assertWorkerSessionAvailable(state, workerSessionName, undefined, { allowCurrentReservation: true });
|
|
16330
|
+
state.orchestration.tasks[taskId] = task;
|
|
16331
|
+
state.orchestration.workerBindings[workerSessionName] = {
|
|
16332
|
+
sourceHandle: workerSessionName,
|
|
16333
|
+
coordinatorSession: preflight.sourceContext.coordinatorSession,
|
|
16334
|
+
workspace: preflight.targetLocation.workspace,
|
|
16335
|
+
...preflight.targetLocation.cwd ? { cwd: preflight.targetLocation.cwd } : {},
|
|
16336
|
+
targetAgent: input.targetAgent,
|
|
16337
|
+
role: preflight.role
|
|
16338
|
+
};
|
|
16339
|
+
} else {
|
|
16340
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSessionName);
|
|
16341
|
+
this.assertWorkerSessionAvailable(state, workerSessionName, undefined, { allowCurrentReservation: true });
|
|
16342
|
+
state.orchestration.tasks[taskId] = task;
|
|
16343
|
+
}
|
|
16344
|
+
await this.deps.saveState(state);
|
|
16345
|
+
return { task: { ...task }, status, previousBinding, normalizedGroupId: preflight.normalizedGroupId };
|
|
16346
|
+
});
|
|
16347
|
+
} catch (error2) {
|
|
16348
|
+
await releaseWorkerReservation();
|
|
16349
|
+
throw error2;
|
|
16350
|
+
}
|
|
16351
|
+
await releaseWorkerReservation();
|
|
15630
16352
|
if (autoRun) {
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
|
|
16353
|
+
this.runAutoRunRpcWorkerTask({
|
|
16354
|
+
task: prepared.task,
|
|
16355
|
+
previousBinding: prepared.previousBinding
|
|
16356
|
+
});
|
|
16357
|
+
}
|
|
16358
|
+
this.logEvent("orchestration.task.created", "delegated task created", this.taskContext(prepared.task));
|
|
16359
|
+
return {
|
|
16360
|
+
taskId: prepared.task.taskId,
|
|
16361
|
+
status: prepared.status,
|
|
16362
|
+
...autoRun ? { workerSession: workerSessionName } : {}
|
|
16363
|
+
};
|
|
16364
|
+
}
|
|
16365
|
+
async runAutoRunRpcWorkerTask(input) {
|
|
16366
|
+
const { task } = input;
|
|
16367
|
+
try {
|
|
16368
|
+
const ensuredWorkerSession = await this.ensureReservedWorkerSession({
|
|
16369
|
+
workerSession: task.workerSession,
|
|
16370
|
+
sourceHandle: task.sourceHandle,
|
|
16371
|
+
sourceKind: task.sourceKind,
|
|
16372
|
+
coordinatorSession: task.coordinatorSession,
|
|
16373
|
+
workspace: task.workspace,
|
|
16374
|
+
...task.cwd ? { cwd: task.cwd } : {},
|
|
16375
|
+
targetAgent: task.targetAgent,
|
|
16376
|
+
...task.role ? { role: task.role } : {}
|
|
16377
|
+
});
|
|
16378
|
+
const startupAction = await this.mutate(async () => {
|
|
16379
|
+
const state = await this.deps.loadState();
|
|
16380
|
+
const current = state.orchestration.tasks[task.taskId];
|
|
16381
|
+
if (current?.workerSession === ensuredWorkerSession && current.status === "running" && current.cancelRequestedAt !== undefined) {
|
|
16382
|
+
return "completeCancellation";
|
|
16383
|
+
}
|
|
16384
|
+
return current !== undefined && current.workerSession === ensuredWorkerSession && current.status === "running" ? "dispatch" : "skip";
|
|
16385
|
+
});
|
|
16386
|
+
if (startupAction === "completeCancellation") {
|
|
16387
|
+
const completed = await this.completeAutoRunStartupCancellation({
|
|
16388
|
+
task,
|
|
16389
|
+
previousBinding: input.previousBinding
|
|
15640
16390
|
});
|
|
15641
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
16391
|
+
if (completed) {
|
|
16392
|
+
this.logEvent("orchestration.task.cancel_completed", "task cancellation completed", {
|
|
16393
|
+
...this.taskContext(task),
|
|
16394
|
+
status: "cancelled"
|
|
16395
|
+
});
|
|
16396
|
+
}
|
|
16397
|
+
return;
|
|
16398
|
+
}
|
|
16399
|
+
if (startupAction !== "dispatch") {
|
|
16400
|
+
await this.cleanupAutoRunStartupBinding({
|
|
16401
|
+
task,
|
|
16402
|
+
previousBinding: input.previousBinding
|
|
16403
|
+
});
|
|
16404
|
+
return;
|
|
16405
|
+
}
|
|
16406
|
+
const preDispatchAction = await this.mutate(async () => {
|
|
16407
|
+
const state = await this.deps.loadState();
|
|
16408
|
+
const current = state.orchestration.tasks[task.taskId];
|
|
16409
|
+
if (current?.workerSession === ensuredWorkerSession && current.status === "running" && current.cancelRequestedAt !== undefined) {
|
|
16410
|
+
return "completeCancellation";
|
|
16411
|
+
}
|
|
16412
|
+
return current !== undefined && current.workerSession === ensuredWorkerSession && current.status === "running" ? "dispatch" : "skip";
|
|
16413
|
+
});
|
|
16414
|
+
if (preDispatchAction === "completeCancellation") {
|
|
16415
|
+
const completed = await this.completeAutoRunStartupCancellation({
|
|
16416
|
+
task,
|
|
16417
|
+
previousBinding: input.previousBinding
|
|
16418
|
+
});
|
|
16419
|
+
if (completed) {
|
|
16420
|
+
this.logEvent("orchestration.task.cancel_completed", "task cancellation completed", {
|
|
16421
|
+
...this.taskContext(task),
|
|
16422
|
+
status: "cancelled"
|
|
16423
|
+
});
|
|
16424
|
+
}
|
|
16425
|
+
return;
|
|
16426
|
+
}
|
|
16427
|
+
if (preDispatchAction !== "dispatch") {
|
|
16428
|
+
await this.cleanupAutoRunStartupBinding({
|
|
16429
|
+
task,
|
|
16430
|
+
previousBinding: input.previousBinding
|
|
16431
|
+
});
|
|
16432
|
+
return;
|
|
16433
|
+
}
|
|
16434
|
+
await this.deps.dispatchWorkerTask({
|
|
16435
|
+
taskId: task.taskId,
|
|
16436
|
+
workerSession: ensuredWorkerSession,
|
|
16437
|
+
coordinatorSession: task.coordinatorSession,
|
|
16438
|
+
workspace: task.workspace,
|
|
16439
|
+
...task.cwd ? { cwd: task.cwd } : {},
|
|
16440
|
+
targetAgent: task.targetAgent,
|
|
16441
|
+
...task.role ? { role: task.role } : {},
|
|
16442
|
+
task: task.task
|
|
16443
|
+
});
|
|
16444
|
+
} catch (error2) {
|
|
16445
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
16446
|
+
const completedCancellation = await this.completeAutoRunStartupCancellation({
|
|
16447
|
+
task,
|
|
16448
|
+
previousBinding: input.previousBinding
|
|
16449
|
+
});
|
|
16450
|
+
if (completedCancellation) {
|
|
16451
|
+
this.logEvent("orchestration.task.cancel_completed", "task cancellation completed", {
|
|
16452
|
+
...this.taskContext(task),
|
|
16453
|
+
status: "cancelled"
|
|
16454
|
+
});
|
|
16455
|
+
return;
|
|
16456
|
+
}
|
|
16457
|
+
const taskMarkedFailed = await this.mutate(async () => {
|
|
16458
|
+
const state = await this.deps.loadState();
|
|
16459
|
+
const current = state.orchestration.tasks[task.taskId];
|
|
16460
|
+
const workerSession = task.workerSession;
|
|
16461
|
+
const taskStillOwnsWorkerSession = current?.workerSession === workerSession;
|
|
16462
|
+
const currentBinding = state.orchestration.workerBindings[workerSession];
|
|
16463
|
+
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && currentBinding.coordinatorSession === task.coordinatorSession && currentBinding.workspace === task.workspace && currentBinding.cwd === task.cwd && currentBinding.targetAgent === task.targetAgent && currentBinding.role === task.role;
|
|
16464
|
+
const otherActiveOwner = Object.values(state.orchestration.tasks).some((candidate) => candidate.taskId !== task.taskId && candidate.workerSession === workerSession && (!this.isTerminalStatus(candidate.status) || candidate.reviewPending !== undefined));
|
|
16465
|
+
const restoreOrDeleteBinding = () => {
|
|
16466
|
+
if (!bindingStillBelongsToThisStartup || otherActiveOwner) {
|
|
16467
|
+
return;
|
|
15649
16468
|
}
|
|
15650
|
-
if (
|
|
15651
|
-
|
|
15652
|
-
|
|
16469
|
+
if (input.previousBinding) {
|
|
16470
|
+
state.orchestration.workerBindings[workerSession] = input.previousBinding;
|
|
16471
|
+
} else {
|
|
16472
|
+
delete state.orchestration.workerBindings[workerSession];
|
|
15653
16473
|
}
|
|
16474
|
+
};
|
|
16475
|
+
if (current && taskStillOwnsWorkerSession && current.status === "cancelled") {
|
|
16476
|
+
restoreOrDeleteBinding();
|
|
15654
16477
|
await this.deps.saveState(state);
|
|
16478
|
+
return false;
|
|
16479
|
+
}
|
|
16480
|
+
if (current && taskStillOwnsWorkerSession && current.cancelRequestedAt === undefined && !this.isTerminalStatus(current.status)) {
|
|
16481
|
+
const now = this.deps.now().toISOString();
|
|
16482
|
+
current.status = "failed";
|
|
16483
|
+
current.summary = message;
|
|
16484
|
+
current.resultText = "";
|
|
16485
|
+
current.updatedAt = now;
|
|
16486
|
+
restoreOrDeleteBinding();
|
|
16487
|
+
await this.deps.saveState(state);
|
|
16488
|
+
return true;
|
|
16489
|
+
}
|
|
16490
|
+
await this.deps.saveState(state);
|
|
16491
|
+
return false;
|
|
16492
|
+
});
|
|
16493
|
+
if (taskMarkedFailed) {
|
|
16494
|
+
this.logEvent("orchestration.task.failed", "task failed", {
|
|
16495
|
+
...this.taskContext(task),
|
|
16496
|
+
error: message
|
|
15655
16497
|
});
|
|
15656
|
-
throw error2;
|
|
15657
16498
|
}
|
|
15658
16499
|
}
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15664
|
-
|
|
16500
|
+
}
|
|
16501
|
+
async completeAutoRunStartupCancellation(input) {
|
|
16502
|
+
const { task } = input;
|
|
16503
|
+
return await this.mutate(async () => {
|
|
16504
|
+
const state = await this.deps.loadState();
|
|
16505
|
+
const workerSession = task.workerSession;
|
|
16506
|
+
const current = state.orchestration.tasks[task.taskId];
|
|
16507
|
+
if (!current || current.workerSession !== workerSession || current.status !== "running" || current.cancelRequestedAt === undefined) {
|
|
16508
|
+
return false;
|
|
16509
|
+
}
|
|
16510
|
+
const now = this.deps.now().toISOString();
|
|
16511
|
+
current.status = "cancelled";
|
|
16512
|
+
current.cancelCompletedAt = now;
|
|
16513
|
+
current.lastCancelError = undefined;
|
|
16514
|
+
current.updatedAt = now;
|
|
16515
|
+
this.bumpGroupUpdated(state, current.groupId, now);
|
|
16516
|
+
const currentBinding = state.orchestration.workerBindings[workerSession];
|
|
16517
|
+
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && currentBinding.coordinatorSession === task.coordinatorSession && currentBinding.workspace === task.workspace && currentBinding.cwd === task.cwd && currentBinding.targetAgent === task.targetAgent && currentBinding.role === task.role;
|
|
16518
|
+
const otherActiveOwner = Object.values(state.orchestration.tasks).some((candidate) => candidate.taskId !== task.taskId && candidate.workerSession === workerSession && (!this.isTerminalStatus(candidate.status) || candidate.reviewPending !== undefined));
|
|
16519
|
+
if (bindingStillBelongsToThisStartup && !otherActiveOwner) {
|
|
16520
|
+
if (input.previousBinding) {
|
|
16521
|
+
state.orchestration.workerBindings[workerSession] = input.previousBinding;
|
|
16522
|
+
} else {
|
|
16523
|
+
delete state.orchestration.workerBindings[workerSession];
|
|
16524
|
+
}
|
|
16525
|
+
}
|
|
16526
|
+
await this.deps.saveState(state);
|
|
16527
|
+
return true;
|
|
16528
|
+
});
|
|
16529
|
+
}
|
|
16530
|
+
async cleanupAutoRunStartupBinding(input) {
|
|
16531
|
+
const { task } = input;
|
|
16532
|
+
return await this.mutate(async () => {
|
|
16533
|
+
const state = await this.deps.loadState();
|
|
16534
|
+
const workerSession = task.workerSession;
|
|
16535
|
+
const currentBinding = state.orchestration.workerBindings[workerSession];
|
|
16536
|
+
const bindingStillBelongsToThisStartup = currentBinding?.sourceHandle === workerSession && currentBinding.coordinatorSession === task.coordinatorSession && currentBinding.workspace === task.workspace && currentBinding.cwd === task.cwd && currentBinding.targetAgent === task.targetAgent && currentBinding.role === task.role;
|
|
16537
|
+
if (!bindingStillBelongsToThisStartup) {
|
|
16538
|
+
return false;
|
|
16539
|
+
}
|
|
16540
|
+
const otherActiveOwner = Object.values(state.orchestration.tasks).some((candidate) => candidate.taskId !== task.taskId && candidate.workerSession === workerSession && (!this.isTerminalStatus(candidate.status) || candidate.reviewPending !== undefined));
|
|
16541
|
+
if (otherActiveOwner) {
|
|
16542
|
+
return false;
|
|
16543
|
+
}
|
|
16544
|
+
if (input.previousBinding) {
|
|
16545
|
+
state.orchestration.workerBindings[workerSession] = input.previousBinding;
|
|
16546
|
+
} else {
|
|
16547
|
+
delete state.orchestration.workerBindings[workerSession];
|
|
16548
|
+
}
|
|
16549
|
+
await this.deps.saveState(state);
|
|
16550
|
+
return true;
|
|
16551
|
+
});
|
|
15665
16552
|
}
|
|
15666
16553
|
async recordWorkerReply(input) {
|
|
15667
16554
|
const task = await this.mutate(async () => {
|
|
@@ -15689,9 +16576,15 @@ class OrchestrationService {
|
|
|
15689
16576
|
task2.summary = input.summary ?? "";
|
|
15690
16577
|
task2.resultText = stripProgressLines(input.resultText ?? "");
|
|
15691
16578
|
if (task2.status === "completed" || task2.status === "failed") {
|
|
15692
|
-
task2.
|
|
15693
|
-
|
|
15694
|
-
|
|
16579
|
+
if (!this.isExternalCoordinatorSession(state, task2.coordinatorSession)) {
|
|
16580
|
+
task2.injectionPending = true;
|
|
16581
|
+
task2.injectionAppliedAt = undefined;
|
|
16582
|
+
task2.lastInjectionError = undefined;
|
|
16583
|
+
} else {
|
|
16584
|
+
task2.injectionPending = undefined;
|
|
16585
|
+
task2.injectionAppliedAt = undefined;
|
|
16586
|
+
task2.lastInjectionError = undefined;
|
|
16587
|
+
}
|
|
15695
16588
|
if (!isContestedResult && task2.chatKey && task2.replyContextToken) {
|
|
15696
16589
|
task2.noticePending = true;
|
|
15697
16590
|
task2.noticeSentAt = undefined;
|
|
@@ -15794,6 +16687,30 @@ class OrchestrationService {
|
|
|
15794
16687
|
const task = state.orchestration.tasks[taskId];
|
|
15795
16688
|
return task ? { ...task } : null;
|
|
15796
16689
|
}
|
|
16690
|
+
async waitTask(input) {
|
|
16691
|
+
const timeoutMs = clampWaitTimeout(input.timeoutMs);
|
|
16692
|
+
const pollIntervalMs = clampPollInterval(input.pollIntervalMs);
|
|
16693
|
+
const deadline = Date.now() + timeoutMs;
|
|
16694
|
+
while (true) {
|
|
16695
|
+
const state = await this.deps.loadState();
|
|
16696
|
+
const task = state.orchestration.tasks[input.taskId];
|
|
16697
|
+
if (!task || task.coordinatorSession !== input.coordinatorSession) {
|
|
16698
|
+
return { status: "not_found", task: null };
|
|
16699
|
+
}
|
|
16700
|
+
const snapshot = { ...task };
|
|
16701
|
+
if (isTerminalTaskStatus2(task.status) && task.reviewPending === undefined) {
|
|
16702
|
+
return { status: "terminal", task: snapshot };
|
|
16703
|
+
}
|
|
16704
|
+
if (isAttentionRequiredTask(task)) {
|
|
16705
|
+
return { status: "attention_required", task: snapshot };
|
|
16706
|
+
}
|
|
16707
|
+
const remainingMs = deadline - Date.now();
|
|
16708
|
+
if (remainingMs <= 0) {
|
|
16709
|
+
return { status: "timeout", task: snapshot };
|
|
16710
|
+
}
|
|
16711
|
+
await sleep2(Math.min(pollIntervalMs, remainingMs));
|
|
16712
|
+
}
|
|
16713
|
+
}
|
|
15797
16714
|
async recordCoordinatorRouteContext(input) {
|
|
15798
16715
|
if (input.coordinatorSession.trim().length === 0) {
|
|
15799
16716
|
throw new Error("coordinatorSession must be a non-empty string");
|
|
@@ -15878,13 +16795,16 @@ class OrchestrationService {
|
|
|
15878
16795
|
return {
|
|
15879
16796
|
taskId: task.taskId,
|
|
15880
16797
|
questionId,
|
|
15881
|
-
coordinatorSession: task.coordinatorSession
|
|
16798
|
+
coordinatorSession: task.coordinatorSession,
|
|
16799
|
+
externalCoordinator: this.isExternalCoordinatorSession(state, task.coordinatorSession)
|
|
15882
16800
|
};
|
|
15883
16801
|
});
|
|
15884
16802
|
try {
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
16803
|
+
if (!prepared.externalCoordinator) {
|
|
16804
|
+
await this.deps.wakeCoordinatorSession?.({
|
|
16805
|
+
coordinatorSession: prepared.coordinatorSession
|
|
16806
|
+
});
|
|
16807
|
+
}
|
|
15888
16808
|
} catch (error2) {
|
|
15889
16809
|
await this.recordOpenQuestionWakeError(prepared.taskId, prepared.questionId, error2 instanceof Error ? error2.message : String(error2));
|
|
15890
16810
|
}
|
|
@@ -15941,6 +16861,7 @@ class OrchestrationService {
|
|
|
15941
16861
|
workerSession: prepared.task.workerSession,
|
|
15942
16862
|
coordinatorSession: prepared.task.coordinatorSession,
|
|
15943
16863
|
workspace: prepared.task.workspace,
|
|
16864
|
+
...prepared.task.cwd ? { cwd: prepared.task.cwd } : {},
|
|
15944
16865
|
targetAgent: prepared.task.targetAgent,
|
|
15945
16866
|
answer
|
|
15946
16867
|
});
|
|
@@ -16026,6 +16947,9 @@ class OrchestrationService {
|
|
|
16026
16947
|
}
|
|
16027
16948
|
const prepared = await this.mutate(async () => {
|
|
16028
16949
|
const state = await this.deps.loadState();
|
|
16950
|
+
if (this.isExternalCoordinatorSession(state, input.coordinatorSession)) {
|
|
16951
|
+
throw new Error("human input routing is not configured for external coordinator");
|
|
16952
|
+
}
|
|
16029
16953
|
const coordinatorState = this.ensureCoordinatorQuestionState(state, input.coordinatorSession);
|
|
16030
16954
|
if (input.expectedActivePackageId !== undefined && coordinatorState.activePackageId !== input.expectedActivePackageId) {
|
|
16031
16955
|
throw new Error(`coordinator "${input.coordinatorSession}" active package is "${coordinatorState.activePackageId ?? ""}", not "${input.expectedActivePackageId}"`);
|
|
@@ -16137,6 +17061,9 @@ class OrchestrationService {
|
|
|
16137
17061
|
}
|
|
16138
17062
|
const prepared = await this.mutate(async () => {
|
|
16139
17063
|
const state = await this.deps.loadState();
|
|
17064
|
+
if (this.isExternalCoordinatorSession(state, input.coordinatorSession)) {
|
|
17065
|
+
throw new Error("human input routing is not configured for external coordinator");
|
|
17066
|
+
}
|
|
16140
17067
|
const coordinatorState = this.ensureCoordinatorQuestionState(state, input.coordinatorSession);
|
|
16141
17068
|
if (coordinatorState.activePackageId !== input.packageId) {
|
|
16142
17069
|
throw new Error(`package "${input.packageId}" is not the active package for coordinator "${input.coordinatorSession}"`);
|
|
@@ -16205,6 +17132,9 @@ class OrchestrationService {
|
|
|
16205
17132
|
async retryHumanQuestionPackageDelivery(input) {
|
|
16206
17133
|
const prepared = await this.mutate(async () => {
|
|
16207
17134
|
const state = await this.deps.loadState();
|
|
17135
|
+
if (this.isExternalCoordinatorSession(state, input.coordinatorSession)) {
|
|
17136
|
+
throw new Error("human input routing is not configured for external coordinator");
|
|
17137
|
+
}
|
|
16208
17138
|
const coordinatorState = this.ensureCoordinatorQuestionState(state, input.coordinatorSession);
|
|
16209
17139
|
if (coordinatorState.activePackageId !== input.packageId) {
|
|
16210
17140
|
throw new Error(`package "${input.packageId}" is not the active package for coordinator "${input.coordinatorSession}"`);
|
|
@@ -16253,6 +17183,9 @@ class OrchestrationService {
|
|
|
16253
17183
|
async claimActiveHumanReply(input) {
|
|
16254
17184
|
return await this.mutate(async () => {
|
|
16255
17185
|
const state = await this.deps.loadState();
|
|
17186
|
+
if (this.isExternalCoordinatorSession(state, input.coordinatorSession)) {
|
|
17187
|
+
return null;
|
|
17188
|
+
}
|
|
16256
17189
|
const coordinatorState = this.ensureCoordinatorQuestionState(state, input.coordinatorSession);
|
|
16257
17190
|
if (!coordinatorState.activePackageId || coordinatorState.activePackageId !== input.packageId) {
|
|
16258
17191
|
return null;
|
|
@@ -16291,6 +17224,9 @@ class OrchestrationService {
|
|
|
16291
17224
|
}
|
|
16292
17225
|
async getActiveHumanQuestionPackage(coordinatorSession) {
|
|
16293
17226
|
const state = await this.deps.loadState();
|
|
17227
|
+
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
17228
|
+
return null;
|
|
17229
|
+
}
|
|
16294
17230
|
const coordinatorState = state.orchestration.coordinatorQuestionState[coordinatorSession];
|
|
16295
17231
|
const activePackageId = coordinatorState?.activePackageId;
|
|
16296
17232
|
if (!activePackageId) {
|
|
@@ -16368,10 +17304,11 @@ class OrchestrationService {
|
|
|
16368
17304
|
await this.deps.saveState(state);
|
|
16369
17305
|
return {
|
|
16370
17306
|
task: { ...task },
|
|
16371
|
-
replacementQuestionId
|
|
17307
|
+
replacementQuestionId,
|
|
17308
|
+
externalCoordinator: this.isExternalCoordinatorSession(state, task.coordinatorSession)
|
|
16372
17309
|
};
|
|
16373
17310
|
});
|
|
16374
|
-
if (prepared.replacementQuestionId) {
|
|
17311
|
+
if (prepared.replacementQuestionId && !prepared.externalCoordinator) {
|
|
16375
17312
|
try {
|
|
16376
17313
|
await this.deps.wakeCoordinatorSession?.({
|
|
16377
17314
|
coordinatorSession: prepared.task.coordinatorSession
|
|
@@ -16478,20 +17415,32 @@ class OrchestrationService {
|
|
|
16478
17415
|
}
|
|
16479
17416
|
async listPendingCoordinatorResults(coordinatorSession) {
|
|
16480
17417
|
const state = await this.deps.loadState();
|
|
17418
|
+
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
17419
|
+
return [];
|
|
17420
|
+
}
|
|
16481
17421
|
return Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession === coordinatorSession && this.canInjectTaskIntoCoordinator(state, task) && (task.injectionPending === true || task.coordinatorInjectedAt === undefined)).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((task) => ({ ...task }));
|
|
16482
17422
|
}
|
|
16483
17423
|
async listPendingCoordinatorBlockers(coordinatorSession) {
|
|
16484
17424
|
const state = await this.deps.loadState();
|
|
17425
|
+
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
17426
|
+
return [];
|
|
17427
|
+
}
|
|
16485
17428
|
const coordinatorState = state.orchestration.coordinatorQuestionState[coordinatorSession];
|
|
16486
17429
|
const hiddenQueuedQuestionKeys = coordinatorState?.activePackageId ? new Set((coordinatorState.queuedQuestions ?? []).map((entry) => `${entry.taskId}:${entry.questionId}`)) : null;
|
|
16487
17430
|
return Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession === coordinatorSession && task.status === "blocked" && task.openQuestion?.status === "open" && !hiddenQueuedQuestionKeys?.has(`${task.taskId}:${task.openQuestion.questionId}`)).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((task) => ({ ...task }));
|
|
16488
17431
|
}
|
|
16489
17432
|
async listContestedCoordinatorResults(coordinatorSession) {
|
|
16490
17433
|
const state = await this.deps.loadState();
|
|
17434
|
+
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
17435
|
+
return [];
|
|
17436
|
+
}
|
|
16491
17437
|
return Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession === coordinatorSession && task.reviewPending !== undefined).sort((left, right) => left.updatedAt.localeCompare(right.updatedAt)).map((task) => ({ ...task }));
|
|
16492
17438
|
}
|
|
16493
17439
|
async listPendingCoordinatorGroups(coordinatorSession) {
|
|
16494
17440
|
const state = await this.deps.loadState();
|
|
17441
|
+
if (this.isExternalCoordinatorSession(state, coordinatorSession)) {
|
|
17442
|
+
return [];
|
|
17443
|
+
}
|
|
16495
17444
|
const groups = this.ensureGroups(state);
|
|
16496
17445
|
const tasks = Object.values(state.orchestration.tasks);
|
|
16497
17446
|
return Object.values(groups).filter((group) => group.coordinatorSession === coordinatorSession).filter((group) => {
|
|
@@ -16758,19 +17707,25 @@ class OrchestrationService {
|
|
|
16758
17707
|
task.updatedAt = now;
|
|
16759
17708
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
16760
17709
|
await this.deps.saveState(state);
|
|
16761
|
-
return {
|
|
17710
|
+
return {
|
|
17711
|
+
task: { ...task },
|
|
17712
|
+
replacementQuestionId,
|
|
17713
|
+
externalCoordinator: this.isExternalCoordinatorSession(state, task.coordinatorSession)
|
|
17714
|
+
};
|
|
16762
17715
|
});
|
|
16763
17716
|
if (prepared.replacementQuestionId) {
|
|
16764
17717
|
this.logEvent("orchestration.task.correction_reopened", "task correction reopened blocker", {
|
|
16765
17718
|
...this.taskContext(prepared.task),
|
|
16766
17719
|
replacement_question_id: prepared.replacementQuestionId
|
|
16767
17720
|
});
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
|
|
17721
|
+
if (!prepared.externalCoordinator) {
|
|
17722
|
+
try {
|
|
17723
|
+
await this.deps.wakeCoordinatorSession?.({
|
|
17724
|
+
coordinatorSession: prepared.task.coordinatorSession
|
|
17725
|
+
});
|
|
17726
|
+
} catch (error2) {
|
|
17727
|
+
await this.recordOpenQuestionWakeError(prepared.task.taskId, prepared.replacementQuestionId, error2 instanceof Error ? error2.message : String(error2));
|
|
17728
|
+
}
|
|
16774
17729
|
}
|
|
16775
17730
|
return prepared.task;
|
|
16776
17731
|
}
|
|
@@ -16810,54 +17765,71 @@ class OrchestrationService {
|
|
|
16810
17765
|
sourceKind: currentTask.sourceKind,
|
|
16811
17766
|
coordinatorSession: currentTask.coordinatorSession,
|
|
16812
17767
|
workspace: currentTask.workspace,
|
|
17768
|
+
...currentTask.cwd ? { cwd: currentTask.cwd } : {},
|
|
16813
17769
|
targetAgent: currentTask.targetAgent,
|
|
16814
17770
|
task: currentTask.task,
|
|
16815
17771
|
...currentTask.role ? { role: currentTask.role } : {}
|
|
16816
17772
|
});
|
|
16817
|
-
const
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
17773
|
+
const releaseWorkerReservation = await this.reserveProposedWorkerSession(workerSession, input.taskId);
|
|
17774
|
+
let ensuredWorkerSession = workerSession;
|
|
17775
|
+
let prepared;
|
|
17776
|
+
try {
|
|
17777
|
+
ensuredWorkerSession = await this.ensureReservedWorkerSession({
|
|
17778
|
+
workerSession,
|
|
17779
|
+
sourceHandle: currentTask.sourceHandle,
|
|
17780
|
+
sourceKind: currentTask.sourceKind,
|
|
17781
|
+
coordinatorSession: currentTask.coordinatorSession,
|
|
17782
|
+
workspace: currentTask.workspace,
|
|
17783
|
+
...currentTask.cwd ? { cwd: currentTask.cwd } : {},
|
|
17784
|
+
targetAgent: currentTask.targetAgent,
|
|
17785
|
+
role: currentTask.role
|
|
17786
|
+
});
|
|
17787
|
+
prepared = await this.mutate(async () => {
|
|
17788
|
+
const state = await this.deps.loadState();
|
|
17789
|
+
const task = state.orchestration.tasks[input.taskId];
|
|
17790
|
+
if (!task) {
|
|
17791
|
+
throw new Error(`task "${input.taskId}" does not exist`);
|
|
17792
|
+
}
|
|
17793
|
+
this.assertCoordinatorOwnership(task, input.coordinatorSession);
|
|
17794
|
+
this.assertNeedsConfirmation(task);
|
|
17795
|
+
const previousStatus = task.status;
|
|
17796
|
+
const previousUpdatedAt = task.updatedAt;
|
|
17797
|
+
const previousWorkerSession = task.workerSession;
|
|
17798
|
+
const previousBinding = state.orchestration.workerBindings[ensuredWorkerSession];
|
|
17799
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, ensuredWorkerSession);
|
|
17800
|
+
this.assertWorkerSessionAvailable(state, ensuredWorkerSession, input.taskId, { allowCurrentReservation: true });
|
|
17801
|
+
task.workerSession = ensuredWorkerSession;
|
|
17802
|
+
task.status = "running";
|
|
17803
|
+
task.updatedAt = this.deps.now().toISOString();
|
|
17804
|
+
state.orchestration.workerBindings[ensuredWorkerSession] = {
|
|
17805
|
+
sourceHandle: ensuredWorkerSession,
|
|
17806
|
+
coordinatorSession: task.coordinatorSession,
|
|
17807
|
+
workspace: task.workspace,
|
|
17808
|
+
...task.cwd ? { cwd: task.cwd } : {},
|
|
17809
|
+
targetAgent: task.targetAgent,
|
|
17810
|
+
role: task.role
|
|
17811
|
+
};
|
|
17812
|
+
await this.deps.saveState(state);
|
|
17813
|
+
return {
|
|
17814
|
+
task: { ...task },
|
|
17815
|
+
previousStatus,
|
|
17816
|
+
previousUpdatedAt,
|
|
17817
|
+
previousWorkerSession,
|
|
17818
|
+
previousBinding
|
|
17819
|
+
};
|
|
17820
|
+
});
|
|
17821
|
+
} catch (error2) {
|
|
17822
|
+
await releaseWorkerReservation();
|
|
17823
|
+
throw error2;
|
|
17824
|
+
}
|
|
17825
|
+
await releaseWorkerReservation();
|
|
16855
17826
|
try {
|
|
16856
17827
|
await this.deps.dispatchWorkerTask({
|
|
16857
17828
|
taskId: prepared.task.taskId,
|
|
16858
17829
|
workerSession: ensuredWorkerSession,
|
|
16859
17830
|
coordinatorSession: prepared.task.coordinatorSession,
|
|
16860
17831
|
workspace: prepared.task.workspace,
|
|
17832
|
+
...prepared.task.cwd ? { cwd: prepared.task.cwd } : {},
|
|
16861
17833
|
targetAgent: prepared.task.targetAgent,
|
|
16862
17834
|
...prepared.task.role ? { role: prepared.task.role } : {},
|
|
16863
17835
|
task: prepared.task.task
|
|
@@ -16869,6 +17841,11 @@ class OrchestrationService {
|
|
|
16869
17841
|
if (task) {
|
|
16870
17842
|
task.status = prepared.previousStatus;
|
|
16871
17843
|
task.updatedAt = prepared.previousUpdatedAt;
|
|
17844
|
+
if (prepared.previousWorkerSession === undefined) {
|
|
17845
|
+
delete task.workerSession;
|
|
17846
|
+
} else {
|
|
17847
|
+
task.workerSession = prepared.previousWorkerSession;
|
|
17848
|
+
}
|
|
16872
17849
|
}
|
|
16873
17850
|
if (prepared.previousBinding) {
|
|
16874
17851
|
state.orchestration.workerBindings[ensuredWorkerSession] = prepared.previousBinding;
|
|
@@ -16907,13 +17884,68 @@ class OrchestrationService {
|
|
|
16907
17884
|
sourceKind: input.sourceKind,
|
|
16908
17885
|
coordinatorSession: input.coordinatorSession,
|
|
16909
17886
|
workspace: input.workspace,
|
|
17887
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
16910
17888
|
targetAgent: input.targetAgent,
|
|
16911
17889
|
role
|
|
16912
17890
|
});
|
|
16913
17891
|
if (reusable && reusable.trim().length > 0) {
|
|
16914
17892
|
return reusable.trim();
|
|
16915
17893
|
}
|
|
16916
|
-
return [input.workspace, input.targetAgent, role, input.coordinatorSession].filter((part) => typeof part === "string" && part.trim().length > 0).map((part) => part.trim()).join(":");
|
|
17894
|
+
return [input.workspace, input.cwd ? this.cwdWorkerSessionPart(input.cwd) : undefined, input.targetAgent, role, input.coordinatorSession].filter((part) => typeof part === "string" && part.trim().length > 0).map((part) => part.trim()).join(":");
|
|
17895
|
+
}
|
|
17896
|
+
async reserveProposedWorkerSession(workerSession, excludingTaskId) {
|
|
17897
|
+
await this.mutate(async () => {
|
|
17898
|
+
const state = await this.deps.loadState();
|
|
17899
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSession);
|
|
17900
|
+
this.assertWorkerSessionAvailable(state, workerSession, excludingTaskId);
|
|
17901
|
+
this.pendingWorkerSessions.set(workerSession, (this.pendingWorkerSessions.get(workerSession) ?? 0) + 1);
|
|
17902
|
+
});
|
|
17903
|
+
let released = false;
|
|
17904
|
+
return async () => {
|
|
17905
|
+
if (released) {
|
|
17906
|
+
return;
|
|
17907
|
+
}
|
|
17908
|
+
released = true;
|
|
17909
|
+
await this.mutate(async () => {
|
|
17910
|
+
const count = this.pendingWorkerSessions.get(workerSession) ?? 0;
|
|
17911
|
+
if (count <= 1) {
|
|
17912
|
+
this.pendingWorkerSessions.delete(workerSession);
|
|
17913
|
+
} else {
|
|
17914
|
+
this.pendingWorkerSessions.set(workerSession, count - 1);
|
|
17915
|
+
}
|
|
17916
|
+
});
|
|
17917
|
+
};
|
|
17918
|
+
}
|
|
17919
|
+
async ensureReservedWorkerSession(request) {
|
|
17920
|
+
const ensuredWorkerSession = await this.deps.ensureWorkerSession(request);
|
|
17921
|
+
if (ensuredWorkerSession !== request.workerSession) {
|
|
17922
|
+
throw new Error(`ensureWorkerSession returned "${ensuredWorkerSession}", expected "${request.workerSession}"`);
|
|
17923
|
+
}
|
|
17924
|
+
return ensuredWorkerSession;
|
|
17925
|
+
}
|
|
17926
|
+
async reserveLogicalTransportSession(transportSession) {
|
|
17927
|
+
await this.mutate(async () => {
|
|
17928
|
+
const state = await this.deps.loadState();
|
|
17929
|
+
if (this.isExternalCoordinatorSession(state, transportSession)) {
|
|
17930
|
+
throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
|
|
17931
|
+
}
|
|
17932
|
+
this.pendingLogicalTransportSessions.set(transportSession, (this.pendingLogicalTransportSessions.get(transportSession) ?? 0) + 1);
|
|
17933
|
+
});
|
|
17934
|
+
let released = false;
|
|
17935
|
+
return async () => {
|
|
17936
|
+
if (released) {
|
|
17937
|
+
return;
|
|
17938
|
+
}
|
|
17939
|
+
released = true;
|
|
17940
|
+
await this.mutate(async () => {
|
|
17941
|
+
const count = this.pendingLogicalTransportSessions.get(transportSession) ?? 0;
|
|
17942
|
+
if (count <= 1) {
|
|
17943
|
+
this.pendingLogicalTransportSessions.delete(transportSession);
|
|
17944
|
+
} else {
|
|
17945
|
+
this.pendingLogicalTransportSessions.set(transportSession, count - 1);
|
|
17946
|
+
}
|
|
17947
|
+
});
|
|
17948
|
+
};
|
|
16917
17949
|
}
|
|
16918
17950
|
buildGroupSummary(group, tasks) {
|
|
16919
17951
|
const sortedTasks = tasks.slice().sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((task) => ({ ...task }));
|
|
@@ -16939,6 +17971,9 @@ class OrchestrationService {
|
|
|
16939
17971
|
if (!group) {
|
|
16940
17972
|
return false;
|
|
16941
17973
|
}
|
|
17974
|
+
if (this.isExternalCoordinatorSession(state, group.coordinatorSession)) {
|
|
17975
|
+
return false;
|
|
17976
|
+
}
|
|
16942
17977
|
if (groupTasks.length === 0) {
|
|
16943
17978
|
return false;
|
|
16944
17979
|
}
|
|
@@ -16951,6 +17986,9 @@ class OrchestrationService {
|
|
|
16951
17986
|
return groupTasks.every((task) => task.status === "completed" || task.status === "failed");
|
|
16952
17987
|
}
|
|
16953
17988
|
canInjectTaskIntoCoordinator(state, task) {
|
|
17989
|
+
if (this.isExternalCoordinatorSession(state, task.coordinatorSession)) {
|
|
17990
|
+
return false;
|
|
17991
|
+
}
|
|
16954
17992
|
if (task.status !== "completed" && task.status !== "failed" || task.reviewPending !== undefined) {
|
|
16955
17993
|
return false;
|
|
16956
17994
|
}
|
|
@@ -16965,7 +18003,8 @@ class OrchestrationService {
|
|
|
16965
18003
|
return {
|
|
16966
18004
|
sourceKind: "worker",
|
|
16967
18005
|
coordinatorSession: binding.coordinatorSession,
|
|
16968
|
-
workspace: binding.workspace
|
|
18006
|
+
workspace: binding.workspace,
|
|
18007
|
+
...binding.cwd ? { cwd: binding.cwd } : {}
|
|
16969
18008
|
};
|
|
16970
18009
|
}
|
|
16971
18010
|
const coordinatorSession = Object.values(state.sessions).find((session) => session.transport_session === sourceHandle);
|
|
@@ -16976,8 +18015,29 @@ class OrchestrationService {
|
|
|
16976
18015
|
workspace: coordinatorSession.workspace
|
|
16977
18016
|
};
|
|
16978
18017
|
}
|
|
18018
|
+
const externalCoordinator = this.ensureExternalCoordinators(state)[sourceHandle];
|
|
18019
|
+
if (externalCoordinator) {
|
|
18020
|
+
return {
|
|
18021
|
+
sourceKind: "coordinator",
|
|
18022
|
+
coordinatorSession: externalCoordinator.coordinatorSession,
|
|
18023
|
+
...externalCoordinator.workspace ? { workspace: externalCoordinator.workspace } : {}
|
|
18024
|
+
};
|
|
18025
|
+
}
|
|
16979
18026
|
throw new Error(`sourceHandle "${sourceHandle}" is not a registered coordinator or worker session`);
|
|
16980
18027
|
}
|
|
18028
|
+
resolveRpcTargetLocation(sourceContext, rawCwd) {
|
|
18029
|
+
const cwd = rawCwd !== undefined ? this.normalizeWorkingDirectory(rawCwd) : sourceContext.cwd;
|
|
18030
|
+
if (cwd) {
|
|
18031
|
+
return {
|
|
18032
|
+
workspace: sourceContext.workspace ?? this.workspaceLabelFromCwd(cwd),
|
|
18033
|
+
cwd
|
|
18034
|
+
};
|
|
18035
|
+
}
|
|
18036
|
+
if (sourceContext.workspace) {
|
|
18037
|
+
return { workspace: sourceContext.workspace };
|
|
18038
|
+
}
|
|
18039
|
+
throw new Error("workingDirectory is required when the external coordinator has no default workspace");
|
|
18040
|
+
}
|
|
16981
18041
|
assertRpcRequestAllowed(state, sourceKind, coordinatorSession, targetAgent, role) {
|
|
16982
18042
|
const policy = this.deps.config.orchestration;
|
|
16983
18043
|
if (sourceKind === "worker" && !policy.allowWorkerChainedRequests) {
|
|
@@ -17022,6 +18082,25 @@ class OrchestrationService {
|
|
|
17022
18082
|
throw new Error("task must be a non-empty string");
|
|
17023
18083
|
}
|
|
17024
18084
|
}
|
|
18085
|
+
normalizeWorkingDirectory(cwd) {
|
|
18086
|
+
const normalized = normalize(cwd.trim());
|
|
18087
|
+
if (normalized.length === 0 || normalized === ".") {
|
|
18088
|
+
throw new Error("workingDirectory must be a non-empty absolute path");
|
|
18089
|
+
}
|
|
18090
|
+
if (!isAbsolute(normalized)) {
|
|
18091
|
+
throw new Error("workingDirectory must be an absolute path");
|
|
18092
|
+
}
|
|
18093
|
+
return normalized;
|
|
18094
|
+
}
|
|
18095
|
+
workspaceLabelFromCwd(cwd) {
|
|
18096
|
+
const base = basename3(cwd).trim() || "cwd";
|
|
18097
|
+
return base.replace(/[^a-zA-Z0-9._-]+/g, "_") || "cwd";
|
|
18098
|
+
}
|
|
18099
|
+
cwdWorkerSessionPart(cwd) {
|
|
18100
|
+
const label = this.workspaceLabelFromCwd(cwd);
|
|
18101
|
+
const hash = createHash2("sha256").update(cwd).digest("hex").slice(0, 8);
|
|
18102
|
+
return `${label}-${hash}`;
|
|
18103
|
+
}
|
|
17025
18104
|
normalizeRole(role) {
|
|
17026
18105
|
const normalized = role?.trim();
|
|
17027
18106
|
return normalized && normalized.length > 0 ? normalized : undefined;
|
|
@@ -17114,6 +18193,37 @@ class OrchestrationService {
|
|
|
17114
18193
|
}
|
|
17115
18194
|
return state.orchestration.coordinatorRoutes;
|
|
17116
18195
|
}
|
|
18196
|
+
isExternalCoordinatorSession(state, coordinatorSession) {
|
|
18197
|
+
return this.ensureExternalCoordinators(state)[coordinatorSession] !== undefined;
|
|
18198
|
+
}
|
|
18199
|
+
assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSession) {
|
|
18200
|
+
if (this.isExternalCoordinatorSession(state, workerSession)) {
|
|
18201
|
+
throw new Error(`worker session "${workerSession}" conflicts with an external coordinator`);
|
|
18202
|
+
}
|
|
18203
|
+
}
|
|
18204
|
+
assertWorkerSessionAvailable(state, workerSession, excludingTaskId, options = {}) {
|
|
18205
|
+
const pendingCount = this.pendingWorkerSessions.get(workerSession) ?? 0;
|
|
18206
|
+
const allowedPendingCount = options.allowCurrentReservation ? 1 : 0;
|
|
18207
|
+
if (pendingCount > allowedPendingCount) {
|
|
18208
|
+
throw new Error(`worker session "${workerSession}" is already in use`);
|
|
18209
|
+
}
|
|
18210
|
+
if (this.hasActiveTaskWorkerSession(state, workerSession, excludingTaskId)) {
|
|
18211
|
+
throw new Error(`worker session "${workerSession}" is already in use`);
|
|
18212
|
+
}
|
|
18213
|
+
}
|
|
18214
|
+
hasActiveTaskWorkerSession(state, workerSession, excludingTaskId) {
|
|
18215
|
+
return Object.values(state.orchestration.tasks).some((task) => task.taskId !== excludingTaskId && task.workerSession === workerSession && (!this.isTerminalStatus(task.status) || task.reviewPending !== undefined));
|
|
18216
|
+
}
|
|
18217
|
+
async assertProposedWorkerSessionDoesNotConflictExternalCoordinator(workerSession) {
|
|
18218
|
+
const state = await this.deps.loadState();
|
|
18219
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSession);
|
|
18220
|
+
}
|
|
18221
|
+
ensureExternalCoordinators(state) {
|
|
18222
|
+
if (!("externalCoordinators" in state.orchestration) || !state.orchestration.externalCoordinators) {
|
|
18223
|
+
state.orchestration.externalCoordinators = {};
|
|
18224
|
+
}
|
|
18225
|
+
return state.orchestration.externalCoordinators;
|
|
18226
|
+
}
|
|
17117
18227
|
ensureGroups(state) {
|
|
17118
18228
|
if (!("groups" in state.orchestration) || !state.orchestration.groups) {
|
|
17119
18229
|
state.orchestration.groups = {};
|
|
@@ -17324,11 +18434,11 @@ class OrchestrationService {
|
|
|
17324
18434
|
});
|
|
17325
18435
|
}
|
|
17326
18436
|
async handoffQueuedQuestions(coordinatorSession, closedPackageId) {
|
|
17327
|
-
const
|
|
18437
|
+
const prepared = await this.mutate(async () => {
|
|
17328
18438
|
const state = await this.deps.loadState();
|
|
17329
18439
|
const coordinatorState = this.ensureCoordinatorQuestionState(state, coordinatorSession);
|
|
17330
18440
|
if (coordinatorState.activePackageId === closedPackageId) {
|
|
17331
|
-
return [];
|
|
18441
|
+
return { externalCoordinator: this.isExternalCoordinatorSession(state, coordinatorSession), queuedQuestions: [] };
|
|
17332
18442
|
}
|
|
17333
18443
|
const validQueuedQuestions = coordinatorState.queuedQuestions.filter((entry) => {
|
|
17334
18444
|
const task = state.orchestration.tasks[entry.taskId];
|
|
@@ -17338,9 +18448,12 @@ class OrchestrationService {
|
|
|
17338
18448
|
coordinatorState.queuedQuestions = validQueuedQuestions;
|
|
17339
18449
|
await this.deps.saveState(state);
|
|
17340
18450
|
}
|
|
17341
|
-
return
|
|
18451
|
+
return {
|
|
18452
|
+
externalCoordinator: this.isExternalCoordinatorSession(state, coordinatorSession),
|
|
18453
|
+
queuedQuestions: validQueuedQuestions
|
|
18454
|
+
};
|
|
17342
18455
|
});
|
|
17343
|
-
if (queuedQuestions.length === 0) {
|
|
18456
|
+
if (prepared.queuedQuestions.length === 0 || prepared.externalCoordinator) {
|
|
17344
18457
|
return;
|
|
17345
18458
|
}
|
|
17346
18459
|
try {
|
|
@@ -17350,12 +18463,12 @@ class OrchestrationService {
|
|
|
17350
18463
|
await this.mutate(async () => {
|
|
17351
18464
|
const state = await this.deps.loadState();
|
|
17352
18465
|
const coordinatorState = this.ensureCoordinatorQuestionState(state, coordinatorSession);
|
|
17353
|
-
coordinatorState.queuedQuestions = coordinatorState.queuedQuestions.filter((entry) => !queuedQuestions.some((queued) => queued.taskId === entry.taskId && queued.questionId === entry.questionId));
|
|
18466
|
+
coordinatorState.queuedQuestions = coordinatorState.queuedQuestions.filter((entry) => !prepared.queuedQuestions.some((queued) => queued.taskId === entry.taskId && queued.questionId === entry.questionId));
|
|
17354
18467
|
await this.deps.saveState(state);
|
|
17355
18468
|
});
|
|
17356
18469
|
} catch (error2) {
|
|
17357
18470
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
17358
|
-
await Promise.all(queuedQuestions.map(async ({ taskId, questionId }) => {
|
|
18471
|
+
await Promise.all(prepared.queuedQuestions.map(async ({ taskId, questionId }) => {
|
|
17359
18472
|
const state = await this.deps.loadState();
|
|
17360
18473
|
const task = state.orchestration.tasks[taskId];
|
|
17361
18474
|
if (!task?.openQuestion || task.openQuestion.status !== "open" || task.openQuestion.questionId !== questionId) {
|
|
@@ -17581,6 +18694,7 @@ class OrchestrationService {
|
|
|
17581
18694
|
taskId: task.taskId,
|
|
17582
18695
|
workerSession: freshTask.workerSession,
|
|
17583
18696
|
workspace: freshTask.workspace,
|
|
18697
|
+
...freshTask.cwd ? { cwd: freshTask.cwd } : {},
|
|
17584
18698
|
targetAgent: freshTask.targetAgent
|
|
17585
18699
|
});
|
|
17586
18700
|
await this.completeTaskCancellation(task.taskId);
|
|
@@ -17590,11 +18704,39 @@ class OrchestrationService {
|
|
|
17590
18704
|
})();
|
|
17591
18705
|
}
|
|
17592
18706
|
}
|
|
18707
|
+
function isTerminalTaskStatus2(status) {
|
|
18708
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
18709
|
+
}
|
|
18710
|
+
function isAttentionRequiredTask(task) {
|
|
18711
|
+
return task.reviewPending !== undefined || task.status === "pending" || task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human";
|
|
18712
|
+
}
|
|
18713
|
+
function clampWaitTimeout(timeoutMs) {
|
|
18714
|
+
if (timeoutMs === undefined) {
|
|
18715
|
+
return DEFAULT_TASK_WAIT_TIMEOUT_MS;
|
|
18716
|
+
}
|
|
18717
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
18718
|
+
return 0;
|
|
18719
|
+
}
|
|
18720
|
+
return Math.min(Math.floor(timeoutMs), MAX_TASK_WAIT_TIMEOUT_MS);
|
|
18721
|
+
}
|
|
18722
|
+
function clampPollInterval(pollIntervalMs) {
|
|
18723
|
+
if (pollIntervalMs === undefined) {
|
|
18724
|
+
return DEFAULT_TASK_WAIT_POLL_INTERVAL_MS;
|
|
18725
|
+
}
|
|
18726
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
|
|
18727
|
+
return 1;
|
|
18728
|
+
}
|
|
18729
|
+
return Math.min(Math.floor(pollIntervalMs), MAX_TASK_WAIT_POLL_INTERVAL_MS);
|
|
18730
|
+
}
|
|
18731
|
+
async function sleep2(ms) {
|
|
18732
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
18733
|
+
}
|
|
17593
18734
|
function isRequestDelegateInput(input) {
|
|
17594
18735
|
return "sourceKind" in input;
|
|
17595
18736
|
}
|
|
17596
18737
|
var init_orchestration_service = __esm(() => {
|
|
17597
18738
|
init_quota_errors();
|
|
18739
|
+
init_task_wait_timeouts();
|
|
17598
18740
|
});
|
|
17599
18741
|
|
|
17600
18742
|
// src/orchestration/worker-prompts.ts
|
|
@@ -17627,10 +18769,12 @@ class SessionService {
|
|
|
17627
18769
|
config;
|
|
17628
18770
|
stateStore;
|
|
17629
18771
|
state;
|
|
17630
|
-
|
|
18772
|
+
stateMutex;
|
|
18773
|
+
constructor(config2, stateStore, state, options = {}) {
|
|
17631
18774
|
this.config = config2;
|
|
17632
18775
|
this.stateStore = stateStore;
|
|
17633
18776
|
this.state = state;
|
|
18777
|
+
this.stateMutex = options.stateMutex ?? new AsyncMutex;
|
|
17634
18778
|
}
|
|
17635
18779
|
async createSession(alias, agent, workspace) {
|
|
17636
18780
|
return await this.createLogicalSession(alias, agent, workspace, `${workspace}:${alias}`);
|
|
@@ -17665,61 +18809,69 @@ class SessionService {
|
|
|
17665
18809
|
return preferred ? this.toResolvedSession(preferred) : null;
|
|
17666
18810
|
}
|
|
17667
18811
|
async useSession(chatKey, alias) {
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
17674
|
-
|
|
18812
|
+
await this.mutate(async () => {
|
|
18813
|
+
const session = this.state.sessions[alias];
|
|
18814
|
+
if (!session) {
|
|
18815
|
+
throw new Error(`session "${alias}" does not exist`);
|
|
18816
|
+
}
|
|
18817
|
+
session.last_used_at = new Date().toISOString();
|
|
18818
|
+
this.state.chat_contexts[chatKey] = { current_session: alias };
|
|
18819
|
+
await this.persist();
|
|
18820
|
+
});
|
|
17675
18821
|
}
|
|
17676
18822
|
async setCurrentSessionMode(chatKey, modeId) {
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
17692
|
-
|
|
18823
|
+
await this.mutate(async () => {
|
|
18824
|
+
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
18825
|
+
if (!currentAlias) {
|
|
18826
|
+
throw new Error("no current session selected");
|
|
18827
|
+
}
|
|
18828
|
+
const session = this.state.sessions[currentAlias];
|
|
18829
|
+
if (!session) {
|
|
18830
|
+
throw new Error("no current session selected");
|
|
18831
|
+
}
|
|
18832
|
+
const normalizedModeId = modeId?.trim();
|
|
18833
|
+
if (normalizedModeId) {
|
|
18834
|
+
session.mode_id = normalizedModeId;
|
|
18835
|
+
} else {
|
|
18836
|
+
delete session.mode_id;
|
|
18837
|
+
}
|
|
18838
|
+
session.last_used_at = new Date().toISOString();
|
|
18839
|
+
await this.persist();
|
|
18840
|
+
});
|
|
17693
18841
|
}
|
|
17694
18842
|
async setCurrentSessionReplyMode(chatKey, replyMode) {
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
|
|
17698
|
-
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
18843
|
+
await this.mutate(async () => {
|
|
18844
|
+
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
18845
|
+
if (!currentAlias) {
|
|
18846
|
+
throw new Error("no current session selected");
|
|
18847
|
+
}
|
|
18848
|
+
const session = this.state.sessions[currentAlias];
|
|
18849
|
+
if (!session) {
|
|
18850
|
+
throw new Error("no current session selected");
|
|
18851
|
+
}
|
|
18852
|
+
if (replyMode) {
|
|
18853
|
+
session.reply_mode = replyMode;
|
|
18854
|
+
} else {
|
|
18855
|
+
delete session.reply_mode;
|
|
18856
|
+
}
|
|
18857
|
+
session.last_used_at = new Date().toISOString();
|
|
18858
|
+
await this.persist();
|
|
18859
|
+
});
|
|
17710
18860
|
}
|
|
17711
18861
|
async getCurrentSession(chatKey) {
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
18862
|
+
return await this.mutate(async () => {
|
|
18863
|
+
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
18864
|
+
if (!currentAlias) {
|
|
18865
|
+
return null;
|
|
18866
|
+
}
|
|
18867
|
+
const session = this.state.sessions[currentAlias];
|
|
18868
|
+
if (!session) {
|
|
18869
|
+
return null;
|
|
18870
|
+
}
|
|
18871
|
+
session.last_used_at = new Date().toISOString();
|
|
18872
|
+
await this.persist();
|
|
18873
|
+
return this.toResolvedSession(session);
|
|
18874
|
+
});
|
|
17723
18875
|
}
|
|
17724
18876
|
async listSessions(chatKey) {
|
|
17725
18877
|
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
@@ -17744,19 +18896,21 @@ class SessionService {
|
|
|
17744
18896
|
return count;
|
|
17745
18897
|
}
|
|
17746
18898
|
async removeSession(alias) {
|
|
17747
|
-
|
|
17748
|
-
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
const wasActive = Object.values(this.state.chat_contexts).some((ctx) => ctx.current_session === alias);
|
|
17752
|
-
delete this.state.sessions[alias];
|
|
17753
|
-
for (const [chatKey, ctx] of Object.entries(this.state.chat_contexts)) {
|
|
17754
|
-
if (ctx.current_session === alias) {
|
|
17755
|
-
delete this.state.chat_contexts[chatKey];
|
|
18899
|
+
return await this.mutate(async () => {
|
|
18900
|
+
const session = this.state.sessions[alias];
|
|
18901
|
+
if (!session) {
|
|
18902
|
+
throw new Error(`session "${alias}" does not exist`);
|
|
17756
18903
|
}
|
|
17757
|
-
|
|
17758
|
-
|
|
17759
|
-
|
|
18904
|
+
const wasActive = Object.values(this.state.chat_contexts).some((ctx) => ctx.current_session === alias);
|
|
18905
|
+
delete this.state.sessions[alias];
|
|
18906
|
+
for (const [chatKey, ctx] of Object.entries(this.state.chat_contexts)) {
|
|
18907
|
+
if (ctx.current_session === alias) {
|
|
18908
|
+
delete this.state.chat_contexts[chatKey];
|
|
18909
|
+
}
|
|
18910
|
+
}
|
|
18911
|
+
await this.persist();
|
|
18912
|
+
return { wasActive };
|
|
18913
|
+
});
|
|
17760
18914
|
}
|
|
17761
18915
|
toResolvedSession(session) {
|
|
17762
18916
|
const agentConfig = this.config.agents[session.agent];
|
|
@@ -17779,41 +18933,51 @@ class SessionService {
|
|
|
17779
18933
|
};
|
|
17780
18934
|
}
|
|
17781
18935
|
async setSessionTransportAgentCommand(alias, transportAgentCommand) {
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
18936
|
+
await this.mutate(async () => {
|
|
18937
|
+
const session = this.state.sessions[alias];
|
|
18938
|
+
if (!session) {
|
|
18939
|
+
throw new Error(`session "${alias}" does not exist`);
|
|
18940
|
+
}
|
|
18941
|
+
const normalized = transportAgentCommand?.trim();
|
|
18942
|
+
if (normalized) {
|
|
18943
|
+
session.transport_agent_command = normalized;
|
|
18944
|
+
} else {
|
|
18945
|
+
delete session.transport_agent_command;
|
|
18946
|
+
}
|
|
18947
|
+
session.last_used_at = new Date().toISOString();
|
|
18948
|
+
await this.persist();
|
|
18949
|
+
});
|
|
18950
|
+
}
|
|
18951
|
+
async mutate(critical) {
|
|
18952
|
+
return await this.stateMutex.run(critical);
|
|
17794
18953
|
}
|
|
17795
18954
|
async persist() {
|
|
17796
18955
|
await this.stateStore.save(this.state);
|
|
17797
18956
|
}
|
|
17798
18957
|
async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
|
|
17799
|
-
this.
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
alias
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
18958
|
+
return await this.mutate(async () => {
|
|
18959
|
+
this.validateSession(alias, agent, workspace);
|
|
18960
|
+
if (this.state.orchestration.externalCoordinators[transportSession]) {
|
|
18961
|
+
throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
|
|
18962
|
+
}
|
|
18963
|
+
const existingSession = this.state.sessions[alias];
|
|
18964
|
+
const now = new Date().toISOString();
|
|
18965
|
+
const normalizedTransportAgentCommand = transportAgentCommand?.trim();
|
|
18966
|
+
const session = {
|
|
18967
|
+
alias,
|
|
18968
|
+
agent,
|
|
18969
|
+
workspace,
|
|
18970
|
+
transport_session: transportSession,
|
|
18971
|
+
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
|
|
18972
|
+
mode_id: existingSession?.mode_id,
|
|
18973
|
+
reply_mode: existingSession?.reply_mode,
|
|
18974
|
+
created_at: existingSession?.created_at ?? now,
|
|
18975
|
+
last_used_at: now
|
|
18976
|
+
};
|
|
18977
|
+
this.state.sessions[alias] = session;
|
|
18978
|
+
await this.persist();
|
|
18979
|
+
return this.toResolvedSession(session);
|
|
18980
|
+
});
|
|
17817
18981
|
}
|
|
17818
18982
|
validateSession(alias, agent, workspace) {
|
|
17819
18983
|
if (alias.trim().length === 0) {
|
|
@@ -17835,295 +18999,6 @@ class SessionService {
|
|
|
17835
18999
|
}
|
|
17836
19000
|
var init_session_service = () => {};
|
|
17837
19001
|
|
|
17838
|
-
// src/orchestration/orchestration-types.ts
|
|
17839
|
-
function createEmptyOrchestrationState() {
|
|
17840
|
-
return {
|
|
17841
|
-
tasks: {},
|
|
17842
|
-
workerBindings: {},
|
|
17843
|
-
groups: {},
|
|
17844
|
-
humanQuestionPackages: {},
|
|
17845
|
-
coordinatorQuestionState: {},
|
|
17846
|
-
coordinatorRoutes: {}
|
|
17847
|
-
};
|
|
17848
|
-
}
|
|
17849
|
-
|
|
17850
|
-
// src/state/types.ts
|
|
17851
|
-
function createEmptyState() {
|
|
17852
|
-
return {
|
|
17853
|
-
sessions: {},
|
|
17854
|
-
chat_contexts: {},
|
|
17855
|
-
orchestration: createEmptyOrchestrationState()
|
|
17856
|
-
};
|
|
17857
|
-
}
|
|
17858
|
-
var init_types2 = () => {};
|
|
17859
|
-
|
|
17860
|
-
// src/state/state-store.ts
|
|
17861
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
17862
|
-
function isRecord2(value) {
|
|
17863
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17864
|
-
}
|
|
17865
|
-
function isString(value) {
|
|
17866
|
-
return typeof value === "string";
|
|
17867
|
-
}
|
|
17868
|
-
function isOptionalString(value) {
|
|
17869
|
-
return value === undefined || typeof value === "string";
|
|
17870
|
-
}
|
|
17871
|
-
function isOptionalBoolean(value) {
|
|
17872
|
-
return value === undefined || typeof value === "boolean";
|
|
17873
|
-
}
|
|
17874
|
-
function isTaskStatus(value) {
|
|
17875
|
-
return value === "pending" || value === "needs_confirmation" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
|
|
17876
|
-
}
|
|
17877
|
-
function isSourceKind(value) {
|
|
17878
|
-
return value === "human" || value === "coordinator" || value === "worker";
|
|
17879
|
-
}
|
|
17880
|
-
function isOpenQuestionRecord(value) {
|
|
17881
|
-
if (!isRecord2(value)) {
|
|
17882
|
-
return false;
|
|
17883
|
-
}
|
|
17884
|
-
return isString(value.questionId) && isString(value.question) && isString(value.whyBlocked) && isString(value.whatIsNeeded) && isString(value.askedAt) && (value.status === "open" || value.status === "answered" || value.status === "superseded") && isOptionalString(value.answeredAt) && (value.answerSource === undefined || value.answerSource === "coordinator" || value.answerSource === "human") && isOptionalString(value.answerText) && isOptionalString(value.packageId) && isOptionalString(value.lastWakeError) && isOptionalString(value.lastResumeError);
|
|
17885
|
-
}
|
|
17886
|
-
function isReviewPendingRecord(value) {
|
|
17887
|
-
if (!isRecord2(value)) {
|
|
17888
|
-
return false;
|
|
17889
|
-
}
|
|
17890
|
-
return isString(value.reviewId) && value.reason === "misrouted_answer" && isString(value.createdAt) && isString(value.resultId) && isString(value.resultText);
|
|
17891
|
-
}
|
|
17892
|
-
function isCorrectionPendingRecord(value) {
|
|
17893
|
-
if (!isRecord2(value)) {
|
|
17894
|
-
return false;
|
|
17895
|
-
}
|
|
17896
|
-
return isString(value.requestedAt) && value.reason === "misrouted_answer";
|
|
17897
|
-
}
|
|
17898
|
-
function isTaskRecord(value) {
|
|
17899
|
-
if (!isRecord2(value)) {
|
|
17900
|
-
return false;
|
|
17901
|
-
}
|
|
17902
|
-
return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending));
|
|
17903
|
-
}
|
|
17904
|
-
function isWorkerBindingRecord(value) {
|
|
17905
|
-
if (!isRecord2(value)) {
|
|
17906
|
-
return false;
|
|
17907
|
-
}
|
|
17908
|
-
return isString(value.sourceHandle) && isString(value.coordinatorSession) && isString(value.workspace) && isString(value.targetAgent) && isOptionalString(value.role);
|
|
17909
|
-
}
|
|
17910
|
-
function isGroupRecord(value) {
|
|
17911
|
-
if (!isRecord2(value)) {
|
|
17912
|
-
return false;
|
|
17913
|
-
}
|
|
17914
|
-
return isString(value.groupId) && isString(value.coordinatorSession) && isString(value.title) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.coordinatorInjectedAt) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError);
|
|
17915
|
-
}
|
|
17916
|
-
function isQueuedQuestionRecord(value) {
|
|
17917
|
-
if (!isRecord2(value)) {
|
|
17918
|
-
return false;
|
|
17919
|
-
}
|
|
17920
|
-
return isString(value.taskId) && isString(value.questionId) && isString(value.enqueuedAt);
|
|
17921
|
-
}
|
|
17922
|
-
function isCoordinatorQuestionStateRecord(value) {
|
|
17923
|
-
if (!isRecord2(value)) {
|
|
17924
|
-
return false;
|
|
17925
|
-
}
|
|
17926
|
-
const queuedQuestions = value.queuedQuestions;
|
|
17927
|
-
if (queuedQuestions !== undefined && !Array.isArray(queuedQuestions)) {
|
|
17928
|
-
return false;
|
|
17929
|
-
}
|
|
17930
|
-
return (value.activePackageId === undefined || isString(value.activePackageId)) && (queuedQuestions === undefined || queuedQuestions.every(isQueuedQuestionRecord));
|
|
17931
|
-
}
|
|
17932
|
-
function isCoordinatorRouteContextRecord(value) {
|
|
17933
|
-
if (!isRecord2(value)) {
|
|
17934
|
-
return false;
|
|
17935
|
-
}
|
|
17936
|
-
return isString(value.coordinatorSession) && isString(value.chatKey) && isOptionalString(value.accountId) && isOptionalString(value.replyContextToken) && isString(value.updatedAt);
|
|
17937
|
-
}
|
|
17938
|
-
function isHumanQuestionPackageMessageRecord(value) {
|
|
17939
|
-
if (!isRecord2(value)) {
|
|
17940
|
-
return false;
|
|
17941
|
-
}
|
|
17942
|
-
return isString(value.messageId) && (value.kind === "initial" || value.kind === "follow_up") && isString(value.promptText) && isString(value.createdAt) && isOptionalString(value.deliveredAt) && isOptionalString(value.deliveredChatKey) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.lastDeliveryError);
|
|
17943
|
-
}
|
|
17944
|
-
function isHumanQuestionPackageRecord(value) {
|
|
17945
|
-
if (!isRecord2(value)) {
|
|
17946
|
-
return false;
|
|
17947
|
-
}
|
|
17948
|
-
const initialTaskIds = value.initialTaskIds;
|
|
17949
|
-
const openTaskIds = value.openTaskIds;
|
|
17950
|
-
const resolvedTaskIds = value.resolvedTaskIds;
|
|
17951
|
-
const messages = value.messages;
|
|
17952
|
-
return isString(value.packageId) && isString(value.coordinatorSession) && (value.status === "active" || value.status === "closed") && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.closedAt) && Array.isArray(initialTaskIds) && initialTaskIds.every(isString) && Array.isArray(openTaskIds) && openTaskIds.every(isString) && Array.isArray(resolvedTaskIds) && resolvedTaskIds.every(isString) && Array.isArray(messages) && messages.every(isHumanQuestionPackageMessageRecord) && isOptionalString(value.awaitingReplyMessageId);
|
|
17953
|
-
}
|
|
17954
|
-
function parseOrchestrationState(raw, path11) {
|
|
17955
|
-
if (raw === undefined) {
|
|
17956
|
-
return createEmptyOrchestrationState();
|
|
17957
|
-
}
|
|
17958
|
-
if (!isRecord2(raw)) {
|
|
17959
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration"`);
|
|
17960
|
-
}
|
|
17961
|
-
const tasks = raw.tasks;
|
|
17962
|
-
if (tasks !== undefined && !isRecord2(tasks)) {
|
|
17963
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration.tasks"`);
|
|
17964
|
-
}
|
|
17965
|
-
const workerBindings = raw.workerBindings;
|
|
17966
|
-
if (workerBindings !== undefined && !isRecord2(workerBindings)) {
|
|
17967
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration.workerBindings"`);
|
|
17968
|
-
}
|
|
17969
|
-
const groups = raw.groups;
|
|
17970
|
-
if (groups !== undefined && !isRecord2(groups)) {
|
|
17971
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration.groups"`);
|
|
17972
|
-
}
|
|
17973
|
-
const humanQuestionPackages = raw.humanQuestionPackages;
|
|
17974
|
-
if (humanQuestionPackages !== undefined && !isRecord2(humanQuestionPackages)) {
|
|
17975
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration.humanQuestionPackages"`);
|
|
17976
|
-
}
|
|
17977
|
-
const coordinatorQuestionState = raw.coordinatorQuestionState;
|
|
17978
|
-
if (coordinatorQuestionState !== undefined && !isRecord2(coordinatorQuestionState)) {
|
|
17979
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration.coordinatorQuestionState"`);
|
|
17980
|
-
}
|
|
17981
|
-
const coordinatorRoutes = raw.coordinatorRoutes;
|
|
17982
|
-
if (coordinatorRoutes !== undefined && !isRecord2(coordinatorRoutes)) {
|
|
17983
|
-
throw new Error(`state file "${path11}" must contain an object field "orchestration.coordinatorRoutes"`);
|
|
17984
|
-
}
|
|
17985
|
-
const parsedTasks = {};
|
|
17986
|
-
for (const [taskId, task] of Object.entries(tasks ?? {})) {
|
|
17987
|
-
if (!isTaskRecord(task)) {
|
|
17988
|
-
throw new Error(`state file "${path11}" contains an invalid orchestration task at "${taskId}"`);
|
|
17989
|
-
}
|
|
17990
|
-
parsedTasks[taskId] = task;
|
|
17991
|
-
}
|
|
17992
|
-
const parsedWorkerBindings = {};
|
|
17993
|
-
for (const [workerSession, binding] of Object.entries(workerBindings ?? {})) {
|
|
17994
|
-
if (!isWorkerBindingRecord(binding)) {
|
|
17995
|
-
throw new Error(`state file "${path11}" contains an invalid orchestration worker binding at "${workerSession}"`);
|
|
17996
|
-
}
|
|
17997
|
-
parsedWorkerBindings[workerSession] = binding;
|
|
17998
|
-
}
|
|
17999
|
-
const parsedGroups = {};
|
|
18000
|
-
for (const [groupId, group] of Object.entries(groups ?? {})) {
|
|
18001
|
-
if (!isGroupRecord(group)) {
|
|
18002
|
-
throw new Error(`state file "${path11}" contains an invalid orchestration group at "${groupId}"`);
|
|
18003
|
-
}
|
|
18004
|
-
parsedGroups[groupId] = group;
|
|
18005
|
-
}
|
|
18006
|
-
const parsedHumanQuestionPackages = {};
|
|
18007
|
-
for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages ?? {})) {
|
|
18008
|
-
if (!isHumanQuestionPackageRecord(packageRecord)) {
|
|
18009
|
-
throw new Error(`state file "${path11}" contains an invalid human question package at "${packageId}"`);
|
|
18010
|
-
}
|
|
18011
|
-
parsedHumanQuestionPackages[packageId] = packageRecord;
|
|
18012
|
-
}
|
|
18013
|
-
const parsedCoordinatorQuestionState = {};
|
|
18014
|
-
for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState ?? {})) {
|
|
18015
|
-
if (!isCoordinatorQuestionStateRecord(questionState)) {
|
|
18016
|
-
throw new Error(`state file "${path11}" contains an invalid coordinator question state at "${coordinatorSession}"`);
|
|
18017
|
-
}
|
|
18018
|
-
parsedCoordinatorQuestionState[coordinatorSession] = {
|
|
18019
|
-
activePackageId: questionState.activePackageId,
|
|
18020
|
-
queuedQuestions: (questionState.queuedQuestions ?? []).map((question) => ({ ...question }))
|
|
18021
|
-
};
|
|
18022
|
-
}
|
|
18023
|
-
const parsedCoordinatorRoutes = {};
|
|
18024
|
-
for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes ?? {})) {
|
|
18025
|
-
if (!isCoordinatorRouteContextRecord(route)) {
|
|
18026
|
-
throw new Error(`state file "${path11}" contains an invalid coordinator route at "${coordinatorSession}"`);
|
|
18027
|
-
}
|
|
18028
|
-
parsedCoordinatorRoutes[coordinatorSession] = route;
|
|
18029
|
-
}
|
|
18030
|
-
return {
|
|
18031
|
-
tasks: parsedTasks,
|
|
18032
|
-
workerBindings: parsedWorkerBindings,
|
|
18033
|
-
groups: parsedGroups,
|
|
18034
|
-
humanQuestionPackages: parsedHumanQuestionPackages,
|
|
18035
|
-
coordinatorQuestionState: parsedCoordinatorQuestionState,
|
|
18036
|
-
coordinatorRoutes: parsedCoordinatorRoutes
|
|
18037
|
-
};
|
|
18038
|
-
}
|
|
18039
|
-
function isReplyMode(value) {
|
|
18040
|
-
return value === "stream" || value === "final" || value === "verbose";
|
|
18041
|
-
}
|
|
18042
|
-
function isSessionRecord(value) {
|
|
18043
|
-
if (!isRecord2(value)) {
|
|
18044
|
-
return false;
|
|
18045
|
-
}
|
|
18046
|
-
return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
|
|
18047
|
-
}
|
|
18048
|
-
function parseSessions(raw, path11) {
|
|
18049
|
-
const sessions = {};
|
|
18050
|
-
for (const [alias, value] of Object.entries(raw)) {
|
|
18051
|
-
if (!isSessionRecord(value)) {
|
|
18052
|
-
throw new Error(`state file "${path11}" contains malformed session record "${alias}"`);
|
|
18053
|
-
}
|
|
18054
|
-
sessions[alias] = value;
|
|
18055
|
-
}
|
|
18056
|
-
return sessions;
|
|
18057
|
-
}
|
|
18058
|
-
function isChatContextRecord(value) {
|
|
18059
|
-
return isRecord2(value) && isString(value.current_session);
|
|
18060
|
-
}
|
|
18061
|
-
function parseChatContexts(raw, path11) {
|
|
18062
|
-
const chatContexts = {};
|
|
18063
|
-
for (const [chatKey, value] of Object.entries(raw)) {
|
|
18064
|
-
if (!isChatContextRecord(value)) {
|
|
18065
|
-
throw new Error(`state file "${path11}" contains malformed chat context record "${chatKey}"`);
|
|
18066
|
-
}
|
|
18067
|
-
chatContexts[chatKey] = value;
|
|
18068
|
-
}
|
|
18069
|
-
return chatContexts;
|
|
18070
|
-
}
|
|
18071
|
-
function parseState(raw, path11) {
|
|
18072
|
-
if (!isRecord2(raw)) {
|
|
18073
|
-
throw new Error(`state file "${path11}" must contain a JSON object`);
|
|
18074
|
-
}
|
|
18075
|
-
const sessions = raw.sessions;
|
|
18076
|
-
if (!isRecord2(sessions)) {
|
|
18077
|
-
throw new Error(`state file "${path11}" must contain an object field "sessions"`);
|
|
18078
|
-
}
|
|
18079
|
-
const chatContexts = raw.chat_contexts;
|
|
18080
|
-
if (!isRecord2(chatContexts)) {
|
|
18081
|
-
throw new Error(`state file "${path11}" must contain an object field "chat_contexts"`);
|
|
18082
|
-
}
|
|
18083
|
-
const orchestration = parseOrchestrationState(raw.orchestration, path11);
|
|
18084
|
-
return {
|
|
18085
|
-
sessions: parseSessions(sessions, path11),
|
|
18086
|
-
chat_contexts: parseChatContexts(chatContexts, path11),
|
|
18087
|
-
orchestration
|
|
18088
|
-
};
|
|
18089
|
-
}
|
|
18090
|
-
|
|
18091
|
-
class StateStore {
|
|
18092
|
-
path;
|
|
18093
|
-
constructor(path11) {
|
|
18094
|
-
this.path = path11;
|
|
18095
|
-
}
|
|
18096
|
-
async load() {
|
|
18097
|
-
try {
|
|
18098
|
-
const content = await readFile7(this.path, "utf8");
|
|
18099
|
-
if (content.trim() === "") {
|
|
18100
|
-
return createEmptyState();
|
|
18101
|
-
}
|
|
18102
|
-
let parsed;
|
|
18103
|
-
try {
|
|
18104
|
-
parsed = JSON.parse(content);
|
|
18105
|
-
} catch (error2) {
|
|
18106
|
-
throw new Error(`failed to parse state file "${this.path}"`, {
|
|
18107
|
-
cause: error2
|
|
18108
|
-
});
|
|
18109
|
-
}
|
|
18110
|
-
return parseState(parsed, this.path);
|
|
18111
|
-
} catch (error2) {
|
|
18112
|
-
if (error2.code === "ENOENT") {
|
|
18113
|
-
return createEmptyState();
|
|
18114
|
-
}
|
|
18115
|
-
throw error2;
|
|
18116
|
-
}
|
|
18117
|
-
}
|
|
18118
|
-
async save(state) {
|
|
18119
|
-
await writePrivateFileAtomic(this.path, JSON.stringify(state, null, 2));
|
|
18120
|
-
}
|
|
18121
|
-
}
|
|
18122
|
-
var init_state_store = __esm(() => {
|
|
18123
|
-
init_private_file();
|
|
18124
|
-
init_types2();
|
|
18125
|
-
});
|
|
18126
|
-
|
|
18127
19002
|
// src/run-console.ts
|
|
18128
19003
|
var exports_run_console = {};
|
|
18129
19004
|
__export(exports_run_console, {
|
|
@@ -18700,19 +19575,30 @@ class AcpxBridgeTransport {
|
|
|
18700
19575
|
}
|
|
18701
19576
|
} : undefined);
|
|
18702
19577
|
}
|
|
18703
|
-
async prompt(session, text, reply, replyContext) {
|
|
19578
|
+
async prompt(session, text, reply, replyContext, options) {
|
|
18704
19579
|
const sink = reply ? createQuotaGatedReplySink({
|
|
18705
19580
|
reply,
|
|
18706
19581
|
...replyContext ? { replyContext } : {}
|
|
18707
19582
|
}) : null;
|
|
19583
|
+
let segmentError;
|
|
19584
|
+
let segmentChain = Promise.resolve();
|
|
18708
19585
|
const result = await this.client.request("prompt", {
|
|
18709
19586
|
...this.toParams(session),
|
|
18710
|
-
text
|
|
19587
|
+
text,
|
|
19588
|
+
...options?.media ? { media: options.media } : {}
|
|
18711
19589
|
}, (event) => {
|
|
18712
19590
|
if (event.type === "prompt.segment") {
|
|
19591
|
+
const onSegment = options?.onSegment;
|
|
19592
|
+
if (onSegment) {
|
|
19593
|
+
const segmentText = event.text;
|
|
19594
|
+
segmentChain = segmentChain.then(() => onSegment(segmentText)).catch((error2) => {
|
|
19595
|
+
segmentError ??= error2;
|
|
19596
|
+
});
|
|
19597
|
+
}
|
|
18713
19598
|
sink?.feedSegment(event.text);
|
|
18714
19599
|
}
|
|
18715
19600
|
});
|
|
19601
|
+
await segmentChain;
|
|
18716
19602
|
if (sink) {
|
|
18717
19603
|
const { overflowCount } = sink.finalize();
|
|
18718
19604
|
await sink.drain({ timeoutMs: 30000 });
|
|
@@ -18721,10 +19607,16 @@ class AcpxBridgeTransport {
|
|
|
18721
19607
|
throw deferred;
|
|
18722
19608
|
}
|
|
18723
19609
|
const summary = buildOverflowSummary(overflowCount);
|
|
19610
|
+
if (segmentError) {
|
|
19611
|
+
throw segmentError;
|
|
19612
|
+
}
|
|
18724
19613
|
return { text: summary ? `${summary}
|
|
18725
19614
|
|
|
18726
19615
|
${result.text}` : "" };
|
|
18727
19616
|
}
|
|
19617
|
+
if (segmentError) {
|
|
19618
|
+
throw segmentError;
|
|
19619
|
+
}
|
|
18728
19620
|
return result;
|
|
18729
19621
|
}
|
|
18730
19622
|
async setMode(session, modeId) {
|
|
@@ -18780,6 +19672,115 @@ var init_spawn_command = __esm(() => {
|
|
|
18780
19672
|
SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
|
|
18781
19673
|
});
|
|
18782
19674
|
|
|
19675
|
+
// src/transport/prompt-media.ts
|
|
19676
|
+
import { mkdtemp, open as open3, rm as rm7, writeFile as writeFile6 } from "node:fs/promises";
|
|
19677
|
+
import { tmpdir as defaultTmpdir } from "node:os";
|
|
19678
|
+
import path11 from "node:path";
|
|
19679
|
+
async function createStructuredPromptFile(text, media, deps = defaultStructuredPromptFileDeps) {
|
|
19680
|
+
if (!media) {
|
|
19681
|
+
return null;
|
|
19682
|
+
}
|
|
19683
|
+
if (media.type !== "image") {
|
|
19684
|
+
throw new Error("prompt media type is not supported; only image media is supported");
|
|
19685
|
+
}
|
|
19686
|
+
const imageData = await deps.readImageFile(media.filePath, MAX_STRUCTURED_IMAGE_BYTES);
|
|
19687
|
+
if (imageData.byteLength === 0) {
|
|
19688
|
+
throw new Error("image prompt must not be empty");
|
|
19689
|
+
}
|
|
19690
|
+
if (imageData.byteLength > MAX_STRUCTURED_IMAGE_BYTES) {
|
|
19691
|
+
throw new Error(`image prompt exceeds ${MAX_STRUCTURED_IMAGE_BYTES} bytes`);
|
|
19692
|
+
}
|
|
19693
|
+
const blocks = [];
|
|
19694
|
+
if (text.trim().length > 0) {
|
|
19695
|
+
blocks.push({ type: "text", text });
|
|
19696
|
+
}
|
|
19697
|
+
blocks.push({
|
|
19698
|
+
type: "image",
|
|
19699
|
+
mimeType: resolveImageMimeType(imageData, media.mimeType),
|
|
19700
|
+
data: imageData.toString("base64")
|
|
19701
|
+
});
|
|
19702
|
+
let dir = "";
|
|
19703
|
+
try {
|
|
19704
|
+
dir = await deps.mkdtemp(path11.join(deps.tmpdir(), "weacpx-acp-prompt-"));
|
|
19705
|
+
const filePath = path11.join(dir, "prompt.json");
|
|
19706
|
+
await deps.writeFile(filePath, JSON.stringify(blocks), "utf8");
|
|
19707
|
+
return {
|
|
19708
|
+
filePath,
|
|
19709
|
+
cleanup: async () => {
|
|
19710
|
+
await deps.rm(dir, { recursive: true, force: true });
|
|
19711
|
+
}
|
|
19712
|
+
};
|
|
19713
|
+
} catch (error2) {
|
|
19714
|
+
if (dir) {
|
|
19715
|
+
try {
|
|
19716
|
+
await deps.rm(dir, { recursive: true, force: true });
|
|
19717
|
+
} catch {}
|
|
19718
|
+
}
|
|
19719
|
+
throw error2;
|
|
19720
|
+
}
|
|
19721
|
+
}
|
|
19722
|
+
async function readImageFileBounded(filePath, maxBytes) {
|
|
19723
|
+
const handle = await open3(filePath, "r");
|
|
19724
|
+
try {
|
|
19725
|
+
const imageStats = await handle.stat();
|
|
19726
|
+
if (!imageStats.isFile()) {
|
|
19727
|
+
throw new Error("image prompt path must be a regular file");
|
|
19728
|
+
}
|
|
19729
|
+
if (imageStats.size > maxBytes) {
|
|
19730
|
+
throw new Error(`image prompt exceeds ${maxBytes} bytes`);
|
|
19731
|
+
}
|
|
19732
|
+
const chunks = [];
|
|
19733
|
+
let total = 0;
|
|
19734
|
+
let position = 0;
|
|
19735
|
+
const chunkSize = 1024 * 1024;
|
|
19736
|
+
while (total <= maxBytes) {
|
|
19737
|
+
const buffer = Buffer.allocUnsafe(Math.min(chunkSize, maxBytes + 1 - total));
|
|
19738
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
19739
|
+
if (bytesRead === 0)
|
|
19740
|
+
break;
|
|
19741
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
19742
|
+
total += bytesRead;
|
|
19743
|
+
position += bytesRead;
|
|
19744
|
+
}
|
|
19745
|
+
return Buffer.concat(chunks, total);
|
|
19746
|
+
} finally {
|
|
19747
|
+
await handle.close();
|
|
19748
|
+
}
|
|
19749
|
+
}
|
|
19750
|
+
function resolveImageMimeType(buffer, declaredMimeType) {
|
|
19751
|
+
if (/^image\/[A-Za-z0-9.+-]+$/.test(declaredMimeType) && declaredMimeType !== "image/*") {
|
|
19752
|
+
return declaredMimeType;
|
|
19753
|
+
}
|
|
19754
|
+
if (buffer.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))) {
|
|
19755
|
+
return "image/png";
|
|
19756
|
+
}
|
|
19757
|
+
if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
19758
|
+
return "image/jpeg";
|
|
19759
|
+
}
|
|
19760
|
+
const header6 = buffer.subarray(0, 6).toString("ascii");
|
|
19761
|
+
if (header6 === "GIF87a" || header6 === "GIF89a") {
|
|
19762
|
+
return "image/gif";
|
|
19763
|
+
}
|
|
19764
|
+
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
19765
|
+
return "image/webp";
|
|
19766
|
+
}
|
|
19767
|
+
if (buffer.length >= 2 && buffer.subarray(0, 2).toString("ascii") === "BM") {
|
|
19768
|
+
return "image/bmp";
|
|
19769
|
+
}
|
|
19770
|
+
return "image/png";
|
|
19771
|
+
}
|
|
19772
|
+
var MAX_STRUCTURED_IMAGE_BYTES, defaultStructuredPromptFileDeps;
|
|
19773
|
+
var init_prompt_media = __esm(() => {
|
|
19774
|
+
MAX_STRUCTURED_IMAGE_BYTES = 100 * 1024 * 1024;
|
|
19775
|
+
defaultStructuredPromptFileDeps = {
|
|
19776
|
+
readImageFile: readImageFileBounded,
|
|
19777
|
+
mkdtemp,
|
|
19778
|
+
writeFile: writeFile6,
|
|
19779
|
+
rm: rm7,
|
|
19780
|
+
tmpdir: defaultTmpdir
|
|
19781
|
+
};
|
|
19782
|
+
});
|
|
19783
|
+
|
|
18783
19784
|
// src/transport/streaming-prompt.ts
|
|
18784
19785
|
function createStreamingPromptState(formatToolCalls = false) {
|
|
18785
19786
|
return {
|
|
@@ -18945,7 +19946,7 @@ async function ensureNodePtyHelperExecutable(helperPath, chmod2 = chmodFs) {
|
|
|
18945
19946
|
var init_node_pty_helper = () => {};
|
|
18946
19947
|
|
|
18947
19948
|
// src/transport/acpx-queue-owner-launcher.ts
|
|
18948
|
-
import { createHash as
|
|
19949
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
18949
19950
|
import { spawn as spawn6 } from "node:child_process";
|
|
18950
19951
|
import { readFile as readFile8, unlink as unlink2 } from "node:fs/promises";
|
|
18951
19952
|
import { homedir as homedir7 } from "node:os";
|
|
@@ -19114,7 +20115,7 @@ function queueLockFilePath(sessionId) {
|
|
|
19114
20115
|
return join9(homedir7(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
|
|
19115
20116
|
}
|
|
19116
20117
|
function shortHash(value, length) {
|
|
19117
|
-
return
|
|
20118
|
+
return createHash3("sha256").update(value).digest("hex").slice(0, length);
|
|
19118
20119
|
}
|
|
19119
20120
|
function resolveDefaultWeacpxCommand(env) {
|
|
19120
20121
|
if (env.WEACPX_CLI_COMMAND?.trim()) {
|
|
@@ -19250,20 +20251,30 @@ class AcpxCliTransport {
|
|
|
19250
20251
|
timeoutMs: this.sessionInitTimeoutMs
|
|
19251
20252
|
});
|
|
19252
20253
|
}
|
|
19253
|
-
async prompt(session, text, reply, replyContext) {
|
|
20254
|
+
async prompt(session, text, reply, replyContext, options) {
|
|
19254
20255
|
await this.launchMcpQueueOwnerIfNeeded(session);
|
|
19255
|
-
const
|
|
19256
|
-
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
20256
|
+
const structuredPrompt = await createStructuredPromptFile(text, options?.media);
|
|
20257
|
+
const args = this.buildPromptArgs(session, text, structuredPrompt?.filePath);
|
|
20258
|
+
try {
|
|
20259
|
+
if (reply || options?.onSegment) {
|
|
20260
|
+
const formatToolCalls = session.replyMode === "verbose";
|
|
20261
|
+
const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, 30000, formatToolCalls, replyContext, options?.onSegment);
|
|
20262
|
+
const baseText = getPromptText(result2);
|
|
20263
|
+
if (!reply) {
|
|
20264
|
+
return { text: baseText };
|
|
20265
|
+
}
|
|
20266
|
+
const summary = buildOverflowSummary(overflowCount);
|
|
20267
|
+
return { text: summary ? `${summary}
|
|
19262
20268
|
|
|
19263
20269
|
${baseText}` : "" };
|
|
20270
|
+
}
|
|
20271
|
+
const result = await this.runCommand(this.command, args);
|
|
20272
|
+
return { text: getPromptText(result) };
|
|
20273
|
+
} finally {
|
|
20274
|
+
try {
|
|
20275
|
+
await structuredPrompt?.cleanup();
|
|
20276
|
+
} catch {}
|
|
19264
20277
|
}
|
|
19265
|
-
const result = await this.runCommand(this.command, args);
|
|
19266
|
-
return { text: getPromptText(result) };
|
|
19267
20278
|
}
|
|
19268
20279
|
async setMode(session, modeId) {
|
|
19269
20280
|
await this.run(this.buildArgs(session, [
|
|
@@ -19384,7 +20395,7 @@ ${baseText}` : "" };
|
|
|
19384
20395
|
})
|
|
19385
20396
|
]);
|
|
19386
20397
|
}
|
|
19387
|
-
async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000, formatToolCalls = false, replyContext) {
|
|
20398
|
+
async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000, formatToolCalls = false, replyContext, onSegment) {
|
|
19388
20399
|
return await new Promise((resolve2, reject) => {
|
|
19389
20400
|
const spawnSpec = resolveSpawnCommand(command, args);
|
|
19390
20401
|
const child = spawn7(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -19392,16 +20403,26 @@ ${baseText}` : "" };
|
|
|
19392
20403
|
let stderr = "";
|
|
19393
20404
|
const state = createStreamingPromptState(formatToolCalls);
|
|
19394
20405
|
let lastReplyAt = Date.now();
|
|
19395
|
-
|
|
20406
|
+
let segmentChain = Promise.resolve();
|
|
20407
|
+
let segmentError;
|
|
20408
|
+
const sink = reply ? createQuotaGatedReplySink({
|
|
19396
20409
|
reply,
|
|
19397
20410
|
...replyContext ? { replyContext } : {}
|
|
19398
|
-
});
|
|
20411
|
+
}) : null;
|
|
20412
|
+
const feedSegment = (segment) => {
|
|
20413
|
+
if (onSegment) {
|
|
20414
|
+
segmentChain = segmentChain.then(() => onSegment(segment)).catch((error2) => {
|
|
20415
|
+
segmentError ??= error2;
|
|
20416
|
+
});
|
|
20417
|
+
}
|
|
20418
|
+
sink?.feedSegment(segment);
|
|
20419
|
+
lastReplyAt = Date.now();
|
|
20420
|
+
};
|
|
19399
20421
|
const flushBuffer = () => {
|
|
19400
20422
|
const remaining = state.buffer.trim();
|
|
19401
20423
|
if (remaining.length > 0) {
|
|
19402
20424
|
state.buffer = "";
|
|
19403
|
-
|
|
19404
|
-
lastReplyAt = Date.now();
|
|
20425
|
+
feedSegment(remaining);
|
|
19405
20426
|
}
|
|
19406
20427
|
};
|
|
19407
20428
|
const timer = setInterval(() => {
|
|
@@ -19414,8 +20435,7 @@ ${baseText}` : "" };
|
|
|
19414
20435
|
stdout2 += String(chunk);
|
|
19415
20436
|
parseStreamingDataChunk(state, String(chunk));
|
|
19416
20437
|
for (const segment of state.segments.splice(0)) {
|
|
19417
|
-
|
|
19418
|
-
lastReplyAt = Date.now();
|
|
20438
|
+
feedSegment(segment);
|
|
19419
20439
|
}
|
|
19420
20440
|
});
|
|
19421
20441
|
child.stderr.on("data", (chunk) => {
|
|
@@ -19429,19 +20449,28 @@ ${baseText}` : "" };
|
|
|
19429
20449
|
clearInterval(timer);
|
|
19430
20450
|
const remaining = state.finalize();
|
|
19431
20451
|
if (remaining.length > 0) {
|
|
19432
|
-
|
|
19433
|
-
}
|
|
19434
|
-
const { overflowCount } = sink
|
|
19435
|
-
|
|
19436
|
-
|
|
20452
|
+
feedSegment(remaining);
|
|
20453
|
+
}
|
|
20454
|
+
const { overflowCount } = sink?.finalize() ?? { overflowCount: 0 };
|
|
20455
|
+
Promise.all([
|
|
20456
|
+
sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
|
|
20457
|
+
segmentChain
|
|
20458
|
+
]).then(() => {
|
|
20459
|
+
const deferred = sink?.getPendingError();
|
|
19437
20460
|
if (deferred) {
|
|
19438
20461
|
reject(deferred);
|
|
19439
20462
|
return;
|
|
19440
20463
|
}
|
|
20464
|
+
if (segmentError) {
|
|
20465
|
+
reject(segmentError);
|
|
20466
|
+
return;
|
|
20467
|
+
}
|
|
19441
20468
|
resolve2({
|
|
19442
20469
|
result: { code: code ?? 1, stdout: stdout2, stderr },
|
|
19443
20470
|
overflowCount
|
|
19444
20471
|
});
|
|
20472
|
+
}).catch((error2) => {
|
|
20473
|
+
reject(error2);
|
|
19445
20474
|
});
|
|
19446
20475
|
});
|
|
19447
20476
|
});
|
|
@@ -19459,7 +20488,7 @@ ${baseText}` : "" };
|
|
|
19459
20488
|
}
|
|
19460
20489
|
return [...prefix, session.agent, ...tail2];
|
|
19461
20490
|
}
|
|
19462
|
-
buildPromptArgs(session, text) {
|
|
20491
|
+
buildPromptArgs(session, text, promptFile) {
|
|
19463
20492
|
const prefix = [
|
|
19464
20493
|
"--format",
|
|
19465
20494
|
"json",
|
|
@@ -19468,7 +20497,7 @@ ${baseText}` : "" };
|
|
|
19468
20497
|
session.cwd,
|
|
19469
20498
|
...this.buildPermissionArgs()
|
|
19470
20499
|
];
|
|
19471
|
-
const tail2 = ["prompt", "-s", session.transportSession, text];
|
|
20500
|
+
const tail2 = promptFile ? ["prompt", "-s", session.transportSession, "--file", promptFile] : ["prompt", "-s", session.transportSession, text];
|
|
19472
20501
|
if (session.agentCommand) {
|
|
19473
20502
|
return [...prefix, "--agent", session.agentCommand, ...tail2];
|
|
19474
20503
|
}
|
|
@@ -19507,6 +20536,7 @@ var require4;
|
|
|
19507
20536
|
var init_acpx_cli_transport = __esm(() => {
|
|
19508
20537
|
init_spawn_command();
|
|
19509
20538
|
init_prompt_output();
|
|
20539
|
+
init_prompt_media();
|
|
19510
20540
|
init_streaming_prompt();
|
|
19511
20541
|
init_quota_gated_reply_sink();
|
|
19512
20542
|
init_node_pty_helper();
|
|
@@ -19908,8 +20938,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
19908
20938
|
await logger2.cleanup();
|
|
19909
20939
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config2.transport.command });
|
|
19910
20940
|
const stateStore = new StateStore(paths.statePath);
|
|
19911
|
-
|
|
19912
|
-
const
|
|
20941
|
+
const state = await stateStore.load();
|
|
20942
|
+
const stateMutex = new AsyncMutex;
|
|
20943
|
+
const sessions = new SessionService(config2, stateStore, state, { stateMutex });
|
|
19913
20944
|
const pendingWorkerDispatches = new Set;
|
|
19914
20945
|
const transport = config2.transport.type === "acpx-bridge" ? await (deps.createBridgeTransport?.() ?? Promise.resolve(new AcpxBridgeTransport(await spawnAcpxBridgeClient({
|
|
19915
20946
|
acpxCommand,
|
|
@@ -20058,34 +21089,53 @@ async function buildApp(paths, deps = {}) {
|
|
|
20058
21089
|
return;
|
|
20059
21090
|
}
|
|
20060
21091
|
};
|
|
21092
|
+
const resolveWorkerRuntimeSession = (input) => {
|
|
21093
|
+
if (!input.cwd) {
|
|
21094
|
+
return sessions.resolveSession(input.workerSession, input.targetAgent, input.workspace, input.workerSession);
|
|
21095
|
+
}
|
|
21096
|
+
const agentConfig = config2.agents[input.targetAgent];
|
|
21097
|
+
if (!agentConfig) {
|
|
21098
|
+
throw new Error(`agent "${input.targetAgent}" is not configured`);
|
|
21099
|
+
}
|
|
21100
|
+
return {
|
|
21101
|
+
alias: input.workerSession,
|
|
21102
|
+
agent: input.targetAgent,
|
|
21103
|
+
agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
|
|
21104
|
+
workspace: input.workspace,
|
|
21105
|
+
transportSession: input.workerSession,
|
|
21106
|
+
cwd: input.cwd
|
|
21107
|
+
};
|
|
21108
|
+
};
|
|
20061
21109
|
const launchWorkerTurn = (input) => {
|
|
20062
|
-
const session =
|
|
21110
|
+
const session = resolveWorkerRuntimeSession(input);
|
|
20063
21111
|
session.mcpCoordinatorSession = input.coordinatorSession;
|
|
20064
21112
|
session.mcpSourceHandle = input.workerSession;
|
|
20065
21113
|
const workerDispatch = (async () => {
|
|
20066
21114
|
let taskRecord;
|
|
20067
21115
|
try {
|
|
20068
21116
|
const progressBuffer = new ProgressLineBuffer;
|
|
20069
|
-
const result = await transport.prompt(session, input.promptText,
|
|
20070
|
-
|
|
20071
|
-
|
|
20072
|
-
|
|
20073
|
-
|
|
20074
|
-
|
|
20075
|
-
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
|
|
20079
|
-
|
|
20080
|
-
|
|
20081
|
-
|
|
21117
|
+
const result = await transport.prompt(session, input.promptText, undefined, undefined, {
|
|
21118
|
+
onSegment: async (chunk) => {
|
|
21119
|
+
const summaries = progressBuffer.feed(chunk);
|
|
21120
|
+
for (const summary of summaries) {
|
|
21121
|
+
try {
|
|
21122
|
+
await orchestration.recordTaskProgress(input.taskId);
|
|
21123
|
+
const taskState = await orchestration.getTask(input.taskId);
|
|
21124
|
+
if (taskState?.chatKey && taskState.replyContextToken) {
|
|
21125
|
+
await deliverOrchestrationTaskProgress(taskState, renderTaskProgress(taskState, summary), {
|
|
21126
|
+
listAccountIds: () => listWeixinAccountIds(),
|
|
21127
|
+
resolveAccount: (accountId) => resolveWeixinAccount(accountId),
|
|
21128
|
+
getContextToken: (accountId, userId) => getContextToken(accountId, userId),
|
|
21129
|
+
reserveMidSegment: (chatKey) => quota.reserveMidSegment(chatKey),
|
|
21130
|
+
logger: logger2
|
|
21131
|
+
});
|
|
21132
|
+
}
|
|
21133
|
+
} catch (error2) {
|
|
21134
|
+
await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
|
|
21135
|
+
taskId: input.taskId,
|
|
21136
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
20082
21137
|
});
|
|
20083
21138
|
}
|
|
20084
|
-
} catch (error2) {
|
|
20085
|
-
await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
|
|
20086
|
-
taskId: input.taskId,
|
|
20087
|
-
message: error2 instanceof Error ? error2.message : String(error2)
|
|
20088
|
-
});
|
|
20089
21139
|
}
|
|
20090
21140
|
}
|
|
20091
21141
|
});
|
|
@@ -20121,7 +21171,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
20121
21171
|
});
|
|
20122
21172
|
}
|
|
20123
21173
|
}
|
|
20124
|
-
if (taskRecord) {
|
|
21174
|
+
if (taskRecord && !isRuntimeExternalCoordinator(taskRecord.coordinatorSession)) {
|
|
20125
21175
|
try {
|
|
20126
21176
|
await wakeCoordinator(taskRecord.coordinatorSession);
|
|
20127
21177
|
} catch (wakeError) {
|
|
@@ -20138,6 +21188,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
20138
21188
|
pendingWorkerDispatches.delete(workerDispatch);
|
|
20139
21189
|
});
|
|
20140
21190
|
};
|
|
21191
|
+
const isRuntimeExternalCoordinator = (coordinatorSession) => {
|
|
21192
|
+
return Boolean(state.orchestration.externalCoordinators[coordinatorSession]);
|
|
21193
|
+
};
|
|
20141
21194
|
orchestration = new OrchestrationService({
|
|
20142
21195
|
now: deps.loggerNow ?? (() => new Date),
|
|
20143
21196
|
createId: () => randomUUID3(),
|
|
@@ -20145,39 +21198,42 @@ async function buildApp(paths, deps = {}) {
|
|
|
20145
21198
|
loadState: async () => JSON.parse(JSON.stringify(state)),
|
|
20146
21199
|
saveState: async (nextState) => {
|
|
20147
21200
|
await stateStore.save(nextState);
|
|
20148
|
-
state
|
|
21201
|
+
replaceRuntimeState(state, nextState);
|
|
20149
21202
|
},
|
|
20150
|
-
|
|
20151
|
-
|
|
21203
|
+
stateMutex,
|
|
21204
|
+
ensureWorkerSession: async ({ workerSession, targetAgent, workspace, cwd, coordinatorSession }) => {
|
|
21205
|
+
const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
|
|
20152
21206
|
session.mcpCoordinatorSession = coordinatorSession;
|
|
20153
21207
|
session.mcpSourceHandle = workerSession;
|
|
20154
21208
|
await transport.ensureSession(session);
|
|
20155
21209
|
return workerSession;
|
|
20156
21210
|
},
|
|
20157
|
-
dispatchWorkerTask: async ({ workerSession, coordinatorSession, targetAgent, workspace, taskId, role, task }) => {
|
|
21211
|
+
dispatchWorkerTask: async ({ workerSession, coordinatorSession, targetAgent, workspace, cwd, taskId, role, task }) => {
|
|
20158
21212
|
launchWorkerTurn({
|
|
20159
21213
|
taskId,
|
|
20160
21214
|
workerSession,
|
|
20161
21215
|
coordinatorSession,
|
|
20162
21216
|
targetAgent,
|
|
20163
21217
|
workspace,
|
|
21218
|
+
...cwd ? { cwd } : {},
|
|
20164
21219
|
promptText: buildWorkerTaskPrompt({ taskId, workerSession, role, task })
|
|
20165
21220
|
});
|
|
20166
21221
|
},
|
|
20167
|
-
cancelWorkerTask: async ({ workerSession, targetAgent, workspace }) => {
|
|
20168
|
-
const session =
|
|
21222
|
+
cancelWorkerTask: async ({ workerSession, targetAgent, workspace, cwd }) => {
|
|
21223
|
+
const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
|
|
20169
21224
|
const result = await transport.cancel(session);
|
|
20170
21225
|
if (!result.cancelled) {
|
|
20171
21226
|
throw new Error(result.message || "worker task cancel was not acknowledged");
|
|
20172
21227
|
}
|
|
20173
21228
|
},
|
|
20174
|
-
resumeWorkerTask: async ({ taskId, workerSession, coordinatorSession, targetAgent, workspace, answer }) => {
|
|
21229
|
+
resumeWorkerTask: async ({ taskId, workerSession, coordinatorSession, targetAgent, workspace, cwd, answer }) => {
|
|
20175
21230
|
launchWorkerTurn({
|
|
20176
21231
|
taskId,
|
|
20177
21232
|
workerSession,
|
|
20178
21233
|
coordinatorSession,
|
|
20179
21234
|
targetAgent,
|
|
20180
21235
|
workspace,
|
|
21236
|
+
...cwd ? { cwd } : {},
|
|
20181
21237
|
promptText: buildWorkerAnswerPrompt(answer)
|
|
20182
21238
|
});
|
|
20183
21239
|
},
|
|
@@ -20187,15 +21243,15 @@ async function buildApp(paths, deps = {}) {
|
|
|
20187
21243
|
deliverCoordinatorMessage: async (input) => {
|
|
20188
21244
|
await sendCoordinatorMessage(input);
|
|
20189
21245
|
},
|
|
20190
|
-
interruptWorkerTask: async ({ workerSession, targetAgent, workspace }) => {
|
|
20191
|
-
const session =
|
|
21246
|
+
interruptWorkerTask: async ({ workerSession, targetAgent, workspace, cwd }) => {
|
|
21247
|
+
const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
|
|
20192
21248
|
const result = await transport.cancel(session);
|
|
20193
21249
|
if (!result.cancelled) {
|
|
20194
21250
|
throw new Error(result.message || "worker interrupt was not acknowledged");
|
|
20195
21251
|
}
|
|
20196
21252
|
},
|
|
20197
|
-
findReusableWorkerSession: async ({ coordinatorSession, workspace, targetAgent, role }) => {
|
|
20198
|
-
const binding = Object.entries(state.orchestration.workerBindings).find(([, current]) => current.coordinatorSession === coordinatorSession && current.workspace === workspace && current.targetAgent === targetAgent && current.role === role);
|
|
21253
|
+
findReusableWorkerSession: async ({ coordinatorSession, workspace, cwd, targetAgent, role }) => {
|
|
21254
|
+
const binding = Object.entries(state.orchestration.workerBindings).find(([, current]) => current.coordinatorSession === coordinatorSession && current.workspace === workspace && current.cwd === cwd && current.targetAgent === targetAgent && current.role === role);
|
|
20199
21255
|
return binding?.[0] ?? null;
|
|
20200
21256
|
},
|
|
20201
21257
|
logger: logger2
|
|
@@ -20241,6 +21297,11 @@ async function buildApp(paths, deps = {}) {
|
|
|
20241
21297
|
}
|
|
20242
21298
|
};
|
|
20243
21299
|
}
|
|
21300
|
+
function replaceRuntimeState(target, source) {
|
|
21301
|
+
target.sessions = source.sessions;
|
|
21302
|
+
target.chat_contexts = source.chat_contexts;
|
|
21303
|
+
target.orchestration = source.orchestration;
|
|
21304
|
+
}
|
|
20244
21305
|
async function main2() {
|
|
20245
21306
|
const paths = resolveRuntimePaths();
|
|
20246
21307
|
try {
|
|
@@ -20722,107 +21783,107 @@ async function checkRuntime(options = {}) {
|
|
|
20722
21783
|
}
|
|
20723
21784
|
function createRuntimeFsProbe() {
|
|
20724
21785
|
return {
|
|
20725
|
-
stat: async (
|
|
20726
|
-
access: async (
|
|
21786
|
+
stat: async (path12) => await stat2(path12),
|
|
21787
|
+
access: async (path12, mode) => await access3(path12, mode)
|
|
20727
21788
|
};
|
|
20728
21789
|
}
|
|
20729
|
-
async function checkDirectoryCreatable(label,
|
|
21790
|
+
async function checkDirectoryCreatable(label, path12, probe, platform) {
|
|
20730
21791
|
try {
|
|
20731
|
-
const stats = await probe.stat(
|
|
21792
|
+
const stats = await probe.stat(path12);
|
|
20732
21793
|
if (!stats.isDirectory()) {
|
|
20733
21794
|
return {
|
|
20734
21795
|
ok: false,
|
|
20735
|
-
detail: `${label}: ${
|
|
21796
|
+
detail: `${label}: ${path12} (exists but is not a directory)`
|
|
20736
21797
|
};
|
|
20737
21798
|
}
|
|
20738
|
-
await probe.access(
|
|
21799
|
+
await probe.access(path12, directoryAccessMode(platform));
|
|
20739
21800
|
return {
|
|
20740
21801
|
ok: true,
|
|
20741
|
-
detail: `${label}: ${
|
|
21802
|
+
detail: `${label}: ${path12} (writable)`
|
|
20742
21803
|
};
|
|
20743
21804
|
} catch (error2) {
|
|
20744
21805
|
if (!isMissingPathError(error2)) {
|
|
20745
21806
|
return {
|
|
20746
21807
|
ok: false,
|
|
20747
|
-
detail: `${label}: ${
|
|
21808
|
+
detail: `${label}: ${path12} (unusable: ${formatError6(error2)})`
|
|
20748
21809
|
};
|
|
20749
21810
|
}
|
|
20750
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
21811
|
+
const parentCheck = await checkCreatableAncestorDirectory(path12, probe, platform);
|
|
20751
21812
|
if (!parentCheck.ok) {
|
|
20752
21813
|
return {
|
|
20753
21814
|
ok: false,
|
|
20754
|
-
detail: `${label}: ${
|
|
21815
|
+
detail: `${label}: ${path12} (parent not writable: ${parentCheck.blockingPath})`
|
|
20755
21816
|
};
|
|
20756
21817
|
}
|
|
20757
21818
|
return {
|
|
20758
21819
|
ok: true,
|
|
20759
|
-
detail: `${label}: ${
|
|
21820
|
+
detail: `${label}: ${path12} (creatable via ${parentCheck.creatableFrom})`
|
|
20760
21821
|
};
|
|
20761
21822
|
}
|
|
20762
21823
|
}
|
|
20763
|
-
async function checkFileCreatable(label,
|
|
21824
|
+
async function checkFileCreatable(label, path12, probe, platform) {
|
|
20764
21825
|
try {
|
|
20765
|
-
const stats = await probe.stat(
|
|
21826
|
+
const stats = await probe.stat(path12);
|
|
20766
21827
|
if (stats.isDirectory()) {
|
|
20767
21828
|
return {
|
|
20768
21829
|
ok: false,
|
|
20769
|
-
detail: `${label}: ${
|
|
21830
|
+
detail: `${label}: ${path12} (exists but is a directory)`
|
|
20770
21831
|
};
|
|
20771
21832
|
}
|
|
20772
|
-
await probe.access(
|
|
21833
|
+
await probe.access(path12, constants.W_OK);
|
|
20773
21834
|
return {
|
|
20774
21835
|
ok: true,
|
|
20775
|
-
detail: `${label}: ${
|
|
21836
|
+
detail: `${label}: ${path12} (writable)`
|
|
20776
21837
|
};
|
|
20777
21838
|
} catch (error2) {
|
|
20778
21839
|
if (!isMissingPathError(error2)) {
|
|
20779
21840
|
return {
|
|
20780
21841
|
ok: false,
|
|
20781
|
-
detail: `${label}: ${
|
|
21842
|
+
detail: `${label}: ${path12} (unusable: ${formatError6(error2)})`
|
|
20782
21843
|
};
|
|
20783
21844
|
}
|
|
20784
|
-
const parentCheck = await checkCreatableAncestorDirectory(dirname11(
|
|
21845
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname11(path12), probe, platform);
|
|
20785
21846
|
if (!parentCheck.ok) {
|
|
20786
21847
|
return {
|
|
20787
21848
|
ok: false,
|
|
20788
|
-
detail: `${label}: ${
|
|
21849
|
+
detail: `${label}: ${path12} (parent not writable: ${parentCheck.blockingPath})`
|
|
20789
21850
|
};
|
|
20790
21851
|
}
|
|
20791
21852
|
return {
|
|
20792
21853
|
ok: true,
|
|
20793
|
-
detail: `${label}: ${
|
|
21854
|
+
detail: `${label}: ${path12} (creatable via ${parentCheck.creatableFrom})`
|
|
20794
21855
|
};
|
|
20795
21856
|
}
|
|
20796
21857
|
}
|
|
20797
|
-
async function checkCreatableAncestorDirectory(
|
|
21858
|
+
async function checkCreatableAncestorDirectory(path12, probe, platform) {
|
|
20798
21859
|
try {
|
|
20799
|
-
const stats = await probe.stat(
|
|
21860
|
+
const stats = await probe.stat(path12);
|
|
20800
21861
|
if (!stats.isDirectory()) {
|
|
20801
21862
|
return {
|
|
20802
21863
|
ok: false,
|
|
20803
|
-
creatableFrom:
|
|
20804
|
-
blockingPath:
|
|
21864
|
+
creatableFrom: path12,
|
|
21865
|
+
blockingPath: path12
|
|
20805
21866
|
};
|
|
20806
21867
|
}
|
|
20807
|
-
await probe.access(
|
|
21868
|
+
await probe.access(path12, directoryAccessMode(platform));
|
|
20808
21869
|
return {
|
|
20809
21870
|
ok: true,
|
|
20810
|
-
creatableFrom:
|
|
21871
|
+
creatableFrom: path12
|
|
20811
21872
|
};
|
|
20812
21873
|
} catch (error2) {
|
|
20813
21874
|
if (!isMissingPathError(error2)) {
|
|
20814
21875
|
return {
|
|
20815
21876
|
ok: false,
|
|
20816
|
-
creatableFrom:
|
|
20817
|
-
blockingPath:
|
|
21877
|
+
creatableFrom: path12,
|
|
21878
|
+
blockingPath: path12
|
|
20818
21879
|
};
|
|
20819
21880
|
}
|
|
20820
|
-
const parent = dirname11(
|
|
20821
|
-
if (parent ===
|
|
21881
|
+
const parent = dirname11(path12);
|
|
21882
|
+
if (parent === path12) {
|
|
20822
21883
|
return {
|
|
20823
21884
|
ok: false,
|
|
20824
|
-
creatableFrom:
|
|
20825
|
-
blockingPath:
|
|
21885
|
+
creatableFrom: path12,
|
|
21886
|
+
blockingPath: path12
|
|
20826
21887
|
};
|
|
20827
21888
|
}
|
|
20828
21889
|
const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
|
|
@@ -21393,9 +22454,11 @@ var init_doctor2 = __esm(async () => {
|
|
|
21393
22454
|
|
|
21394
22455
|
// src/cli.ts
|
|
21395
22456
|
init_config_store();
|
|
22457
|
+
init_load_config();
|
|
21396
22458
|
init_ensure_config();
|
|
21397
22459
|
init_create_daemon_controller();
|
|
21398
22460
|
init_daemon_files();
|
|
22461
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
21399
22462
|
import { homedir as homedir12 } from "node:os";
|
|
21400
22463
|
import { sep } from "node:path";
|
|
21401
22464
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -33832,6 +34895,7 @@ function requireHome(env) {
|
|
|
33832
34895
|
}
|
|
33833
34896
|
|
|
33834
34897
|
// src/mcp/weacpx-mcp-tools.ts
|
|
34898
|
+
init_task_wait_timeouts();
|
|
33835
34899
|
init_quota_errors();
|
|
33836
34900
|
var groupStatusSchema = exports_external.enum(["pending", "running", "terminal"]);
|
|
33837
34901
|
var taskStatusSchema = exports_external.enum([
|
|
@@ -33860,6 +34924,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
33860
34924
|
inputSchema: exports_external.object({
|
|
33861
34925
|
targetAgent: exports_external.string().min(1),
|
|
33862
34926
|
task: exports_external.string().min(1),
|
|
34927
|
+
workingDirectory: exports_external.string().min(1).optional(),
|
|
33863
34928
|
role: exports_external.string().min(1).optional(),
|
|
33864
34929
|
groupId: exports_external.string().min(1).optional()
|
|
33865
34930
|
}).strict(),
|
|
@@ -34013,6 +35078,22 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
34013
35078
|
return createSuccessResult(`任务「${task.taskId}」已请求取消。`, task);
|
|
34014
35079
|
})
|
|
34015
35080
|
},
|
|
35081
|
+
{
|
|
35082
|
+
name: "task_wait",
|
|
35083
|
+
description: "Wait for an orchestration task to finish or require attention using a bounded timeout.",
|
|
35084
|
+
inputSchema: exports_external.object({
|
|
35085
|
+
taskId: exports_external.string().min(1),
|
|
35086
|
+
timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WAIT_TIMEOUT_MS).optional(),
|
|
35087
|
+
pollIntervalMs: exports_external.number().int().min(1).max(MAX_TASK_WAIT_POLL_INTERVAL_MS).optional()
|
|
35088
|
+
}).strict(),
|
|
35089
|
+
handler: async (args) => await asToolResult(async () => {
|
|
35090
|
+
const result = await transport.waitTask({
|
|
35091
|
+
coordinatorSession,
|
|
35092
|
+
...args
|
|
35093
|
+
});
|
|
35094
|
+
return createSuccessResult(renderTaskWaitResult(result), result);
|
|
35095
|
+
})
|
|
35096
|
+
},
|
|
34016
35097
|
{
|
|
34017
35098
|
name: "worker_raise_question",
|
|
34018
35099
|
description: "Raise a blocker question for the current bound session.",
|
|
@@ -34129,6 +35210,21 @@ async function asToolResult(action) {
|
|
|
34129
35210
|
return createErrorResult(formatToolError(error2));
|
|
34130
35211
|
}
|
|
34131
35212
|
}
|
|
35213
|
+
function renderTaskWaitResult(result) {
|
|
35214
|
+
if (result.status === "not_found") {
|
|
35215
|
+
return "Task not found.";
|
|
35216
|
+
}
|
|
35217
|
+
if (!result.task) {
|
|
35218
|
+
return `Task wait ${result.status.replace("_", " ")}; current state is unavailable.`;
|
|
35219
|
+
}
|
|
35220
|
+
if (result.status === "timeout") {
|
|
35221
|
+
return `Task ${result.task.taskId} wait timed out; current state is ${result.task.status}.`;
|
|
35222
|
+
}
|
|
35223
|
+
if (result.status === "attention_required") {
|
|
35224
|
+
return `Task ${result.task.taskId} requires attention; current state is ${result.task.status}.`;
|
|
35225
|
+
}
|
|
35226
|
+
return `Task ${result.task.taskId} reached terminal state ${result.task.status}.`;
|
|
35227
|
+
}
|
|
34132
35228
|
function createSuccessResult(text, structuredContent) {
|
|
34133
35229
|
return {
|
|
34134
35230
|
content: [{ type: "text", text }],
|
|
@@ -34151,6 +35247,7 @@ function formatToolError(error2) {
|
|
|
34151
35247
|
|
|
34152
35248
|
// src/orchestration/orchestration-client.ts
|
|
34153
35249
|
init_orchestration_ipc();
|
|
35250
|
+
init_task_wait_timeouts();
|
|
34154
35251
|
import { randomUUID } from "node:crypto";
|
|
34155
35252
|
import { createConnection } from "node:net";
|
|
34156
35253
|
|
|
@@ -34163,17 +35260,20 @@ class OrchestrationClient {
|
|
|
34163
35260
|
this.createId = deps.createId ?? (() => randomUUID());
|
|
34164
35261
|
this.timeoutMs = deps.timeoutMs ?? 30000;
|
|
34165
35262
|
}
|
|
35263
|
+
async registerExternalCoordinator(input) {
|
|
35264
|
+
return await this.request("coordinator.register_external", input);
|
|
35265
|
+
}
|
|
34166
35266
|
async delegateRequest(input) {
|
|
34167
35267
|
return await this.request("delegate.request", input);
|
|
34168
35268
|
}
|
|
34169
|
-
async getTask(taskId) {
|
|
34170
|
-
return await this.request("task.get", { taskId });
|
|
34171
|
-
}
|
|
34172
35269
|
async getTaskForCoordinator(input) {
|
|
34173
35270
|
return await this.request("task.get", input);
|
|
34174
35271
|
}
|
|
34175
35272
|
async listTasks(filter) {
|
|
34176
|
-
return await this.request("task.list",
|
|
35273
|
+
return await this.request("task.list", { filter });
|
|
35274
|
+
}
|
|
35275
|
+
async waitTask(input) {
|
|
35276
|
+
return await this.request("task.wait", input, getWaitRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
|
|
34177
35277
|
}
|
|
34178
35278
|
async approveTask(input) {
|
|
34179
35279
|
return await this.request("task.approve", input);
|
|
@@ -34220,7 +35320,7 @@ class OrchestrationClient {
|
|
|
34220
35320
|
async cancelGroup(input) {
|
|
34221
35321
|
return await this.request("group.cancel", input);
|
|
34222
35322
|
}
|
|
34223
|
-
async request(method, params) {
|
|
35323
|
+
async request(method, params, timeoutMs = this.timeoutMs) {
|
|
34224
35324
|
const id = this.createId();
|
|
34225
35325
|
return await new Promise((resolve, reject) => {
|
|
34226
35326
|
const socket = createConnection(this.endpoint.path);
|
|
@@ -34239,8 +35339,8 @@ class OrchestrationClient {
|
|
|
34239
35339
|
reject(error2);
|
|
34240
35340
|
};
|
|
34241
35341
|
timer = setTimeout(() => {
|
|
34242
|
-
fail(new Error(`orchestration RPC timeout after ${
|
|
34243
|
-
},
|
|
35342
|
+
fail(new Error(`orchestration RPC timeout after ${timeoutMs}ms: ${method}`));
|
|
35343
|
+
}, timeoutMs);
|
|
34244
35344
|
socket.setEncoding("utf8");
|
|
34245
35345
|
socket.once("error", fail);
|
|
34246
35346
|
socket.once("connect", () => {
|
|
@@ -34286,6 +35386,11 @@ class OrchestrationClient {
|
|
|
34286
35386
|
});
|
|
34287
35387
|
}
|
|
34288
35388
|
}
|
|
35389
|
+
function getWaitRequestTimeoutMs(waitTimeoutMs, defaultTimeoutMs) {
|
|
35390
|
+
const requestedWaitTimeoutMs = waitTimeoutMs === undefined ? undefined : Number.isFinite(waitTimeoutMs) ? waitTimeoutMs : 0;
|
|
35391
|
+
const boundedWaitTimeoutMs = Math.min(Math.max(Math.floor(requestedWaitTimeoutMs ?? DEFAULT_TASK_WAIT_TIMEOUT_MS), 0), MAX_TASK_WAIT_TIMEOUT_MS);
|
|
35392
|
+
return Math.max(defaultTimeoutMs, boundedWaitTimeoutMs + TASK_WAIT_RPC_TIMEOUT_PADDING_MS);
|
|
35393
|
+
}
|
|
34289
35394
|
|
|
34290
35395
|
// src/mcp/weacpx-mcp-transport.ts
|
|
34291
35396
|
function createOrchestrationTransport(endpoint, deps = {}) {
|
|
@@ -34295,6 +35400,7 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
34295
35400
|
sourceHandle: input.sourceHandle ?? input.coordinatorSession,
|
|
34296
35401
|
targetAgent: input.targetAgent,
|
|
34297
35402
|
task: input.task,
|
|
35403
|
+
...input.workingDirectory ? { cwd: input.workingDirectory } : {},
|
|
34298
35404
|
...input.role ? { role: input.role } : {},
|
|
34299
35405
|
...input.groupId ? { groupId: input.groupId } : {}
|
|
34300
35406
|
}),
|
|
@@ -34313,6 +35419,7 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
34313
35419
|
approveTask: async (input) => await client.approveTask(input),
|
|
34314
35420
|
rejectTask: async (input) => await client.rejectTask(input),
|
|
34315
35421
|
cancelTask: async (input) => await client.cancelTaskForCoordinator(input),
|
|
35422
|
+
waitTask: async (input) => await client.waitTask(input),
|
|
34316
35423
|
workerRaiseQuestion: async (input) => {
|
|
34317
35424
|
const sourceHandle = input.sourceHandle.trim();
|
|
34318
35425
|
if (sourceHandle.length === 0) {
|
|
@@ -34343,16 +35450,39 @@ function createWeacpxMcpServer(options) {
|
|
|
34343
35450
|
tools: {}
|
|
34344
35451
|
}
|
|
34345
35452
|
});
|
|
34346
|
-
|
|
34347
|
-
|
|
34348
|
-
|
|
34349
|
-
|
|
34350
|
-
|
|
34351
|
-
|
|
34352
|
-
|
|
34353
|
-
|
|
34354
|
-
|
|
35453
|
+
let toolState = null;
|
|
35454
|
+
let toolStatePromise = null;
|
|
35455
|
+
async function getToolState() {
|
|
35456
|
+
if (toolState) {
|
|
35457
|
+
return toolState;
|
|
35458
|
+
}
|
|
35459
|
+
if (toolStatePromise) {
|
|
35460
|
+
return await toolStatePromise;
|
|
35461
|
+
}
|
|
35462
|
+
toolStatePromise = resolveMcpIdentity(server, options).then((identity) => {
|
|
35463
|
+
toolState = buildToolState({
|
|
35464
|
+
transport: options.transport,
|
|
35465
|
+
coordinatorSession: identity.coordinatorSession,
|
|
35466
|
+
...identity.sourceHandle ? { sourceHandle: identity.sourceHandle } : {}
|
|
35467
|
+
});
|
|
35468
|
+
return toolState;
|
|
35469
|
+
}).finally(() => {
|
|
35470
|
+
toolStatePromise = null;
|
|
35471
|
+
});
|
|
35472
|
+
return await toolStatePromise;
|
|
35473
|
+
}
|
|
35474
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
35475
|
+
const tools = (await getToolState()).tools;
|
|
35476
|
+
return {
|
|
35477
|
+
tools: tools.map((tool) => ({
|
|
35478
|
+
name: tool.name,
|
|
35479
|
+
description: tool.description,
|
|
35480
|
+
inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema))
|
|
35481
|
+
}))
|
|
35482
|
+
};
|
|
35483
|
+
});
|
|
34355
35484
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
35485
|
+
const toolMap = (await getToolState()).toolMap;
|
|
34356
35486
|
const tool = toolMap.get(request.params.name);
|
|
34357
35487
|
if (!tool) {
|
|
34358
35488
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
@@ -34365,12 +35495,35 @@ function createWeacpxMcpServer(options) {
|
|
|
34365
35495
|
});
|
|
34366
35496
|
return server;
|
|
34367
35497
|
}
|
|
35498
|
+
function buildToolState(options) {
|
|
35499
|
+
const tools = buildWeacpxMcpToolRegistry(options);
|
|
35500
|
+
return {
|
|
35501
|
+
tools,
|
|
35502
|
+
toolMap: new Map(tools.map((tool) => [tool.name, tool]))
|
|
35503
|
+
};
|
|
35504
|
+
}
|
|
35505
|
+
async function resolveMcpIdentity(server, options) {
|
|
35506
|
+
if (options.resolveIdentity) {
|
|
35507
|
+
return await options.resolveIdentity({
|
|
35508
|
+
clientName: server.getClientVersion()?.name,
|
|
35509
|
+
listRoots: async () => (await server.listRoots()).roots
|
|
35510
|
+
});
|
|
35511
|
+
}
|
|
35512
|
+
if (options.coordinatorSession) {
|
|
35513
|
+
return {
|
|
35514
|
+
coordinatorSession: options.coordinatorSession,
|
|
35515
|
+
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {}
|
|
35516
|
+
};
|
|
35517
|
+
}
|
|
35518
|
+
throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
|
|
35519
|
+
}
|
|
34368
35520
|
async function runWeacpxMcpServer(options) {
|
|
34369
35521
|
const transport = createOrchestrationTransport(options.endpoint ?? resolveDefaultOrchestrationEndpoint(process.env, process.platform));
|
|
34370
35522
|
const server = createWeacpxMcpServer({
|
|
34371
35523
|
transport,
|
|
34372
|
-
coordinatorSession: options.coordinatorSession,
|
|
34373
|
-
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {}
|
|
35524
|
+
...options.coordinatorSession ? { coordinatorSession: options.coordinatorSession } : {},
|
|
35525
|
+
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
|
|
35526
|
+
...options.resolveIdentity ? { resolveIdentity: options.resolveIdentity } : {}
|
|
34374
35527
|
});
|
|
34375
35528
|
const stdio = new StdioServerTransport(stdin, stdout);
|
|
34376
35529
|
await server.connect(stdio);
|
|
@@ -34387,12 +35540,55 @@ function formatZodError(error2) {
|
|
|
34387
35540
|
}).join("; ");
|
|
34388
35541
|
}
|
|
34389
35542
|
|
|
35543
|
+
// src/mcp/infer-coordinator-identity.ts
|
|
35544
|
+
init_workspace_path();
|
|
35545
|
+
function inferExternalCoordinatorSession(input) {
|
|
35546
|
+
const suffix = input.workspace?.trim() || input.instanceId?.trim() || "instance";
|
|
35547
|
+
return `external_${sanitizeMcpClientName(input.clientName)}:${suffix}`;
|
|
35548
|
+
}
|
|
35549
|
+
function sanitizeMcpClientName(input) {
|
|
35550
|
+
const normalized = (input ?? "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
35551
|
+
return normalized.length > 0 ? normalized : "mcp-host";
|
|
35552
|
+
}
|
|
35553
|
+
|
|
35554
|
+
// src/mcp/parse-coordinator-workspace.ts
|
|
35555
|
+
function parseCoordinatorWorkspace(args, env = process.env) {
|
|
35556
|
+
let fromFlag = null;
|
|
35557
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
35558
|
+
if (args[index] === "--workspace") {
|
|
35559
|
+
const value = args[index + 1];
|
|
35560
|
+
if (value === undefined) {
|
|
35561
|
+
throw new Error("--workspace requires a non-empty value");
|
|
35562
|
+
}
|
|
35563
|
+
const trimmedValue = value.trim();
|
|
35564
|
+
if (trimmedValue.length === 0 || trimmedValue.startsWith("-")) {
|
|
35565
|
+
throw new Error("--workspace requires a non-empty value");
|
|
35566
|
+
}
|
|
35567
|
+
fromFlag = value;
|
|
35568
|
+
}
|
|
35569
|
+
}
|
|
35570
|
+
const trimmedFlag = fromFlag?.trim();
|
|
35571
|
+
if (trimmedFlag && trimmedFlag.length > 0) {
|
|
35572
|
+
return trimmedFlag;
|
|
35573
|
+
}
|
|
35574
|
+
const trimmedEnv = env.WEACPX_COORDINATOR_WORKSPACE?.trim();
|
|
35575
|
+
return trimmedEnv && trimmedEnv.length > 0 ? trimmedEnv : null;
|
|
35576
|
+
}
|
|
35577
|
+
|
|
34390
35578
|
// src/mcp/parse-coordinator-session.ts
|
|
34391
35579
|
function parseCoordinatorSession(args, env = process.env) {
|
|
34392
35580
|
let fromFlag = null;
|
|
34393
|
-
for (let index = 0;index < args.length
|
|
35581
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
34394
35582
|
if (args[index] === "--coordinator-session") {
|
|
34395
|
-
|
|
35583
|
+
const value = args[index + 1];
|
|
35584
|
+
if (value === undefined) {
|
|
35585
|
+
throw new Error("--coordinator-session requires a non-empty value");
|
|
35586
|
+
}
|
|
35587
|
+
const trimmedValue = value.trim();
|
|
35588
|
+
if (trimmedValue.length === 0 || trimmedValue.startsWith("-")) {
|
|
35589
|
+
throw new Error("--coordinator-session requires a non-empty value");
|
|
35590
|
+
}
|
|
35591
|
+
fromFlag = value;
|
|
34396
35592
|
}
|
|
34397
35593
|
}
|
|
34398
35594
|
const trimmedFlag = fromFlag?.trim();
|
|
@@ -34406,9 +35602,17 @@ function parseCoordinatorSession(args, env = process.env) {
|
|
|
34406
35602
|
// src/mcp/parse-source-handle.ts
|
|
34407
35603
|
function parseSourceHandle(args, env = process.env) {
|
|
34408
35604
|
let fromFlag = null;
|
|
34409
|
-
for (let index = 0;index < args.length
|
|
35605
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
34410
35606
|
if (args[index] === "--source-handle") {
|
|
34411
|
-
|
|
35607
|
+
const value = args[index + 1];
|
|
35608
|
+
if (value === undefined) {
|
|
35609
|
+
throw new Error("--source-handle requires a non-empty value");
|
|
35610
|
+
}
|
|
35611
|
+
const trimmedValue = value.trim();
|
|
35612
|
+
if (trimmedValue.length === 0 || trimmedValue.startsWith("-")) {
|
|
35613
|
+
throw new Error("--source-handle requires a non-empty value");
|
|
35614
|
+
}
|
|
35615
|
+
fromFlag = value;
|
|
34412
35616
|
}
|
|
34413
35617
|
}
|
|
34414
35618
|
const trimmedFlag = fromFlag?.trim();
|
|
@@ -34421,8 +35625,99 @@ function parseSourceHandle(args, env = process.env) {
|
|
|
34421
35625
|
|
|
34422
35626
|
// src/cli.ts
|
|
34423
35627
|
init_workspace_path();
|
|
35628
|
+
init_state_store();
|
|
34424
35629
|
init_version();
|
|
34425
35630
|
init_consumer_lock();
|
|
35631
|
+
async function prepareMcpCoordinatorStartup(input) {
|
|
35632
|
+
const coordinatorSession = input.coordinatorSession.trim();
|
|
35633
|
+
const existingSession = Object.values(input.state.sessions).find((session) => session.transport_session === coordinatorSession);
|
|
35634
|
+
const workspace = input.workspace?.trim();
|
|
35635
|
+
if (workspace) {
|
|
35636
|
+
if (existingSession) {
|
|
35637
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" conflicts with an existing logical session`);
|
|
35638
|
+
}
|
|
35639
|
+
const existingExternalCoordinator2 = input.state.orchestration?.externalCoordinators?.[coordinatorSession];
|
|
35640
|
+
if (existingExternalCoordinator2?.workspace && existingExternalCoordinator2.workspace !== workspace) {
|
|
35641
|
+
throw new Error(`coordinatorSession "${coordinatorSession}" is already bound to workspace "${existingExternalCoordinator2.workspace}"; use a new coordinator session for workspace "${workspace}"`);
|
|
35642
|
+
}
|
|
35643
|
+
if (!input.config.workspaces[workspace]) {
|
|
35644
|
+
if (existingExternalCoordinator2?.workspace === workspace) {
|
|
35645
|
+
throw new Error(`workspace "${workspace}" is not configured for coordinatorSession "${coordinatorSession}"; restore that workspace config or use a new coordinator session for a different workspace`);
|
|
35646
|
+
}
|
|
35647
|
+
throw new Error(`workspace "${workspace}" is not configured`);
|
|
35648
|
+
}
|
|
35649
|
+
await registerExternalCoordinatorOrThrow(input.client, { coordinatorSession, workspace });
|
|
35650
|
+
return { kind: "external-coordinator", workspace };
|
|
35651
|
+
}
|
|
35652
|
+
if (existingSession) {
|
|
35653
|
+
return { kind: "existing-session" };
|
|
35654
|
+
}
|
|
35655
|
+
const existingExternalCoordinator = input.state.orchestration?.externalCoordinators?.[coordinatorSession];
|
|
35656
|
+
if (existingExternalCoordinator) {
|
|
35657
|
+
if (existingExternalCoordinator.workspace && !input.config.workspaces[existingExternalCoordinator.workspace]) {
|
|
35658
|
+
throw new Error(`workspace "${existingExternalCoordinator.workspace}" is not configured for coordinatorSession "${coordinatorSession}"; restore that workspace config or use a new coordinator session for a different workspace`);
|
|
35659
|
+
}
|
|
35660
|
+
await registerExternalCoordinatorOrThrow(input.client, {
|
|
35661
|
+
coordinatorSession,
|
|
35662
|
+
...existingExternalCoordinator.workspace ? { workspace: existingExternalCoordinator.workspace } : {}
|
|
35663
|
+
});
|
|
35664
|
+
return {
|
|
35665
|
+
kind: "external-coordinator",
|
|
35666
|
+
...existingExternalCoordinator.workspace ? { workspace: existingExternalCoordinator.workspace } : {}
|
|
35667
|
+
};
|
|
35668
|
+
}
|
|
35669
|
+
await registerExternalCoordinatorOrThrow(input.client, { coordinatorSession });
|
|
35670
|
+
return { kind: "external-coordinator" };
|
|
35671
|
+
}
|
|
35672
|
+
function createMcpStdioIdentityResolver(input) {
|
|
35673
|
+
const instanceId = randomUUID4().slice(0, 8);
|
|
35674
|
+
return async (context) => {
|
|
35675
|
+
const parsedCoordinatorSession = input.parsedCoordinatorSession?.trim() || null;
|
|
35676
|
+
const workspace = input.workspace?.trim() || null;
|
|
35677
|
+
const sourceHandle = input.sourceHandle?.trim() || null;
|
|
35678
|
+
const resolvedWorkspace = workspace;
|
|
35679
|
+
const resolvedCoordinatorSession = parsedCoordinatorSession ?? inferExternalCoordinatorSession({
|
|
35680
|
+
clientName: context.clientName,
|
|
35681
|
+
...resolvedWorkspace ? { workspace: resolvedWorkspace } : { instanceId }
|
|
35682
|
+
});
|
|
35683
|
+
await prepareMcpCoordinatorStartup({
|
|
35684
|
+
coordinatorSession: resolvedCoordinatorSession,
|
|
35685
|
+
...resolvedWorkspace ? { workspace: resolvedWorkspace } : {},
|
|
35686
|
+
config: input.config,
|
|
35687
|
+
state: input.state,
|
|
35688
|
+
client: input.client
|
|
35689
|
+
});
|
|
35690
|
+
return {
|
|
35691
|
+
coordinatorSession: resolvedCoordinatorSession,
|
|
35692
|
+
...sourceHandle ? { sourceHandle } : {}
|
|
35693
|
+
};
|
|
35694
|
+
};
|
|
35695
|
+
}
|
|
35696
|
+
async function registerExternalCoordinatorOrThrow(client, input) {
|
|
35697
|
+
try {
|
|
35698
|
+
await client.registerExternalCoordinator(input);
|
|
35699
|
+
} catch (error2) {
|
|
35700
|
+
if (isUnavailableOrchestrationIpcError(error2)) {
|
|
35701
|
+
throw new Error("weacpx daemon orchestration IPC is unavailable; run `weacpx start` and check `weacpx status`");
|
|
35702
|
+
}
|
|
35703
|
+
if (input.workspace && isDaemonWorkspaceNotConfiguredError(error2, input.workspace)) {
|
|
35704
|
+
throw new Error(`workspace "${input.workspace}" is not configured in the running daemon; restart it with \`weacpx stop && weacpx start\``);
|
|
35705
|
+
}
|
|
35706
|
+
throw error2;
|
|
35707
|
+
}
|
|
35708
|
+
}
|
|
35709
|
+
function isDaemonWorkspaceNotConfiguredError(error2, workspace) {
|
|
35710
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
35711
|
+
return message === `workspace "${workspace}" is not configured`;
|
|
35712
|
+
}
|
|
35713
|
+
function isUnavailableOrchestrationIpcError(error2) {
|
|
35714
|
+
const code = typeof error2 === "object" && error2 !== null && "code" in error2 ? String(error2.code) : "";
|
|
35715
|
+
if (code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET") {
|
|
35716
|
+
return true;
|
|
35717
|
+
}
|
|
35718
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
35719
|
+
return /connect (ENOENT|ECONNREFUSED|ECONNRESET)\b/.test(message);
|
|
35720
|
+
}
|
|
34426
35721
|
var HELP_LINES = [
|
|
34427
35722
|
"用法:",
|
|
34428
35723
|
"weacpx login - 微信登录",
|
|
@@ -34434,7 +35729,7 @@ var HELP_LINES = [
|
|
|
34434
35729
|
"weacpx doctor - 运行诊断",
|
|
34435
35730
|
"weacpx version - 查看版本",
|
|
34436
35731
|
"weacpx workspace list|add|rm - 管理本机工作区(别名:ws)",
|
|
34437
|
-
"weacpx mcp-stdio --coordinator-session <session> [--source-handle <handle>] - 启动 MCP stdio 服务"
|
|
35732
|
+
"weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
|
|
34438
35733
|
];
|
|
34439
35734
|
async function runCli(args, deps = {}) {
|
|
34440
35735
|
const command = args[0];
|
|
@@ -34652,17 +35947,41 @@ async function defaultDoctor(options) {
|
|
|
34652
35947
|
return await main4(options);
|
|
34653
35948
|
}
|
|
34654
35949
|
async function defaultMcpStdio(args, deps = {}) {
|
|
34655
|
-
|
|
34656
|
-
|
|
34657
|
-
|
|
34658
|
-
|
|
35950
|
+
let coordinatorSession;
|
|
35951
|
+
let sourceHandle;
|
|
35952
|
+
let endpoint;
|
|
35953
|
+
let identityResolver;
|
|
35954
|
+
try {
|
|
35955
|
+
const parsedCoordinatorSession = parseCoordinatorSession(args, process.env);
|
|
35956
|
+
sourceHandle = parseSourceHandle(args, process.env);
|
|
35957
|
+
const workspace = parseCoordinatorWorkspace(args, process.env);
|
|
35958
|
+
endpoint = resolveDefaultOrchestrationEndpoint(process.env, process.platform);
|
|
35959
|
+
const client = new OrchestrationClient(endpoint);
|
|
35960
|
+
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
35961
|
+
await ensureConfigExists(runtimePaths.configPath);
|
|
35962
|
+
const config2 = await loadConfig(runtimePaths.configPath);
|
|
35963
|
+
const state = await new StateStore(runtimePaths.statePath).load();
|
|
35964
|
+
const resolveIdentity = createMcpStdioIdentityResolver({
|
|
35965
|
+
parsedCoordinatorSession,
|
|
35966
|
+
sourceHandle,
|
|
35967
|
+
workspace,
|
|
35968
|
+
config: config2,
|
|
35969
|
+
state,
|
|
35970
|
+
client
|
|
35971
|
+
});
|
|
35972
|
+
const eagerIdentity = parsedCoordinatorSession && workspace ? await resolveIdentity({ clientName: undefined, listRoots: async () => [] }) : null;
|
|
35973
|
+
coordinatorSession = eagerIdentity?.coordinatorSession ?? "";
|
|
35974
|
+
identityResolver = eagerIdentity ? undefined : resolveIdentity;
|
|
35975
|
+
} catch (error2) {
|
|
35976
|
+
(deps.stderr ?? ((text) => process.stderr.write(text)))(`${error2 instanceof Error ? error2.message : String(error2)}
|
|
34659
35977
|
`);
|
|
34660
35978
|
return 2;
|
|
34661
35979
|
}
|
|
34662
35980
|
await runWeacpxMcpServer({
|
|
34663
|
-
endpoint
|
|
34664
|
-
coordinatorSession,
|
|
34665
|
-
...sourceHandle ? { sourceHandle } : {}
|
|
35981
|
+
endpoint,
|
|
35982
|
+
...coordinatorSession ? { coordinatorSession } : {},
|
|
35983
|
+
...sourceHandle ? { sourceHandle } : {},
|
|
35984
|
+
...identityResolver ? { resolveIdentity: identityResolver } : {}
|
|
34666
35985
|
});
|
|
34667
35986
|
return 0;
|
|
34668
35987
|
}
|
|
@@ -34727,5 +36046,7 @@ if (__require.main == __require.module) {
|
|
|
34727
36046
|
process.exitCode = await runCli(process.argv.slice(2));
|
|
34728
36047
|
}
|
|
34729
36048
|
export {
|
|
34730
|
-
runCli
|
|
36049
|
+
runCli,
|
|
36050
|
+
prepareMcpCoordinatorStartup,
|
|
36051
|
+
createMcpStdioIdentityResolver
|
|
34731
36052
|
};
|