wayang-ai 0.8.2 → 0.8.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.
Files changed (2) hide show
  1. package/dist/cli.js +242 -180
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import * as path8 from "path";
4
+ import * as path9 from "path";
5
5
 
6
6
  // src/cli-args.ts
7
7
  import meow from "meow";
@@ -463,11 +463,11 @@ var RemoteStateStore = class {
463
463
  rpc;
464
464
  entries = /* @__PURE__ */ new Map();
465
465
  byId = /* @__PURE__ */ new Map();
466
- subscribe(target, path9, callback) {
467
- const key = `${target}:${path9}`;
466
+ subscribe(target, path10, callback) {
467
+ const key = `${target}:${path10}`;
468
468
  let entry = this.entries.get(key);
469
469
  if (!entry) {
470
- entry = { subscriptionId: "", target, path: path9, data: void 0, listeners: /* @__PURE__ */ new Set(), settled: false };
470
+ entry = { subscriptionId: "", target, path: path10, data: void 0, listeners: /* @__PURE__ */ new Set(), settled: false };
471
471
  this.entries.set(key, entry);
472
472
  this.connect(entry, key);
473
473
  }
@@ -477,8 +477,8 @@ var RemoteStateStore = class {
477
477
  if (entry.listeners.size === 0) this.teardown(entry, key);
478
478
  };
479
479
  }
480
- getSnapshot(target, path9) {
481
- return this.entries.get(`${target}:${path9}`)?.data;
480
+ getSnapshot(target, path10) {
481
+ return this.entries.get(`${target}:${path10}`)?.data;
482
482
  }
483
483
  connect(entry, key) {
484
484
  this.rpc.request(METHOD.SUBSCRIBE, { target: entry.target, path: entry.path }).then((res) => {
@@ -516,11 +516,11 @@ var RemoteSubscribable = class {
516
516
  }
517
517
  store;
518
518
  target;
519
- subscribe(path9, callback) {
520
- return this.store.subscribe(this.target, path9, callback);
519
+ subscribe(path10, callback) {
520
+ return this.store.subscribe(this.target, path10, callback);
521
521
  }
522
- getSnapshot(path9) {
523
- return this.store.getSnapshot(this.target, path9);
522
+ getSnapshot(path10) {
523
+ return this.store.getSnapshot(this.target, path10);
524
524
  }
525
525
  };
526
526
 
@@ -1475,43 +1475,43 @@ var BaseWayangState = class {
1475
1475
  }
1476
1476
  // --- Read ---
1477
1477
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- public default generic: bare get() callers across the repo rely on it; per-call sites pass an explicit T
1478
- get(path9) {
1479
- return get(this.data, path9);
1478
+ get(path10) {
1479
+ return get(this.data, path10);
1480
1480
  }
1481
1481
  // --- Write operations ---
1482
- set(path9, value) {
1483
- const prev = get(this.data, path9);
1484
- set(this.data, path9, value);
1485
- this.persist(path9, "set");
1486
- this.notify(path9, "set", prev);
1482
+ set(path10, value) {
1483
+ const prev = get(this.data, path10);
1484
+ set(this.data, path10, value);
1485
+ this.persist(path10, "set");
1486
+ this.notify(path10, "set", prev);
1487
1487
  }
1488
1488
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- partial update merges an untyped object into the state tree root (see `data`)
1489
- update(path9, partial) {
1490
- const prev = cloneDeep(get(this.data, path9));
1491
- const target = get(this.data, path9);
1489
+ update(path10, partial) {
1490
+ const prev = cloneDeep(get(this.data, path10));
1491
+ const target = get(this.data, path10);
1492
1492
  if (target && typeof target === "object") {
1493
1493
  Object.assign(target, partial);
1494
1494
  } else {
1495
- set(this.data, path9, partial);
1495
+ set(this.data, path10, partial);
1496
1496
  }
1497
- this.persist(path9, "update");
1498
- this.notify(path9, "update", prev);
1497
+ this.persist(path10, "update");
1498
+ this.notify(path10, "update", prev);
1499
1499
  }
1500
- append(path9, entry) {
1501
- const arr = get(this.data, path9);
1500
+ append(path10, entry) {
1501
+ const arr = get(this.data, path10);
1502
1502
  if (!Array.isArray(arr)) {
1503
- throw new Error(`State.append: path "${path9}" is not an array`);
1503
+ throw new Error(`State.append: path "${path10}" is not an array`);
1504
1504
  }
1505
- const stamped = path9 === "conversation" ? applyAppendEnvelope(entry, arr) : entry;
1505
+ const stamped = path10 === "conversation" ? applyAppendEnvelope(entry, arr) : entry;
1506
1506
  arr.push(stamped);
1507
- set(this.data, path9, [...arr]);
1508
- this.persist(path9, "append", stamped);
1509
- this.notify(path9, "append", void 0);
1507
+ set(this.data, path10, [...arr]);
1508
+ this.persist(path10, "append", stamped);
1509
+ this.notify(path10, "append", void 0);
1510
1510
  }
1511
- remove(path9, idx) {
1512
- const arr = get(this.data, path9);
1511
+ remove(path10, idx) {
1512
+ const arr = get(this.data, path10);
1513
1513
  if (!Array.isArray(arr)) {
1514
- throw new Error(`State.remove: path "${path9}" is not an array`);
1514
+ throw new Error(`State.remove: path "${path10}" is not an array`);
1515
1515
  }
1516
1516
  const prev = idx !== void 0 ? arr[idx] : arr[arr.length - 1];
1517
1517
  if (idx === void 0) {
@@ -1519,16 +1519,16 @@ var BaseWayangState = class {
1519
1519
  } else {
1520
1520
  arr.splice(idx, 1);
1521
1521
  }
1522
- this.persist(path9, "remove");
1523
- this.notify(path9, "remove", prev);
1522
+ this.persist(path10, "remove");
1523
+ this.notify(path10, "remove", prev);
1524
1524
  }
1525
1525
  // --- Subscription ---
1526
- on(path9, callback) {
1527
- if (!this.subscriptions.has(path9)) {
1528
- this.subscriptions.set(path9, /* @__PURE__ */ new Set());
1526
+ on(path10, callback) {
1527
+ if (!this.subscriptions.has(path10)) {
1528
+ this.subscriptions.set(path10, /* @__PURE__ */ new Set());
1529
1529
  }
1530
- this.subscriptions.get(path9).add(callback);
1531
- return () => this.subscriptions.get(path9)?.delete(callback);
1530
+ this.subscriptions.get(path10).add(callback);
1531
+ return () => this.subscriptions.get(path10)?.delete(callback);
1532
1532
  }
1533
1533
  // --- Persistence ---
1534
1534
  /**
@@ -1536,8 +1536,8 @@ var BaseWayangState = class {
1536
1536
  * for the helper to pick its own strategy. Snapshot helpers replace the
1537
1537
  * file; append helpers write only the new entry and ignore window edits.
1538
1538
  */
1539
- persist(path9, op, entry) {
1540
- const topKey = path9.split(".")[0];
1539
+ persist(path10, op, entry) {
1540
+ const topKey = path10.split(".")[0];
1541
1541
  for (const mapping of this.persistMappings) {
1542
1542
  if (mapping.path !== topKey) continue;
1543
1543
  mapping.helper.write({
@@ -2169,8 +2169,8 @@ function isSameWorkspacePath(a, b) {
2169
2169
  function resolveWorkspaceSlug(workspace) {
2170
2170
  const slug = workspace?.slug?.trim();
2171
2171
  if (slug) return slug;
2172
- const path9 = workspace?.path?.trim();
2173
- return path9 ? workspaceSlugFromPath(path9) : null;
2172
+ const path10 = workspace?.path?.trim();
2173
+ return path10 ? workspaceSlugFromPath(path10) : null;
2174
2174
  }
2175
2175
  function formatTaskWorkspaceMark(workspace, defaultWorkspacePath) {
2176
2176
  const slug = resolveWorkspaceSlug(workspace);
@@ -2364,27 +2364,27 @@ function toolTarget(toolName, args, budget) {
2364
2364
  return fitWithSuffix(lines[0].trim(), note, budget);
2365
2365
  }
2366
2366
  case "write_file": {
2367
- const path9 = argString(args, ["path", "file_path"]);
2368
- if (!path9) return "";
2367
+ const path10 = argString(args, ["path", "file_path"]);
2368
+ if (!path10) return "";
2369
2369
  const lines = lineCount(args.content);
2370
2370
  const note = lines > 0 ? ` (+${lines})` : "";
2371
- return fitWithSuffix(shortenPath(path9), note, budget);
2371
+ return fitWithSuffix(shortenPath(path10), note, budget);
2372
2372
  }
2373
2373
  case "edit_file": {
2374
- const path9 = argString(args, ["path", "file_path"]);
2375
- if (!path9) return "";
2374
+ const path10 = argString(args, ["path", "file_path"]);
2375
+ if (!path10) return "";
2376
2376
  const oldLines = lineCount(args.old_string);
2377
2377
  const newLines = lineCount(args.new_string);
2378
2378
  const note = oldLines > 0 || newLines > 0 ? ` (${oldLines}\u2192${newLines} \u884C)` : "";
2379
- return fitWithSuffix(shortenPath(path9), note, budget);
2379
+ return fitWithSuffix(shortenPath(path10), note, budget);
2380
2380
  }
2381
2381
  case "read_file": {
2382
- const path9 = argString(args, ["path", "file_path"]);
2383
- if (!path9) return "";
2382
+ const path10 = argString(args, ["path", "file_path"]);
2383
+ if (!path10) return "";
2384
2384
  const offset = argNumber(args, ["offset"]);
2385
2385
  const limit = argNumber(args, ["limit"]);
2386
2386
  const range = offset !== void 0 ? `:${offset}${limit !== void 0 ? `+${limit}` : ""}` : "";
2387
- return truncateToWidth(shortenPath(path9) + range, budget);
2387
+ return truncateToWidth(shortenPath(path10) + range, budget);
2388
2388
  }
2389
2389
  case "search_files":
2390
2390
  return truncateToWidth(argString(args, ["pattern"]) ?? "", budget);
@@ -4063,15 +4063,15 @@ import { Box as Box8, Text as Text9, useStdout } from "ink";
4063
4063
 
4064
4064
  // src/tui/hooks/use-wayang-state.ts
4065
4065
  import { useState as useState4, useEffect as useEffect3 } from "react";
4066
- function useWayangState(source, path9) {
4067
- const [value, setValue] = useState4(() => source.getSnapshot(path9));
4066
+ function useWayangState(source, path10) {
4067
+ const [value, setValue] = useState4(() => source.getSnapshot(path10));
4068
4068
  useEffect3(() => {
4069
- const unSub = source.subscribe(path9, () => {
4070
- setValue(source.getSnapshot(path9));
4069
+ const unSub = source.subscribe(path10, () => {
4070
+ setValue(source.getSnapshot(path10));
4071
4071
  });
4072
- setValue(source.getSnapshot(path9));
4072
+ setValue(source.getSnapshot(path10));
4073
4073
  return unSub;
4074
- }, [source, path9]);
4074
+ }, [source, path10]);
4075
4075
  return value;
4076
4076
  }
4077
4077
 
@@ -4571,7 +4571,7 @@ function AppContent() {
4571
4571
  // package.json
4572
4572
  var package_default = {
4573
4573
  name: "wayang-ai",
4574
- version: "0.8.2",
4574
+ version: "0.8.3",
4575
4575
  description: "Multi-agent orchestration platform",
4576
4576
  type: "module",
4577
4577
  license: "MIT",
@@ -5471,11 +5471,11 @@ var SignalQueue = class {
5471
5471
  this.counter = Math.max(mark, this.maxIdInWindow());
5472
5472
  }
5473
5473
  // --- Subscribable ---
5474
- subscribe(path9, callback) {
5475
- return this.state.on(path9, callback);
5474
+ subscribe(path10, callback) {
5475
+ return this.state.on(path10, callback);
5476
5476
  }
5477
- getSnapshot(path9) {
5478
- return this.state.get(path9);
5477
+ getSnapshot(path10) {
5478
+ return this.state.get(path10);
5479
5479
  }
5480
5480
  enqueue(sig) {
5481
5481
  const id = `sig-${++this.counter}`;
@@ -6104,9 +6104,9 @@ function workerTranscriptPath(sessionDir, workerId) {
6104
6104
  }
6105
6105
  function readWorkerTranscript(sessionDir, workerId) {
6106
6106
  if (!isValidWorkerId(workerId)) return null;
6107
- const path9 = workerTranscriptPath(sessionDir, workerId);
6108
- if (!existsSync8(path9)) return null;
6109
- return new JSONLFileHelper(path9).read();
6107
+ const path10 = workerTranscriptPath(sessionDir, workerId);
6108
+ if (!existsSync8(path10)) return null;
6109
+ return new JSONLFileHelper(path10).read();
6110
6110
  }
6111
6111
  var MAX_TRANSCRIPT_WINDOW_BYTES = 2 * 1024 * 1024;
6112
6112
  var MIN_WINDOW_BYTES = 64 * 1024;
@@ -6136,10 +6136,10 @@ function parseWindow(text, startsAtFileHead) {
6136
6136
  }
6137
6137
  function readWorkerTranscriptWindow(sessionDir, workerId, opts = {}) {
6138
6138
  if (!isValidWorkerId(workerId)) return null;
6139
- const path9 = workerTranscriptPath(sessionDir, workerId);
6139
+ const path10 = workerTranscriptPath(sessionDir, workerId);
6140
6140
  let size;
6141
6141
  try {
6142
- size = statSync2(path9).size;
6142
+ size = statSync2(path10).size;
6143
6143
  } catch {
6144
6144
  return null;
6145
6145
  }
@@ -6153,7 +6153,7 @@ function readWorkerTranscriptWindow(sessionDir, workerId, opts = {}) {
6153
6153
  const readLen = Math.min(size, windowBytes);
6154
6154
  const start = size - readLen;
6155
6155
  const chunk = Buffer.alloc(readLen);
6156
- const fd = openSync4(path9, "r");
6156
+ const fd = openSync4(path10, "r");
6157
6157
  try {
6158
6158
  readSync2(fd, chunk, 0, readLen, start);
6159
6159
  } finally {
@@ -7057,6 +7057,23 @@ ${lines}`
7057
7057
  );
7058
7058
  }
7059
7059
 
7060
+ // src/infra/general-workspace.ts
7061
+ import { mkdirSync as mkdirSync6 } from "fs";
7062
+ import { homedir } from "os";
7063
+ import { join as join14 } from "path";
7064
+ var GENERAL_WORKSPACE_DIRNAME = "workspace";
7065
+ function wayangDirFor(ctx) {
7066
+ return wayangDirForSessionDir(ctx.sessionDir) ?? join14(homedir(), ".wayang");
7067
+ }
7068
+ function resolveGeneralWorkspace(ctx) {
7069
+ return join14(wayangDirFor(ctx), GENERAL_WORKSPACE_DIRNAME);
7070
+ }
7071
+ function ensureGeneralWorkspace(ctx) {
7072
+ const dir = resolveGeneralWorkspace(ctx);
7073
+ mkdirSync6(dir, { recursive: true });
7074
+ return dir;
7075
+ }
7076
+
7060
7077
  // src/services/agents/prompts/controller-prompt.ts
7061
7078
  var IDENTITY = section(
7062
7079
  "Identity",
@@ -7333,13 +7350,17 @@ You resumed a previous session that had unfinished tasks. Workers were NOT autom
7333
7350
 
7334
7351
  Do NOT blindly re-create everything. The user may have moved on. Prefer to summarize and ask unless the intent is obvious.`
7335
7352
  );
7336
- var WORKING_PRINCIPLES = section(
7337
- "Working principles",
7338
- `\u3010\u4EFB\u52A1\u62C6\u5206\u3011
7353
+ function buildWorkingPrinciples(generalWorkspace) {
7354
+ const body = `\u3010\u4EFB\u52A1\u62C6\u5206\u3011
7339
7355
  - \u4E00\u4E2A\u4EFB\u52A1\u53EA\u4EA4\u4ED8\u4E00\u4E2A\u53EF\u72EC\u7ACB\u9A8C\u6536\u7684\u7ED3\u679C\uFF1B\u4E0D\u8981\u628A\u8BBE\u8BA1\u3001\u5B9E\u73B0\u3001\u6D4B\u8BD5\u585E\u8FDB\u540C\u4E00\u6BB5\u3002
7340
7356
  - \u5148\u505A\u80FD\u88AB\u5355\u6D4B\u65AD\u8A00\u7684\u6838\u5FC3\u903B\u8F91\uFF0C\u628A\u8FB9\u754C\uFF08\u5BBD\u5B57\u7B26\u3001\u4EE3\u7406\u5BF9\u3001\u6298\u884C\u4F4D\u7F6E\u4E4B\u7C7B\uFF09\u56FA\u5316\u6210\u7528\u4F8B\uFF0C\u518D\u53E0\u52A0\u96C6\u6210\u4E0E\u4EA4\u4E92\u5C42\u3002
7341
7357
  - \u4EFB\u52A1\u63CF\u8FF0\u5FC5\u987B\u81EA\u5305\u542B\uFF1A\u80CC\u666F\u3001\u76EE\u6807\u3001\u8F93\u5165\u8F93\u51FA\u3001\u7EA6\u675F\u3001\u9A8C\u6536\u6807\u51C6\u3002\u6267\u884C\u65B9\u770B\u4E0D\u5230\u4F60\u7684\u5BF9\u8BDD\u3002
7342
7358
 
7359
+ \u3010\u5DE5\u4F5C\u533A\u3011
7360
+ - \u7528\u6237\u7ED9\u4E86\u76EE\u5F55\uFF0C\u6216\u4EFB\u52A1\u660E\u663E\u5C5E\u4E8E\u67D0\u4E2A\u4EE3\u7801\u5E93\uFF1A\u7528\u90A3\u4E2A\u76EE\u5F55\u3002
7361
+ - \u7528\u6237\u6CA1\u6307\u5B9A\u3001\u6216\u4EFB\u52A1\u4E0E\u4EE3\u7801\u5E93\u65E0\u5173\uFF08\u8C03\u7814\u3001\u8D44\u6599\u6574\u7406\u3001\u4FE1\u606F\u67E5\u8BE2\u3001\u5199\u6587\u6863\u8349\u7A3F\uFF09\uFF1A\u7528\u901A\u7528\u5DE5\u4F5C\u533A ${generalWorkspace}\uFF0C\u5B83\u4E0D\u5C5E\u4E8E\u4EFB\u4F55\u4ED3\u5E93\uFF0C\u9996\u6B21\u4F7F\u7528\u81EA\u52A8\u521B\u5EFA\u3002
7362
+ - \u4EFB\u52A1\u4F1A\u7559\u4E0B\u4E2D\u95F4\u4EA7\u7269\u65F6\u5199\u8FDB\u63CF\u8FF0\uFF1A\u4E34\u65F6\u6587\u4EF6\u653E\u4EFB\u52A1 workspace \u5185\u7684 .agents/tmp/\uFF0C\u4E0D\u8981\u7528 /tmp\u3002
7363
+
7343
7364
  \u3010\u5206\u5DE5\u4E0E\u5BA1\u6838\u3011
7344
7365
  - \u8BBE\u8BA1\u5224\u65AD\u4E0E\u673A\u68B0\u5B9E\u73B0\u5206\u5F00\u6D3E\uFF1A\u8BBE\u8BA1\u65B9\u5148\u5B9E\u6D4B\u5173\u952E\u884C\u4E3A\uFF0C\u4EA7\u51FA\u300C\u6539\u54EA\u4E2A\u6587\u4EF6\u3001\u51FD\u6570\u7B7E\u540D\u3001\u6570\u636E\u7ED3\u6784\u3001\u8FB9\u754C\u60C5\u51B5\u3001\u6D4B\u8BD5\u6E05\u5355\u300D\u7684\u65B9\u6848\uFF1B\u6267\u884C\u65B9\u7167\u65B9\u6848\u7F16\u7801\uFF0C\u4E0D\u505A\u8BBE\u8BA1\u51B3\u7B56\u3002
7345
7366
  - \u65B9\u6848\u91CC\u8981\u663E\u5F0F\u5199\u51FA\u300C\u4E0D\u8981\u505A\u4EC0\u4E48\u300D\uFF0C\u5426\u5219\u6267\u884C\u65B9\u4F1A\u987A\u624B\u91CD\u6784\u65E0\u5173\u4EE3\u7801\u3002
@@ -7364,8 +7385,9 @@ var WORKING_PRINCIPLES = section(
7364
7385
  - \u540C\u533A\u4E92\u5377 \u2014\u2014 \u8BA9\u4E24\u4E2A\u6267\u884C\u8005\u5728\u540C\u4E00\u5DE5\u4F5C\u533A\u5E76\u53D1\u63D0\u4EA4\uFF0C\u4E92\u76F8\u628A\u5BF9\u65B9\u7684\u6539\u52A8\u5377\u8FDB\u81EA\u5DF1\u7684 commit\u3002
7365
7386
  - \u9759\u9ED8\u5224\u6B7B \u2014\u2014 \u51ED\u8F93\u51FA\u9759\u9ED8\u5224\u65AD\u6B7B\u6D3B\uFF1A\u628A\u5728\u8DD1\u957F\u4EFB\u52A1\u5F53\u6210\u5361\u6B7B\u6740\u6389\uFF0C\u6216\u628A\u6D3B\u9501\u5F53\u6210\u6162\u4EFB\u52A1\u6B7B\u7B49\u3002
7366
7387
  - \u7EFF\u706F\u5373\u5B8C\u6210 \u2014\u2014 \u5355\u6D4B\u6216\u6D41\u7A0B\u5168\u7EFF\u5C31\u5BA3\u5E03\u5B8C\u6210\uFF1B\u5076\u53D1\u5931\u8D25\u590D\u8DD1\u4E00\u6B21\u901A\u8FC7\u5C31\u5F53\u6CA1\u53D1\u751F\uFF0C\u4E0D\u505A\u8BB0\u5F55\u3002
7367
- - \u96F6\u788E\u53D6\u8BC1 \u2014\u2014 \u628A\u4E00\u6B21\u53D6\u8BC1\u62C6\u6210\u51E0\u5341\u6761\u96F6\u788E\u53EA\u8BFB\u547D\u4EE4\u9010\u6761\u7B49\u6279\u51C6\uFF0C\u6392\u969C\u88AB\u6D41\u7A0B\u672C\u8EAB\u62D6\u6B7B\u3002`
7368
- );
7388
+ - \u96F6\u788E\u53D6\u8BC1 \u2014\u2014 \u628A\u4E00\u6B21\u53D6\u8BC1\u62C6\u6210\u51E0\u5341\u6761\u96F6\u788E\u53EA\u8BFB\u547D\u4EE4\u9010\u6761\u7B49\u6279\u51C6\uFF0C\u6392\u969C\u88AB\u6D41\u7A0B\u672C\u8EAB\u62D6\u6B7B\u3002`;
7389
+ return section("Working principles", body);
7390
+ }
7369
7391
  var HARD_CONSTRAINTS = section(
7370
7392
  "Hard constraints",
7371
7393
  `- NEVER execute commands or write files yourself. Delegate via add_task\uFF08worker will execute\uFF09.
@@ -7391,7 +7413,7 @@ function buildControllerSystemPrompt(ctx, skills, opts) {
7391
7413
  TOOL_USAGE,
7392
7414
  PLANNING_TOOLS,
7393
7415
  SIGNAL_HANDLING,
7394
- WORKING_PRINCIPLES,
7416
+ buildWorkingPrinciples(resolveGeneralWorkspace(ctx)),
7395
7417
  HARD_CONSTRAINTS,
7396
7418
  buildWorkerList(ctx.config.workers),
7397
7419
  buildSkillCatalog(skills),
@@ -7404,7 +7426,7 @@ function buildControllerSystemPrompt(ctx, skills, opts) {
7404
7426
 
7405
7427
  // src/services/agents/prompts/controller-rules.ts
7406
7428
  import { readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
7407
- import { join as join14 } from "path";
7429
+ import { join as join15 } from "path";
7408
7430
  var CONTROLLER_RULES_FILENAME = "AGENTS.md";
7409
7431
  var CONTROLLER_RULES_MAX_BYTES = 32 * 1024;
7410
7432
  function loadControllerRules(opts) {
@@ -7451,13 +7473,13 @@ ${intro}`, ...bodies];
7451
7473
  return parts.join("\n\n");
7452
7474
  }
7453
7475
  function readRulesFile(dir) {
7454
- const candidate = join14(dir, CONTROLLER_RULES_FILENAME);
7476
+ const candidate = join15(dir, CONTROLLER_RULES_FILENAME);
7455
7477
  try {
7456
- const path9 = realpathSync2(candidate);
7457
- if (!statSync3(path9).isFile()) return null;
7458
- const content = readFileSync8(path9, "utf8");
7478
+ const path10 = realpathSync2(candidate);
7479
+ if (!statSync3(path10).isFile()) return null;
7480
+ const content = readFileSync8(path10, "utf8");
7459
7481
  if (!content.trim()) return null;
7460
- return { path: path9, content };
7482
+ return { path: path10, content };
7461
7483
  } catch {
7462
7484
  return null;
7463
7485
  }
@@ -7470,7 +7492,7 @@ function truncateToBytes(text, maxBytes) {
7470
7492
 
7471
7493
  // src/services/agents/prompts/project-rules.ts
7472
7494
  import { existsSync as existsSync9, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
7473
- import { basename as basename5, dirname as dirname6, join as join15, resolve as resolve3 } from "path";
7495
+ import { basename as basename5, dirname as dirname6, join as join16, resolve as resolve3 } from "path";
7474
7496
  var RULE_FILE_NAMES = ["AGENTS.md", "CLAUDE.md"];
7475
7497
  var MAX_LEVELS = 5;
7476
7498
  var PROJECT_RULES_MAX_LENGTH = 8e3;
@@ -7479,7 +7501,7 @@ function findProjectRules(startDir) {
7479
7501
  for (let level = 0; level < MAX_LEVELS; level++) {
7480
7502
  const found = readRuleFile(current);
7481
7503
  if (found) return found;
7482
- if (existsSync9(join15(current, ".git"))) break;
7504
+ if (existsSync9(join16(current, ".git"))) break;
7483
7505
  const parent = dirname6(current);
7484
7506
  if (parent === current) break;
7485
7507
  current = parent;
@@ -7488,12 +7510,12 @@ function findProjectRules(startDir) {
7488
7510
  }
7489
7511
  function readRuleFile(dir) {
7490
7512
  for (const name of RULE_FILE_NAMES) {
7491
- const path9 = join15(dir, name);
7513
+ const path10 = join16(dir, name);
7492
7514
  try {
7493
- if (!statSync4(path9).isFile()) continue;
7494
- const content = readFileSync9(path9, "utf8");
7515
+ if (!statSync4(path10).isFile()) continue;
7516
+ const content = readFileSync9(path10, "utf8");
7495
7517
  if (!content.trim()) continue;
7496
- return { path: path9, content };
7518
+ return { path: path10, content };
7497
7519
  } catch {
7498
7520
  }
7499
7521
  }
@@ -7569,6 +7591,9 @@ Only use bash when no dedicated tool fits the task (e.g. running builds, install
7569
7591
  - edit_file modifies an existing file by replacing a unique string. The old_string must appear exactly once. If it matches multiple times, the tool will tell you \u2014 expand the surrounding context to make it unique.
7570
7592
  - Always verify changes: after write_file or edit_file, use read_file to confirm the content is correct. For large files, use offset and limit to read specific sections instead of the entire file.
7571
7593
  - Use relative paths (relative to workspace). Accessing files outside workspace requires controller approval.
7594
+ - Scratch files and intermediate artifacts go to \`.agents/tmp/\` inside the current task workspace. Never \`/tmp\` \u2014 it sits outside the workspace, so every command there costs an approval \u2014 and never the home directory or the repository root.
7595
+ - Anything worth keeping goes to a normal path in the workspace (it is version-controlled); one-off intermediates stay in \`.agents/tmp/\`.
7596
+ - Before writing, confirm which workspace you are in: \`pwd\`, or the workspace named in the task description \u2014 that one outranks any path you assume.
7572
7597
 
7573
7598
  ## Web search
7574
7599
 
@@ -7688,6 +7713,7 @@ var RULES = [
7688
7713
  "Report meaningful progress as you work (entering a new phase, completing a sub-step, recovering from an error).",
7689
7714
  "Do NOT output a final summary right before completing \u2014 the system already captures the result separately.",
7690
7715
  "Match the language of the task description.",
7716
+ "Keep scratch files and intermediate artifacts in `.agents/tmp/` inside the task workspace \u2014 never `/tmp`, the home directory, or the repository root.",
7691
7717
  "</Rules>"
7692
7718
  ].join("\n");
7693
7719
  function buildThirdPartyPrompt(skills) {
@@ -7880,11 +7906,11 @@ var WorkerAgent = class extends BaseAgent {
7880
7906
  return this.state.get("conversation") ?? [];
7881
7907
  }
7882
7908
  // --- Subscribable ---
7883
- subscribe(path9, callback) {
7884
- return this.state.on(path9, callback);
7909
+ subscribe(path10, callback) {
7910
+ return this.state.on(path10, callback);
7885
7911
  }
7886
- getSnapshot(path9) {
7887
- return this.state.get(path9);
7912
+ getSnapshot(path10) {
7913
+ return this.state.get(path10);
7888
7914
  }
7889
7915
  /**
7890
7916
  * Accept a message from the controller — writes to internal inbox. The
@@ -7992,7 +8018,7 @@ import { resolve as resolve5 } from "path";
7992
8018
 
7993
8019
  // src/infra/permission/policy.ts
7994
8020
  import { realpathSync as realpathSync3 } from "fs";
7995
- import { basename as basename6, dirname as dirname7, isAbsolute, join as join16, resolve as resolve4, sep } from "path";
8021
+ import { basename as basename6, dirname as dirname7, isAbsolute, join as join17, resolve as resolve4, sep } from "path";
7996
8022
  var DEFAULT_SENSITIVE_RELATIVE_PATHS = [
7997
8023
  ".ssh",
7998
8024
  ".gnupg",
@@ -8005,9 +8031,7 @@ var DEFAULT_SENSITIVE_RELATIVE_PATHS = [
8005
8031
  ".git-credentials",
8006
8032
  ".npmrc",
8007
8033
  ".pypirc",
8008
- ".config/gh",
8009
- ".wayang.config.json",
8010
- ".wayang"
8034
+ ".config/gh"
8011
8035
  ];
8012
8036
  var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([
8013
8037
  ".ssh",
@@ -8019,13 +8043,7 @@ var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([
8019
8043
  ".netrc",
8020
8044
  ".git-credentials",
8021
8045
  ".npmrc",
8022
- ".pypirc",
8023
- // The wayang data dir (sessions/conversations) — same intent as the
8024
- // home-anchored `.wayang` entry, but applied wherever it appears.
8025
- ".wayang"
8026
- ]);
8027
- var SENSITIVE_FILENAMES = /* @__PURE__ */ new Set([
8028
- ".wayang.config.json"
8046
+ ".pypirc"
8029
8047
  ]);
8030
8048
  var SENSITIVE_SEGMENT_PAIRS = [
8031
8049
  [".docker", "config.json"]
@@ -8046,7 +8064,7 @@ function resolveDeepestExistingAncestor(lexical) {
8046
8064
  for (let depth = 0; depth <= MAX_ANCESTOR_DEPTH; depth += 1) {
8047
8065
  try {
8048
8066
  const resolved = realpathSync3(candidate);
8049
- return peeled.length === 0 ? resolved : resolve4(join16(resolved, ...peeled));
8067
+ return peeled.length === 0 ? resolved : resolve4(join17(resolved, ...peeled));
8050
8068
  } catch {
8051
8069
  }
8052
8070
  const parent = dirname7(candidate);
@@ -8073,7 +8091,6 @@ function matchSensitiveEntry(absPath) {
8073
8091
  for (let i = 0; i < segments.length; i += 1) {
8074
8092
  const segment = segments[i];
8075
8093
  if (SENSITIVE_SEGMENTS.has(segment)) return { entry: segment, source: "segment" };
8076
- if (SENSITIVE_FILENAMES.has(segment)) return { entry: segment, source: "filename" };
8077
8094
  for (const [dir, child] of SENSITIVE_SEGMENT_PAIRS) {
8078
8095
  if (segment === dir && segments[i + 1] === child) {
8079
8096
  return { entry: `${dir}/${child}`, source: "pair" };
@@ -8651,10 +8668,10 @@ function extractPathCandidates(segments) {
8651
8668
  function commandAccess(candidates) {
8652
8669
  return candidates.some((candidate) => candidate.access === "write") ? "write" : "unknown";
8653
8670
  }
8654
- function decidePath(scope, path9, access) {
8671
+ function decidePath(scope, path10, access) {
8655
8672
  const permission = scope.permission;
8656
8673
  return decideAccess({
8657
- path: path9,
8674
+ path: path10,
8658
8675
  access,
8659
8676
  cwd: scope.workspace,
8660
8677
  homeDir: scope.homeDir,
@@ -8975,7 +8992,7 @@ var BlockingRequestMap = class {
8975
8992
 
8976
8993
  // src/services/agents/claude-code-worker.ts
8977
8994
  import { basename as basename7, dirname as dirname8 } from "path";
8978
- import { homedir } from "os";
8995
+ import { homedir as homedir2 } from "os";
8979
8996
  var INQUIRY_STREAM_CLOSE_TIMEOUT_MS = 36e4;
8980
8997
  var ClaudeCodeWorker = class _ClaudeCodeWorker {
8981
8998
  id;
@@ -9239,11 +9256,11 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
9239
9256
  return this.state.get("conversation") ?? [];
9240
9257
  }
9241
9258
  // --- Subscribable ---
9242
- subscribe(path9, callback) {
9243
- return this.state.on(path9, callback);
9259
+ subscribe(path10, callback) {
9260
+ return this.state.on(path10, callback);
9244
9261
  }
9245
- getSnapshot(path9) {
9246
- return this.state.get(path9);
9262
+ getSnapshot(path10) {
9263
+ return this.state.get(path10);
9247
9264
  }
9248
9265
  /**
9249
9266
  * Accept a message from the controller. The message is queued and handed
@@ -9331,7 +9348,7 @@ var ClaudeCodeWorker = class _ClaudeCodeWorker {
9331
9348
  effectiveAccessScope() {
9332
9349
  if (this._accessScope) return this._accessScope;
9333
9350
  const wayangDir = wayangDirForSessionDir(this.sessionDir);
9334
- const homeDir2 = wayangDir && basename7(wayangDir) === ".wayang" ? dirname8(wayangDir) : homedir();
9351
+ const homeDir2 = wayangDir && basename7(wayangDir) === ".wayang" ? dirname8(wayangDir) : homedir2();
9335
9352
  return { homeDir: homeDir2, workspace: this.workspaceDir };
9336
9353
  }
9337
9354
  /**
@@ -9808,11 +9825,14 @@ controller: ${answer}`,
9808
9825
  };
9809
9826
 
9810
9827
  // src/services/tools/add-task.ts
9828
+ import { mkdirSync as mkdirSync7 } from "fs";
9829
+ import { homedir as homedir3 } from "os";
9830
+ import * as path8 from "path";
9811
9831
  import { z as z3 } from "zod";
9812
9832
 
9813
9833
  // src/services/tools/workspace-rules.ts
9814
9834
  import { readFileSync as readFileSync10, statSync as statSync5 } from "fs";
9815
- import { join as join17 } from "path";
9835
+ import { join as join18 } from "path";
9816
9836
  var RULE_FILE_NAMES2 = ["AGENTS.md", "CLAUDE.md"];
9817
9837
  var WORKSPACE_RULES_MAX_LINES = 40;
9818
9838
  var WORKSPACE_RULES_MAX_CHARS = 2e3;
@@ -9820,7 +9840,7 @@ function buildWorkspaceRulesSummary(input2) {
9820
9840
  const workspacePath = input2.workspacePath;
9821
9841
  if (!workspacePath) return "";
9822
9842
  for (const name of RULE_FILE_NAMES2) {
9823
- const rulesPath = join17(workspacePath, name);
9843
+ const rulesPath = join18(workspacePath, name);
9824
9844
  if (!isRegularFile(rulesPath)) continue;
9825
9845
  const header = `Workspace rules (from ${rulesPath}):`;
9826
9846
  const refusal = describeRefusal(rulesPath, workspacePath, input2);
@@ -9893,17 +9913,50 @@ function readTextFile(p) {
9893
9913
  }
9894
9914
 
9895
9915
  // src/services/tools/add-task.ts
9916
+ var HOME_VAR_PREFIX = /^\$(?:\{HOME\}|HOME)(?=\/|$)/;
9917
+ function expandHomePrefix(raw, homeDir2) {
9918
+ if (raw === "~") return homeDir2;
9919
+ if (raw.startsWith("~/")) return path8.join(homeDir2, raw.slice(2));
9920
+ const match = HOME_VAR_PREFIX.exec(raw);
9921
+ if (match) {
9922
+ const rest = raw.slice(match[0].length);
9923
+ return rest === "" ? homeDir2 : path8.join(homeDir2, rest);
9924
+ }
9925
+ return raw.startsWith("~") ? null : raw;
9926
+ }
9927
+ function ensureWorkspaceDir(dirPath) {
9928
+ try {
9929
+ mkdirSync7(dirPath, { recursive: true });
9930
+ return null;
9931
+ } catch (err) {
9932
+ return `Cannot create workspace ${dirPath}: ${err.message}. The task was NOT created.`;
9933
+ }
9934
+ }
9896
9935
  function resolveTaskWorkspace(requested, deps) {
9897
9936
  const current = deps.workspaceDir;
9898
- const requestedPath = requested !== void 0 && requested.trim() !== "" ? normalizeWorkspacePath(requested) : void 0;
9937
+ let declared;
9938
+ if (requested !== void 0 && requested.trim() !== "") {
9939
+ const expanded = expandHomePrefix(requested.trim(), deps.homeDir ?? homedir3());
9940
+ if (expanded === null) {
9941
+ return {
9942
+ error: `Cannot resolve workspace "${requested}": only "~" and "$HOME" expand to the home directory, so no readable path was left. The task was NOT created.`
9943
+ };
9944
+ }
9945
+ declared = expanded;
9946
+ }
9899
9947
  if (!current) {
9900
- if (requestedPath !== void 0) {
9948
+ if (declared !== void 0) {
9901
9949
  return {
9902
- error: `Cannot resolve workspace ${requestedPath}: the current workspace is unknown in this context, so the task was NOT created.`
9950
+ error: `Cannot resolve workspace ${normalizeWorkspacePath(declared)}: the current workspace is unknown in this context, so the task was NOT created.`
9903
9951
  };
9904
9952
  }
9905
9953
  return {};
9906
9954
  }
9955
+ if (declared !== void 0 && path8.isAbsolute(declared)) {
9956
+ const dirError = ensureWorkspaceDir(declared);
9957
+ if (dirError !== null) return { error: dirError };
9958
+ }
9959
+ const requestedPath = declared !== void 0 ? normalizeWorkspacePath(declared) : void 0;
9907
9960
  const targetPath = requestedPath ?? normalizeWorkspacePath(current);
9908
9961
  const ref = resolveWorkspaceRef(deps.wayangDir ?? null, targetPath);
9909
9962
  return { workspace: { path: ref.path, slug: ref.slug } };
@@ -9959,6 +10012,7 @@ function withWorkspaceRules(resultText, workspacePath, deps) {
9959
10012
  ${summary}`;
9960
10013
  }
9961
10014
  function addTaskTool(deps) {
10015
+ const generalWorkspaceNote = deps.generalWorkspaceDir ? ` Tasks that belong to no codebase (research, collecting material, drafting a document) run in the general workspace ${deps.generalWorkspaceDir} \u2014 it is created on first use.` : "";
9962
10016
  return defineTool({
9963
10017
  description: "Add a new task to the queue. A Worker will execute it automatically.",
9964
10018
  parameters: z3.object({
@@ -9973,7 +10027,7 @@ function addTaskTool(deps) {
9973
10027
  "Dispatch this task to an existing idle worker (the workerId returned from a prior multiStage task). The worker resumes with its accumulated context. Omit for a fresh worker."
9974
10028
  ),
9975
10029
  workspace: z3.string().optional().describe(
9976
- "Target workspace path; defaults to the current workspace. A different path runs this task there \u2014 the worker, its tools and its permission checks use it as their root."
10030
+ `Target workspace path; defaults to the current workspace. A different path runs this task there \u2014 the worker, its tools and its permission checks use it as their root. A leading ~ or $HOME expands to the home directory, and a missing directory is created.${generalWorkspaceNote}`
9977
10031
  ),
9978
10032
  allowedDirs: z3.array(z3.string()).optional().describe(
9979
10033
  "Extra directories this task may access, in addition to its workspace. Only this task is affected \u2014 the grant is NOT inherited by other tasks. Each entry must be inside the user home and off the denied-path list (credentials, tool config); an invalid entry fails the whole call and no task is created. Paths are normalized; relative entries resolve against the task workspace."
@@ -10553,8 +10607,8 @@ function readFileTool(deps = {}) {
10553
10607
  offset: z14.number().int().min(1).optional().describe("Start line number (1-based)"),
10554
10608
  limit: z14.number().int().min(1).optional().describe("Number of lines to read")
10555
10609
  }),
10556
- execute: safeExecute("read_file", async ({ path: path9, offset, limit }) => {
10557
- const resolved = resolve6(workspace, path9);
10610
+ execute: safeExecute("read_file", async ({ path: path10, offset, limit }) => {
10611
+ const resolved = resolve6(workspace, path10);
10558
10612
  const content = readFileSync11(resolved, "utf-8");
10559
10613
  const lines = content.split("\n");
10560
10614
  const totalLines = lines.length;
@@ -10575,7 +10629,7 @@ function readFileTool(deps = {}) {
10575
10629
 
10576
10630
  // src/services/tools/write-file.ts
10577
10631
  import { z as z15 } from "zod";
10578
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync10 } from "fs";
10632
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10 } from "fs";
10579
10633
  import { dirname as dirname9, resolve as resolve7 } from "path";
10580
10634
  function writeFileTool(deps = {}) {
10581
10635
  const workspace = deps.cwd ?? process.cwd();
@@ -10585,12 +10639,12 @@ function writeFileTool(deps = {}) {
10585
10639
  path: z15.string().describe("File path (relative to workspace or absolute)"),
10586
10640
  content: z15.string().describe("File content")
10587
10641
  }),
10588
- execute: safeExecute("write_file", async ({ path: path9, content }) => {
10589
- const resolved = resolve7(workspace, path9);
10642
+ execute: safeExecute("write_file", async ({ path: path10, content }) => {
10643
+ const resolved = resolve7(workspace, path10);
10590
10644
  if (existsSync10(resolved)) {
10591
- return `[ERROR] write_file: File already exists: ${path9}. Use edit_file to modify existing files.`;
10645
+ return `[ERROR] write_file: File already exists: ${path10}. Use edit_file to modify existing files.`;
10592
10646
  }
10593
- mkdirSync6(dirname9(resolved), { recursive: true });
10647
+ mkdirSync8(dirname9(resolved), { recursive: true });
10594
10648
  writeFileSync4(resolved, content, "utf-8");
10595
10649
  return `Written ${content.length} chars to ${resolved}`;
10596
10650
  })
@@ -10610,22 +10664,22 @@ function editFileTool(deps) {
10610
10664
  old_string: z16.string().describe("Exact text to find (must be unique in file)"),
10611
10665
  new_string: z16.string().describe("Replacement text")
10612
10666
  }),
10613
- execute: safeExecute("edit_file", async ({ path: path9, old_string, new_string }) => {
10614
- const resolved = resolve8(workspace, path9);
10667
+ execute: safeExecute("edit_file", async ({ path: path10, old_string, new_string }) => {
10668
+ const resolved = resolve8(workspace, path10);
10615
10669
  let content;
10616
10670
  try {
10617
10671
  content = readFileSync12(resolved, "utf-8");
10618
10672
  } catch {
10619
- return `[ERROR] edit_file: File not found: ${path9}`;
10673
+ return `[ERROR] edit_file: File not found: ${path10}`;
10620
10674
  }
10621
10675
  const count = content.split(old_string).length - 1;
10622
10676
  if (count === 0)
10623
- return `[ERROR] edit_file: old_string not found in ${path9}, read the file content and provide a correct old_string.`;
10677
+ return `[ERROR] edit_file: old_string not found in ${path10}, read the file content and provide a correct old_string.`;
10624
10678
  if (count > 1)
10625
- return `[ERROR] edit_file: old_string found ${count} times in ${path9}. Provide more surrounding context to make it unique.`;
10679
+ return `[ERROR] edit_file: old_string found ${count} times in ${path10}. Provide more surrounding context to make it unique.`;
10626
10680
  const newContent = content.replace(old_string, new_string);
10627
10681
  writeFileSync5(resolved, newContent, "utf-8");
10628
- return `Replaced ${old_string.length} chars with ${new_string.length} chars in ${path9}`;
10682
+ return `Replaced ${old_string.length} chars with ${new_string.length} chars in ${path10}`;
10629
10683
  })
10630
10684
  });
10631
10685
  }
@@ -10642,8 +10696,8 @@ function searchFilesTool(deps) {
10642
10696
  pattern: z17.string().describe('Glob pattern (e.g. "**/*.ts", "src/**/*.test.ts")'),
10643
10697
  path: z17.string().optional().describe("Base directory for search (default: workspace root)")
10644
10698
  }),
10645
- execute: safeExecute("search_files", async ({ pattern, path: path9 }) => {
10646
- const basePath = path9 ? normalizePath(path9, { base: workspace }) : normalizePath(workspace);
10699
+ execute: safeExecute("search_files", async ({ pattern, path: path10 }) => {
10700
+ const basePath = path10 ? normalizePath(path10, { base: workspace }) : normalizePath(workspace);
10647
10701
  if (homeDir2) {
10648
10702
  const { decision, matchedEntry } = decideAccess({
10649
10703
  access: "read",
@@ -10651,7 +10705,7 @@ function searchFilesTool(deps) {
10651
10705
  cwd: workspace,
10652
10706
  path: basePath
10653
10707
  });
10654
- const requested = path9 ?? workspace;
10708
+ const requested = path10 ?? workspace;
10655
10709
  if (matchedEntry !== void 0 || decision === "deny") {
10656
10710
  return `[ERROR] search_files: refusing to search "${requested}" \u2014 the path is on the sensitive list (a protected location).`;
10657
10711
  }
@@ -10686,8 +10740,8 @@ function searchContentTool(deps) {
10686
10740
  path: z18.string().optional().describe("Directory to search in (default: workspace root)"),
10687
10741
  include: z18.string().optional().describe('File pattern to include (e.g. "*.ts", "*.py")')
10688
10742
  }),
10689
- execute: safeExecute("search_content", async ({ query: query2, path: path9, include }) => {
10690
- const searchDir = path9 ? resolve9(workspace, path9) : workspace;
10743
+ execute: safeExecute("search_content", async ({ query: query2, path: path10, include }) => {
10744
+ const searchDir = path10 ? resolve9(workspace, path10) : workspace;
10691
10745
  const cmd = `grep -rn${include ? ` --include=${shellQuote(include)}` : ""} -- ${shellQuote(query2)} ${shellQuote(searchDir)}`;
10692
10746
  try {
10693
10747
  const output = execSync3(cmd, {
@@ -10950,6 +11004,7 @@ function createControllerTools(deps) {
10950
11004
  onTaskDispatched: deps.onTaskDispatched,
10951
11005
  workspaceDir: deps.workspaceDir,
10952
11006
  wayangDir: deps.wayangDir,
11007
+ generalWorkspaceDir: deps.generalWorkspaceDir,
10953
11008
  homeDir: deps.homeDir,
10954
11009
  permissionConfig: deps.permissionConfig
10955
11010
  }),
@@ -11208,7 +11263,7 @@ function formatApiCallError(err) {
11208
11263
 
11209
11264
  // src/services/task-execute-engine.ts
11210
11265
  import { basename as basename8, dirname as dirname10, resolve as resolve10 } from "path";
11211
- import { homedir as homedir2 } from "os";
11266
+ import { homedir as homedir4 } from "os";
11212
11267
  var RuntimeState = class extends BaseWayangState {
11213
11268
  constructor(initialData) {
11214
11269
  super(initialData, []);
@@ -11219,7 +11274,7 @@ var RuntimeState = class extends BaseWayangState {
11219
11274
  var PUPPET_WORKER_TYPE = "puppet";
11220
11275
  function resolveUserHomeDir(sessionDir) {
11221
11276
  const wayangDir = wayangDirForSessionDir(sessionDir);
11222
- return wayangDir && basename8(wayangDir) === ".wayang" ? dirname10(wayangDir) : homedir2();
11277
+ return wayangDir && basename8(wayangDir) === ".wayang" ? dirname10(wayangDir) : homedir4();
11223
11278
  }
11224
11279
  var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
11225
11280
  var INQUIRY_TIMEOUT_MS = 3e5;
@@ -11288,15 +11343,15 @@ var TaskExecuteEngine = class {
11288
11343
  // ---------------------------------------------------------------------------
11289
11344
  // Subscribable
11290
11345
  // ---------------------------------------------------------------------------
11291
- subscribe(path9, callback) {
11292
- if (path9.startsWith("tasks.")) return this.taskState.on(path9, callback);
11293
- if (path9 === "activeWorkers" || path9.startsWith("activeWorkers.")) return this.workerState.on(path9, callback);
11294
- throw new Error(`TaskExecuteEngine: unknown subscription path "${path9}"`);
11346
+ subscribe(path10, callback) {
11347
+ if (path10.startsWith("tasks.")) return this.taskState.on(path10, callback);
11348
+ if (path10 === "activeWorkers" || path10.startsWith("activeWorkers.")) return this.workerState.on(path10, callback);
11349
+ throw new Error(`TaskExecuteEngine: unknown subscription path "${path10}"`);
11295
11350
  }
11296
- getSnapshot(path9) {
11297
- if (path9.startsWith("tasks.")) return this.taskState.get(path9);
11298
- if (path9 === "activeWorkers" || path9.startsWith("activeWorkers.")) return this.workerState.get(path9);
11299
- throw new Error(`TaskExecuteEngine: unknown snapshot path "${path9}"`);
11351
+ getSnapshot(path10) {
11352
+ if (path10.startsWith("tasks.")) return this.taskState.get(path10);
11353
+ if (path10 === "activeWorkers" || path10.startsWith("activeWorkers.")) return this.workerState.get(path10);
11354
+ throw new Error(`TaskExecuteEngine: unknown snapshot path "${path10}"`);
11300
11355
  }
11301
11356
  // ---------------------------------------------------------------------------
11302
11357
  // Task management
@@ -12444,7 +12499,7 @@ var ControllerLoop = class {
12444
12499
  };
12445
12500
 
12446
12501
  // src/services/agents/controller-state.ts
12447
- import { join as join18 } from "path";
12502
+ import { join as join20 } from "path";
12448
12503
 
12449
12504
  // src/services/conversation/conversation-index.ts
12450
12505
  function emptyUsage() {
@@ -12668,8 +12723,8 @@ var ControllerAgentState = class extends BaseWayangState {
12668
12723
  /** In-memory resolve function for the current inquiry — never persisted. */
12669
12724
  inquiryResolver = null;
12670
12725
  constructor(ctx) {
12671
- const jsonFile = new JSONFileHelper(join18(ctx.sessionDir, "runtime-state.json"));
12672
- const jsonlFile = new JSONLFileHelper(join18(ctx.sessionDir, "conversation.jsonl"));
12726
+ const jsonFile = new JSONFileHelper(join20(ctx.sessionDir, "runtime-state.json"));
12727
+ const jsonlFile = new JSONLFileHelper(join20(ctx.sessionDir, "conversation.jsonl"));
12673
12728
  const initialData = {
12674
12729
  runtimeState: {
12675
12730
  session: { id: "", startedAt: 0 },
@@ -12694,9 +12749,9 @@ var ControllerAgentState = class extends BaseWayangState {
12694
12749
  * shared conversation-envelope module — the single source of truth. This
12695
12750
  * override only feeds the in-memory turn index incrementally.
12696
12751
  */
12697
- append(path9, entry) {
12698
- super.append(path9, entry);
12699
- if (path9 === "conversation" && this.convIndex) {
12752
+ append(path10, entry) {
12753
+ super.append(path10, entry);
12754
+ if (path10 === "conversation" && this.convIndex) {
12700
12755
  this.convIndex.onAppend(entry);
12701
12756
  }
12702
12757
  }
@@ -12855,7 +12910,7 @@ function buildSummaryInput(entries, maxChars) {
12855
12910
 
12856
12911
  // src/services/agents/controller-agent.ts
12857
12912
  import { basename as basename9, dirname as dirname11 } from "path";
12858
- import { homedir as homedir3 } from "os";
12913
+ import { homedir as homedir5 } from "os";
12859
12914
  var SUMMARIZER_INPUT_RATIO = 0.5;
12860
12915
  var ControllerAgent = class _ControllerAgent extends BaseAgent {
12861
12916
  state;
@@ -12921,7 +12976,13 @@ var ControllerAgent = class _ControllerAgent extends BaseAgent {
12921
12976
  const state = new ControllerAgentState(ctx);
12922
12977
  const recorder = new ToolCallRecorder(ctx.logger);
12923
12978
  const wayangDir = wayangDirForSessionDir(ctx.sessionDir);
12924
- const homeDir2 = wayangDir && basename9(wayangDir) === ".wayang" ? dirname11(wayangDir) : homedir3();
12979
+ const homeDir2 = wayangDir && basename9(wayangDir) === ".wayang" ? dirname11(wayangDir) : homedir5();
12980
+ const generalWorkspaceDir = resolveGeneralWorkspace(ctx);
12981
+ try {
12982
+ ensureGeneralWorkspace(ctx);
12983
+ } catch {
12984
+ ctx.logger.warn(`could not create the general workspace at ${generalWorkspaceDir}`);
12985
+ }
12925
12986
  const controllerRules = loadControllerRules({
12926
12987
  homeDir: homeDir2,
12927
12988
  cwd: ctx.workspaceDir || process.cwd()
@@ -12979,7 +13040,8 @@ var ControllerAgent = class _ControllerAgent extends BaseAgent {
12979
13040
  // Default workspace for tasks created by add_task; a declared workspace
12980
13041
  // is stamped instead and the engine executes the task there.
12981
13042
  workspaceDir: ctx.workspaceDir,
12982
- wayangDir
13043
+ wayangDir,
13044
+ generalWorkspaceDir
12983
13045
  });
12984
13046
  return new _ControllerAgent(ctx, state, provider, tools, skills, recorder, controllerRules);
12985
13047
  }
@@ -13246,11 +13308,11 @@ var ControllerAgent = class _ControllerAgent extends BaseAgent {
13246
13308
  return this.state.getTurn(turnId);
13247
13309
  }
13248
13310
  // --- Subscribable ---
13249
- subscribe(path9, callback) {
13250
- return this.state.on(path9, callback);
13311
+ subscribe(path10, callback) {
13312
+ return this.state.on(path10, callback);
13251
13313
  }
13252
- getSnapshot(path9) {
13253
- return this.state.get(path9);
13314
+ getSnapshot(path10) {
13315
+ return this.state.get(path10);
13254
13316
  }
13255
13317
  /** Check if context needs compaction. */
13256
13318
  needsCompaction() {
@@ -13579,7 +13641,7 @@ function signalToEntry(sig, ts, sessionId) {
13579
13641
 
13580
13642
  // src/services/skills/registry.ts
13581
13643
  import { readFileSync as readFileSync13, readdirSync as readdirSync2, statSync as statSync6 } from "fs";
13582
- import { join as join19 } from "path";
13644
+ import { join as join21 } from "path";
13583
13645
  import { parse as parseYaml } from "yaml";
13584
13646
  var SKILL_FILE = "SKILL.md";
13585
13647
  function parseFrontmatter(raw) {
@@ -13614,7 +13676,7 @@ function listResources(skillDir) {
13614
13676
  const walk = (relDir) => {
13615
13677
  let entries;
13616
13678
  try {
13617
- entries = readdirSync2(join19(skillDir, relDir), { withFileTypes: true });
13679
+ entries = readdirSync2(join21(skillDir, relDir), { withFileTypes: true });
13618
13680
  } catch {
13619
13681
  return;
13620
13682
  }
@@ -13661,7 +13723,7 @@ var SkillRegistry = class {
13661
13723
  if (existing) return existing;
13662
13724
  const desc = this.descriptors.get(name);
13663
13725
  if (!desc) return void 0;
13664
- const filePath = join19(desc.dir, SKILL_FILE);
13726
+ const filePath = join21(desc.dir, SKILL_FILE);
13665
13727
  let raw;
13666
13728
  try {
13667
13729
  raw = readFileSync13(filePath, "utf-8");
@@ -13692,8 +13754,8 @@ var SkillRegistry = class {
13692
13754
  for (const entry of entries) {
13693
13755
  if (!entry.isDirectory()) continue;
13694
13756
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
13695
- const skillDir = join19(dir, entry.name);
13696
- const skillFile = join19(skillDir, SKILL_FILE);
13757
+ const skillDir = join21(dir, entry.name);
13758
+ const skillFile = join21(skillDir, SKILL_FILE);
13697
13759
  if (!existsAsFile(skillFile)) continue;
13698
13760
  const { frontmatter } = safeReadFrontmatter(skillFile);
13699
13761
  const description = asString(frontmatter.description);
@@ -13706,16 +13768,16 @@ var SkillRegistry = class {
13706
13768
  }
13707
13769
  }
13708
13770
  };
13709
- function existsAsFile(path9) {
13771
+ function existsAsFile(path10) {
13710
13772
  try {
13711
- return statSync6(path9).isFile();
13773
+ return statSync6(path10).isFile();
13712
13774
  } catch {
13713
13775
  return false;
13714
13776
  }
13715
13777
  }
13716
- function safeReadFrontmatter(path9) {
13778
+ function safeReadFrontmatter(path10) {
13717
13779
  try {
13718
- return parseFrontmatter(readFileSync13(path9, "utf-8"));
13780
+ return parseFrontmatter(readFileSync13(path10, "utf-8"));
13719
13781
  } catch {
13720
13782
  return { frontmatter: {}, body: "" };
13721
13783
  }
@@ -13723,12 +13785,12 @@ function safeReadFrontmatter(path9) {
13723
13785
 
13724
13786
  // src/services/skills/skill-dir.ts
13725
13787
  import { existsSync as existsSync11 } from "fs";
13726
- import { homedir as homedir4 } from "os";
13727
- import { join as join20, resolve as resolve11 } from "path";
13788
+ import { homedir as homedir6 } from "os";
13789
+ import { join as join22, resolve as resolve11 } from "path";
13728
13790
  function resolveSkillDirs(workspaceDir2, configDirs) {
13729
13791
  const candidates = [
13730
- join20(homedir4(), ".wayang", "skills"),
13731
- join20(workspaceDir2, ".wayang", "skills"),
13792
+ join22(homedir6(), ".wayang", "skills"),
13793
+ join22(workspaceDir2, ".wayang", "skills"),
13732
13794
  ...(configDirs ?? []).map((d) => resolve11(d))
13733
13795
  ];
13734
13796
  const seen = /* @__PURE__ */ new Set();
@@ -14055,11 +14117,11 @@ async function runWayangServer(options, io = process) {
14055
14117
  let nextSubscriptionId = 1;
14056
14118
  rpc.register(METHOD.SUBSCRIBE, (params) => {
14057
14119
  const target = resolveTarget(params.target);
14058
- const path9 = requiredString(params, "path");
14059
- const data = target.getSnapshot(path9);
14120
+ const path10 = requiredString(params, "path");
14121
+ const data = target.getSnapshot(path10);
14060
14122
  const subscriptionId = `sub-${nextSubscriptionId++}`;
14061
- subscriptions.set(subscriptionId, target.subscribe(path9, (event) => {
14062
- rpc.notify(NOTIFY.STATE_EVENT, { subscriptionId, target: params.target, path: path9, data: event.data });
14123
+ subscriptions.set(subscriptionId, target.subscribe(path10, (event) => {
14124
+ rpc.notify(NOTIFY.STATE_EVENT, { subscriptionId, target: params.target, path: path10, data: event.data });
14063
14125
  }));
14064
14126
  return { subscriptionId, data: data === void 0 ? null : data };
14065
14127
  });
@@ -14240,9 +14302,9 @@ if (result.status === "error") {
14240
14302
  process.exit(1);
14241
14303
  }
14242
14304
  var { input, flags } = result;
14243
- var homeDir = path8.resolve(flags.homeDir ?? (process.env.HOME || "~"));
14305
+ var homeDir = path9.resolve(flags.homeDir ?? (process.env.HOME || "~"));
14244
14306
  var workspaceDir = normalizeWorkspacePath(flags.workspaceDir ?? process.cwd());
14245
- var configPath = flags.config ? path8.resolve(flags.config) : path8.join(homeDir, ".wayang.config.json");
14307
+ var configPath = flags.config ? path9.resolve(flags.config) : path9.join(homeDir, ".wayang.config.json");
14246
14308
  function fatal(err) {
14247
14309
  if (err instanceof SessionLockedError) {
14248
14310
  console.error(formatSessionLockHint(err));
@@ -14254,7 +14316,7 @@ function fatal(err) {
14254
14316
  if (input[0] === "server") {
14255
14317
  serverMain({
14256
14318
  configPath,
14257
- homeDir: path8.join(homeDir, ".wayang"),
14319
+ homeDir: path9.join(homeDir, ".wayang"),
14258
14320
  workspaceDir,
14259
14321
  session: flags.session ?? "new",
14260
14322
  injectPreviousTasks: flags.injectPreviousTasks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wayang-ai",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Multi-agent orchestration platform",
5
5
  "type": "module",
6
6
  "license": "MIT",