wayang-ai 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +106 -63
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1104,14 +1104,17 @@ function summarizeWorkerMessage(argsJson, width, result, maxLines = 3) {
|
|
|
1104
1104
|
const message = args ? str(args.message) ?? "" : argsJson;
|
|
1105
1105
|
const { lines, hidden } = previewLines(message, maxLines, width);
|
|
1106
1106
|
const failed = result !== void 0 && (result.isError === true || /^\[ERROR\]/.test(result.text));
|
|
1107
|
+
let delivery;
|
|
1108
|
+
if (result !== void 0 && !failed) {
|
|
1109
|
+
delivery = /\bqueued\b/i.test(result.text) ? "queued" : /\bdelivered\b/i.test(result.text) ? "delivered" : void 0;
|
|
1110
|
+
}
|
|
1107
1111
|
return {
|
|
1108
1112
|
...workerId && { workerId },
|
|
1109
1113
|
lines,
|
|
1110
1114
|
hiddenLines: hidden,
|
|
1111
1115
|
outcome: result === void 0 ? "pending" : failed ? "failed" : "ok",
|
|
1112
1116
|
failure: failed ? failureText(result.text, width) : "",
|
|
1113
|
-
...
|
|
1114
|
-
...result !== void 0 && !failed && /\bdelivered\b/.test(result.text) && { delivery: "delivered" }
|
|
1117
|
+
...delivery && { delivery }
|
|
1115
1118
|
};
|
|
1116
1119
|
}
|
|
1117
1120
|
var TASK_ID_RE = /^Task (\S+) (?:added|assigned)/;
|
|
@@ -2448,7 +2451,7 @@ function AppContent() {
|
|
|
2448
2451
|
// package.json
|
|
2449
2452
|
var package_default = {
|
|
2450
2453
|
name: "wayang-ai",
|
|
2451
|
-
version: "0.6.
|
|
2454
|
+
version: "0.6.3",
|
|
2452
2455
|
description: "Multi-agent orchestration platform",
|
|
2453
2456
|
type: "module",
|
|
2454
2457
|
license: "MIT",
|
|
@@ -4537,18 +4540,39 @@ function controllerMessageContent(content) {
|
|
|
4537
4540
|
return `[Controller message] ${content}`;
|
|
4538
4541
|
}
|
|
4539
4542
|
function createInboxInjection(deps) {
|
|
4543
|
+
let baseLength = null;
|
|
4540
4544
|
const injected = [];
|
|
4541
4545
|
return {
|
|
4542
4546
|
prepareStep({ messages }) {
|
|
4543
4547
|
const fresh = deps.drainInbox();
|
|
4544
4548
|
if (fresh.length === 0 && injected.length === 0) return void 0;
|
|
4549
|
+
if (baseLength === null) baseLength = messages.length;
|
|
4550
|
+
const responseCount = Math.max(0, messages.length - baseLength);
|
|
4545
4551
|
for (const m of fresh) {
|
|
4546
|
-
injected.push({
|
|
4552
|
+
injected.push({
|
|
4553
|
+
message: { role: "user", content: controllerMessageContent(m.content) },
|
|
4554
|
+
// After the response messages that exist NOW — i.e. at the tail of
|
|
4555
|
+
// the conversation as this step sees it, before this step's reply.
|
|
4556
|
+
insertAfter: responseCount
|
|
4557
|
+
});
|
|
4547
4558
|
deps.onInject(m.content);
|
|
4548
4559
|
}
|
|
4549
|
-
|
|
4560
|
+
const merged = messages.slice(0, baseLength);
|
|
4561
|
+
let cursor = 0;
|
|
4562
|
+
for (const inj of injected) {
|
|
4563
|
+
while (cursor < Math.min(inj.insertAfter, responseCount)) {
|
|
4564
|
+
merged.push(messages[baseLength + cursor]);
|
|
4565
|
+
cursor++;
|
|
4566
|
+
}
|
|
4567
|
+
merged.push(inj.message);
|
|
4568
|
+
}
|
|
4569
|
+
while (cursor < responseCount) {
|
|
4570
|
+
merged.push(messages[baseLength + cursor]);
|
|
4571
|
+
cursor++;
|
|
4572
|
+
}
|
|
4573
|
+
return { messages: merged };
|
|
4550
4574
|
},
|
|
4551
|
-
injectedMessages: () =>
|
|
4575
|
+
injectedMessages: () => injected.map((i) => i.message)
|
|
4552
4576
|
};
|
|
4553
4577
|
}
|
|
4554
4578
|
|
|
@@ -4803,7 +4827,10 @@ The tool shows task metadata (status, result, error) and recent worker conversat
|
|
|
4803
4827
|
|
|
4804
4828
|
## Sending messages to workers (chat_worker)
|
|
4805
4829
|
|
|
4806
|
-
Use chat_worker(workerId, message) to send guidance to a running worker.
|
|
4830
|
+
Use chat_worker(workerId, message) to send guidance to a running worker.
|
|
4831
|
+
The worker reads it at its next step (puppet) or turn boundary (third-party) \u2014 a tool call in progress is NEVER interrupted; an idle multi-stage worker reads it when its next stage starts.
|
|
4832
|
+
Delivery is blind: "queued" only means the message sits in the worker's inbox until that boundary. A stuck worker never reads it.
|
|
4833
|
+
A third-party worker that already finished its session rejects the message with an explicit [ERROR] \u2014 inspect its output instead of retrying.
|
|
4807
4834
|
Use sparingly \u2014 only when you need to:
|
|
4808
4835
|
- Correct a worker that is going in the wrong direction
|
|
4809
4836
|
- Provide additional context the worker did not have
|
|
@@ -5272,7 +5299,11 @@ var WorkerAgent = class extends BaseAgent {
|
|
|
5272
5299
|
getSnapshot(path7) {
|
|
5273
5300
|
return this.state.get(path7);
|
|
5274
5301
|
}
|
|
5275
|
-
/**
|
|
5302
|
+
/**
|
|
5303
|
+
* Accept a message from the controller — writes to internal inbox. The
|
|
5304
|
+
* puppet reads it at its next LLM step, so a running (or idle) worker
|
|
5305
|
+
* always accepts.
|
|
5306
|
+
*/
|
|
5276
5307
|
acceptMessage(message) {
|
|
5277
5308
|
this.state.append("inbox", {
|
|
5278
5309
|
id: generateId("msg"),
|
|
@@ -5280,6 +5311,7 @@ var WorkerAgent = class extends BaseAgent {
|
|
|
5280
5311
|
timestamp: Date.now()
|
|
5281
5312
|
});
|
|
5282
5313
|
this.logger.info({ workerId: this.id }, "Controller message accepted");
|
|
5314
|
+
return true;
|
|
5283
5315
|
}
|
|
5284
5316
|
/** Unread controller messages (non-destructive) — engine reports these when the run ends. */
|
|
5285
5317
|
pendingMessages() {
|
|
@@ -5376,14 +5408,20 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5376
5408
|
// --- Controller messaging via streamInput ---
|
|
5377
5409
|
_messageQueue = [];
|
|
5378
5410
|
_messageResolve = null;
|
|
5379
|
-
/** Active SDK query — interrupted to deliver controller messages promptly. */
|
|
5380
|
-
_activeQuery = null;
|
|
5381
5411
|
/**
|
|
5382
|
-
*
|
|
5383
|
-
*
|
|
5384
|
-
*
|
|
5412
|
+
* True while run() is executing. Controller messages are rejected once the
|
|
5413
|
+
* run's input has closed (see `_inputClosed`), but idle multi-stage workers
|
|
5414
|
+
* (no run in flight) keep accepting — the next stage folds them into its
|
|
5415
|
+
* opening prompt.
|
|
5385
5416
|
*/
|
|
5386
|
-
|
|
5417
|
+
_runInFlight = false;
|
|
5418
|
+
/**
|
|
5419
|
+
* True once the prompt iterator reported done for the current run: the
|
|
5420
|
+
* session can no longer take input (a turn result arrived with nothing
|
|
5421
|
+
* queued, or the run was aborted). Further acceptMessage calls fail
|
|
5422
|
+
* explicitly instead of pretending to queue.
|
|
5423
|
+
*/
|
|
5424
|
+
_inputClosed = false;
|
|
5387
5425
|
// --- ask_controller via in-process SDK MCP server ---
|
|
5388
5426
|
_askController = null;
|
|
5389
5427
|
// --- Permission control via canUseTool ---
|
|
@@ -5392,31 +5430,17 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5392
5430
|
/** Exposed for engine to resolve pending permission requests. */
|
|
5393
5431
|
permissionHandler = {
|
|
5394
5432
|
resolvePermission: (requestId, approved, reason) => {
|
|
5395
|
-
|
|
5433
|
+
return this._pendingPermissions.resolve(
|
|
5396
5434
|
requestId,
|
|
5397
5435
|
approved ? { behavior: "allow" } : { behavior: "deny", message: reason ?? "Denied by controller" }
|
|
5398
5436
|
);
|
|
5399
|
-
if (ok) this.afterPermissionSettled();
|
|
5400
|
-
return ok;
|
|
5401
5437
|
},
|
|
5402
5438
|
cleanup: () => {
|
|
5403
|
-
this._interruptQueued = false;
|
|
5404
5439
|
this._pendingPermissions.cleanupAll(() => ({ behavior: "deny", message: "Worker aborted" }));
|
|
5405
5440
|
},
|
|
5406
5441
|
wrappedTools: {}
|
|
5407
5442
|
// not used for ClaudeCodeWorker
|
|
5408
5443
|
};
|
|
5409
|
-
/**
|
|
5410
|
-
* A permission settled (approved / denied / timed out). If a controller
|
|
5411
|
-
* message was waiting for the permission window to close, deliver its
|
|
5412
|
-
* interrupt now — this runs on BOTH the resolve and the timeout path.
|
|
5413
|
-
*/
|
|
5414
|
-
afterPermissionSettled() {
|
|
5415
|
-
if (this._interruptQueued && this._pendingPermissions.size === 0) {
|
|
5416
|
-
this._interruptQueued = false;
|
|
5417
|
-
this.requestInterrupt();
|
|
5418
|
-
}
|
|
5419
|
-
}
|
|
5420
5444
|
// Signal context set during run()
|
|
5421
5445
|
_signalQueue = null;
|
|
5422
5446
|
_runContext = null;
|
|
@@ -5463,6 +5487,8 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5463
5487
|
}
|
|
5464
5488
|
async run(task, _tools, onProgress) {
|
|
5465
5489
|
this._terminalResult = null;
|
|
5490
|
+
this._inputClosed = false;
|
|
5491
|
+
this._runInFlight = true;
|
|
5466
5492
|
this.state.set("runtimeState.task", { id: task.id, description: task.description });
|
|
5467
5493
|
const turnEnv = this.buildTurnEnv(task);
|
|
5468
5494
|
this._currentTurnEnv = turnEnv;
|
|
@@ -5520,18 +5546,17 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5520
5546
|
prompt: this.buildPromptIterable(task.description),
|
|
5521
5547
|
options
|
|
5522
5548
|
});
|
|
5523
|
-
this._activeQuery = stream;
|
|
5524
5549
|
try {
|
|
5525
5550
|
for await (const message of stream) {
|
|
5526
5551
|
if (this.abortController.signal.aborted) break;
|
|
5527
5552
|
this.processStreamMessage(message, onProgress);
|
|
5528
|
-
if (this._terminalResult) break;
|
|
5529
5553
|
}
|
|
5530
5554
|
} finally {
|
|
5531
|
-
this.
|
|
5555
|
+
this._inputClosed = true;
|
|
5532
5556
|
}
|
|
5533
5557
|
} catch (err) {
|
|
5534
5558
|
this.logger.error({ err, taskId: task.id }, "ClaudeCodeWorker query error");
|
|
5559
|
+
this._inputClosed = true;
|
|
5535
5560
|
if (!this._terminalResult) {
|
|
5536
5561
|
this._terminalResult = {
|
|
5537
5562
|
status: "failed",
|
|
@@ -5558,6 +5583,7 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5558
5583
|
startedAt: resultTs,
|
|
5559
5584
|
endedAt: resultTs
|
|
5560
5585
|
}, turnEnv));
|
|
5586
|
+
this._runInFlight = false;
|
|
5561
5587
|
return this._terminalResult;
|
|
5562
5588
|
}
|
|
5563
5589
|
/** Set signal context for permission requests. Called by engine before run(). */
|
|
@@ -5586,34 +5612,36 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5586
5612
|
getSnapshot(path7) {
|
|
5587
5613
|
return this.state.get(path7);
|
|
5588
5614
|
}
|
|
5589
|
-
/**
|
|
5615
|
+
/**
|
|
5616
|
+
* Accept a message from the controller. The message is queued and handed
|
|
5617
|
+
* to the SDK only when it pulls the next prompt: the CLI parks pulled
|
|
5618
|
+
* prompts in an internal queue that it drains strictly BETWEEN turns, so
|
|
5619
|
+
* delivery always lands on a turn boundary — a tool call in progress is
|
|
5620
|
+
* never cut off (the v0.6 interrupt delivery did exactly that and killed
|
|
5621
|
+
* the session with an orphaned tool_use; see docs/develop/v0.6-design.md
|
|
5622
|
+
* §6.1 — the hazard is any unclosed tool_use, not just a pending
|
|
5623
|
+
* permission).
|
|
5624
|
+
*
|
|
5625
|
+
* Returns false when delivery is impossible: the run's input has closed
|
|
5626
|
+
* (final result received) or the worker was aborted. Idle multi-stage
|
|
5627
|
+
* workers (no run in flight) always accept — the next stage folds the
|
|
5628
|
+
* message into its opening prompt.
|
|
5629
|
+
*/
|
|
5590
5630
|
acceptMessage(message) {
|
|
5631
|
+
if (this.abortController.signal.aborted) return false;
|
|
5632
|
+
if (this._runInFlight && this._inputClosed) return false;
|
|
5591
5633
|
this._messageQueue.push(message);
|
|
5592
5634
|
if (this._messageResolve) {
|
|
5593
5635
|
this._messageResolve();
|
|
5594
5636
|
this._messageResolve = null;
|
|
5595
5637
|
}
|
|
5596
|
-
if ((this.config.interruptOnMessage ?? "immediate") === "immediate") {
|
|
5597
|
-
this.requestInterrupt();
|
|
5598
|
-
}
|
|
5599
5638
|
this.logger.info({ workerId: this.id }, "Controller message accepted");
|
|
5639
|
+
return true;
|
|
5600
5640
|
}
|
|
5601
5641
|
/** Unread controller messages (non-destructive) — engine reports these when the run ends. */
|
|
5602
5642
|
pendingMessages() {
|
|
5603
5643
|
return [...this._messageQueue];
|
|
5604
5644
|
}
|
|
5605
|
-
/** Interrupt the active query unless a permission is pending (queued until it settles). */
|
|
5606
|
-
requestInterrupt() {
|
|
5607
|
-
if (this.abortController.signal.aborted || !this._activeQuery) return;
|
|
5608
|
-
if (this._pendingPermissions.size > 0) {
|
|
5609
|
-
this._interruptQueued = true;
|
|
5610
|
-
return;
|
|
5611
|
-
}
|
|
5612
|
-
if (typeof this._activeQuery.interrupt !== "function") return;
|
|
5613
|
-
this._activeQuery.interrupt().catch((err) => {
|
|
5614
|
-
this.logger.warn({ workerId: this.id, err: String(err) }, "interrupt() failed");
|
|
5615
|
-
});
|
|
5616
|
-
}
|
|
5617
5645
|
/**
|
|
5618
5646
|
* Provide the engine-backed ask_controller implementation. Once set, the
|
|
5619
5647
|
* worker exposes it to Claude Code as an in-process SDK MCP tool.
|
|
@@ -5651,9 +5679,7 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
|
|
|
5651
5679
|
...this._turnLineage
|
|
5652
5680
|
});
|
|
5653
5681
|
return this._pendingPermissions.wait(requestId, _ClaudeCodeWorker.PERMISSION_TIMEOUT_MS, () => {
|
|
5654
|
-
|
|
5655
|
-
this.afterPermissionSettled();
|
|
5656
|
-
return value;
|
|
5682
|
+
return { behavior: "deny", message: "Permission request timed out" };
|
|
5657
5683
|
});
|
|
5658
5684
|
}
|
|
5659
5685
|
// --- Prompt as AsyncIterable (supports follow-up messages) ---
|
|
@@ -5680,13 +5706,22 @@ ${leftoverText}` : `${buildThirdPartyPrompt(this.skills)}${initialPrompt}`;
|
|
|
5680
5706
|
};
|
|
5681
5707
|
}
|
|
5682
5708
|
while (true) {
|
|
5683
|
-
if (this.abortController.signal.aborted
|
|
5709
|
+
if (this.abortController.signal.aborted) {
|
|
5710
|
+
this._inputClosed = true;
|
|
5684
5711
|
return { value: void 0, done: true };
|
|
5685
5712
|
}
|
|
5686
|
-
if (this.
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5713
|
+
if (this._terminalResult) {
|
|
5714
|
+
if (this._messageQueue.length === 0) {
|
|
5715
|
+
this._inputClosed = true;
|
|
5716
|
+
return { value: void 0, done: true };
|
|
5717
|
+
}
|
|
5718
|
+
} else if (this._messageQueue.length === 0) {
|
|
5719
|
+
await new Promise((resolve11) => {
|
|
5720
|
+
this._messageResolve = resolve11;
|
|
5721
|
+
});
|
|
5722
|
+
continue;
|
|
5723
|
+
}
|
|
5724
|
+
break;
|
|
5690
5725
|
}
|
|
5691
5726
|
const msg = this._messageQueue.shift();
|
|
5692
5727
|
return {
|
|
@@ -6353,7 +6388,7 @@ function updateProgressTool(deps) {
|
|
|
6353
6388
|
import { z as z23 } from "zod";
|
|
6354
6389
|
function chatWorkerTool(deps) {
|
|
6355
6390
|
return defineTool({
|
|
6356
|
-
description:
|
|
6391
|
+
description: `Send a message to a running worker. Use this to provide additional guidance, correct the worker's approach, or ask the worker to check something. The worker reads it at its next step (puppet) or turn boundary (third-party); idle multi-stage workers read it when their next stage starts. A third-party worker NEVER acts on the message mid-tool-call: it finishes the current turn first, so "queued" can mean a delay of minutes. If the worker has already finished, this tool fails explicitly \u2014 inspect its output instead.`,
|
|
6357
6392
|
parameters: z23.object({
|
|
6358
6393
|
workerId: z23.string().describe("ID of the target worker"),
|
|
6359
6394
|
message: z23.string().describe("Message to send to the worker")
|
|
@@ -6363,8 +6398,11 @@ function chatWorkerTool(deps) {
|
|
|
6363
6398
|
if (status === "not_running") {
|
|
6364
6399
|
return `[ERROR] Worker ${workerId} is not running (it may have just finished).`;
|
|
6365
6400
|
}
|
|
6401
|
+
if (status === "finished") {
|
|
6402
|
+
return `[ERROR] Worker ${workerId} has already finished; message not delivered. Use get_task_detail to inspect its output.`;
|
|
6403
|
+
}
|
|
6366
6404
|
if (status === "queued") {
|
|
6367
|
-
return `Message queued for worker ${workerId} \u2014 it
|
|
6405
|
+
return `Message queued for worker ${workerId} \u2014 QUEUED only, not delivered yet: the worker reads it at its next step (puppet), turn boundary (third-party \u2014 the current turn, including any running tool call, finishes first) or next stage start (idle). A stuck worker will never reach that point. Verify with get_task_detail before relying on it; if it looks stuck, use cancel_task instead of waiting for this message.`;
|
|
6368
6406
|
}
|
|
6369
6407
|
return `Message delivered to worker ${workerId} \u2014 it will be read at the worker's next step.`;
|
|
6370
6408
|
})
|
|
@@ -6885,14 +6923,16 @@ var TaskExecuteEngine = class {
|
|
|
6885
6923
|
* Send a message to a worker. Delivery is judged by the activeWorkers
|
|
6886
6924
|
* runtime state — NOT by the workers Map, which lingers one tick past
|
|
6887
6925
|
* disposal (removeWorkerTracking defers) and would accept messages for a
|
|
6888
|
-
* worker that is already gone.
|
|
6926
|
+
* worker that is already gone. A worker that rejects the message (its run
|
|
6927
|
+
* can no longer take input) is reported as 'finished' — never a fake
|
|
6928
|
+
* 'queued'.
|
|
6889
6929
|
*/
|
|
6890
6930
|
sendMessageToWorker(workerId, message) {
|
|
6891
6931
|
const info = this.getActiveWorkers().find((w) => w.workerId === workerId);
|
|
6892
6932
|
if (!info) return "not_running";
|
|
6893
6933
|
const worker = this.workers.get(workerId);
|
|
6894
6934
|
if (!worker) return "not_running";
|
|
6895
|
-
worker.acceptMessage(message);
|
|
6935
|
+
if (!worker.acceptMessage(message)) return "finished";
|
|
6896
6936
|
if (info.status === "idle") return "queued";
|
|
6897
6937
|
return worker instanceof WorkerAgent ? "delivered" : "queued";
|
|
6898
6938
|
}
|
|
@@ -7556,7 +7596,7 @@ var ControllerLoop = class {
|
|
|
7556
7596
|
this.signalQueue = signalQueue;
|
|
7557
7597
|
this.controllerAgent = controllerAgent;
|
|
7558
7598
|
this.heartbeatProvider = heartbeatProvider;
|
|
7559
|
-
this.heartbeatIntervalMs = options?.heartbeatIntervalMs ??
|
|
7599
|
+
this.heartbeatIntervalMs = options?.heartbeatIntervalMs ?? 6e4;
|
|
7560
7600
|
}
|
|
7561
7601
|
ctx;
|
|
7562
7602
|
signalQueue;
|
|
@@ -9245,8 +9285,11 @@ async function runWayangServer(options, io = process) {
|
|
|
9245
9285
|
return { status };
|
|
9246
9286
|
});
|
|
9247
9287
|
rpc.register(METHOD.CHAT_WORKER, (params) => {
|
|
9248
|
-
supervisor.engine.sendMessageToWorker(
|
|
9249
|
-
|
|
9288
|
+
const status = supervisor.engine.sendMessageToWorker(
|
|
9289
|
+
requiredString(params, "workerId"),
|
|
9290
|
+
requiredString(params, "message")
|
|
9291
|
+
);
|
|
9292
|
+
return { status };
|
|
9250
9293
|
});
|
|
9251
9294
|
rpc.register(METHOD.DISPOSE_WORKER, (params) => {
|
|
9252
9295
|
supervisor.engine.abortByWorkerId(requiredString(params, "workerId"));
|