@charzhu/openjaw-agent 0.3.0 → 0.3.1

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/main.js CHANGED
@@ -137,7 +137,11 @@ function loadAgentConfig() {
137
137
  feishu: parsed?.feishu ?? void 0,
138
138
  wechat: parsed?.wechat ?? void 0,
139
139
  features: {
140
- skill_auto_suggest: parsed?.features?.skill_auto_suggest ?? true
140
+ skill_auto_suggest: parsed?.features?.skill_auto_suggest ?? true,
141
+ dynamic_workflows: {
142
+ ...DEFAULT_DYNAMIC_WORKFLOWS,
143
+ ...parsed?.features?.dynamic_workflows ?? {}
144
+ }
141
145
  }
142
146
  };
143
147
  if (config.llm.provider === "anthropic") {
@@ -199,12 +203,20 @@ function updateBridgeConfig(name, values) {
199
203
  );
200
204
  return next;
201
205
  }
202
- var DEFAULT_COPILOT_OAUTH_CLIENT_ID, DEFAULT_CONFIG, configWriteChain;
206
+ var DEFAULT_COPILOT_OAUTH_CLIENT_ID, DEFAULT_DYNAMIC_WORKFLOWS, DEFAULT_CONFIG, configWriteChain;
203
207
  var init_config = __esm({
204
208
  "src/config.ts"() {
205
209
  "use strict";
206
210
  init_packageRoot();
207
211
  DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98";
212
+ DEFAULT_DYNAMIC_WORKFLOWS = {
213
+ enabled: true,
214
+ planner_mode: "adaptive",
215
+ hard_max_workers: 1024,
216
+ hard_max_concurrent_workers: 128,
217
+ worker_timeout_ms: 18e4,
218
+ persist_history: true
219
+ };
208
220
  DEFAULT_CONFIG = {
209
221
  llm: {
210
222
  provider: "anthropic",
@@ -856,7 +868,7 @@ Start-Sleep -Milliseconds 50
856
868
  }
857
869
  }
858
870
  async function wait(duration) {
859
- await new Promise((resolve5) => setTimeout(resolve5, duration * 1e3));
871
+ await new Promise((resolve6) => setTimeout(resolve6, duration * 1e3));
860
872
  return { output: `Waited ${duration} seconds` };
861
873
  }
862
874
  function getDisplayDimensions() {
@@ -2551,7 +2563,7 @@ var init_copilot = __esm({
2551
2563
  handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
2552
2564
  });
2553
2565
  const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
2554
- return new Promise((resolve5, reject) => {
2566
+ return new Promise((resolve6, reject) => {
2555
2567
  const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
2556
2568
  let settled = false;
2557
2569
  const timeout = setTimeout(() => {
@@ -2565,7 +2577,7 @@ var init_copilot = __esm({
2565
2577
  settled = true;
2566
2578
  clearTimeout(timeout);
2567
2579
  ws.close();
2568
- resolve5(value);
2580
+ resolve6(value);
2569
2581
  }, "finish");
2570
2582
  const fail = /* @__PURE__ */ __name((error) => {
2571
2583
  if (settled) return;
@@ -5472,18 +5484,18 @@ ${summary}
5472
5484
  content: parsed.question,
5473
5485
  choices: parsed.choices ?? void 0
5474
5486
  };
5475
- const userResponse = await new Promise((resolve5) => {
5487
+ const userResponse = await new Promise((resolve6) => {
5476
5488
  if (this._pendingAskUserResponse !== null) {
5477
5489
  const buffered = this._pendingAskUserResponse;
5478
5490
  this._pendingAskUserResponse = null;
5479
- resolve5(buffered);
5491
+ resolve6(buffered);
5480
5492
  return;
5481
5493
  }
5482
- this._askUserResolver = resolve5;
5494
+ this._askUserResolver = resolve6;
5483
5495
  setTimeout(() => {
5484
- if (this._askUserResolver === resolve5) {
5496
+ if (this._askUserResolver === resolve6) {
5485
5497
  this._askUserResolver = null;
5486
- resolve5("[No response from user \u2014 timed out after 5 minutes]");
5498
+ resolve6("[No response from user \u2014 timed out after 5 minutes]");
5487
5499
  }
5488
5500
  }, 5 * 60 * 1e3);
5489
5501
  });
@@ -6146,7 +6158,7 @@ var init_browser = __esm({
6146
6158
  await Page.domContentEventFired();
6147
6159
  } else if (options.waitFor === "networkidle") {
6148
6160
  await Page.loadEventFired();
6149
- await new Promise((resolve5) => setTimeout(resolve5, 1e3));
6161
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
6150
6162
  }
6151
6163
  const result = await Runtime.evaluate({
6152
6164
  expression: "document.title"
@@ -6917,7 +6929,7 @@ var init_browser = __esm({
6917
6929
  if (exists) {
6918
6930
  return true;
6919
6931
  }
6920
- await new Promise((resolve5) => setTimeout(resolve5, 200));
6932
+ await new Promise((resolve6) => setTimeout(resolve6, 200));
6921
6933
  }
6922
6934
  return false;
6923
6935
  }
@@ -7145,10 +7157,10 @@ function createBrowseTools(config, sharedBrowser) {
7145
7157
  }
7146
7158
  },
7147
7159
  execute: /* @__PURE__ */ __name(async (input) => {
7148
- const { join: join47 } = await import("node:path");
7160
+ const { join: join48 } = await import("node:path");
7149
7161
  const { tmpdir: tmpdir13 } = await import("node:os");
7150
- const { randomUUID: randomUUID14 } = await import("node:crypto");
7151
- const screenshotPath = join47(tmpdir13(), `openjaw-browser-${randomUUID14().slice(0, 8)}.png`);
7162
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
7163
+ const screenshotPath = join48(tmpdir13(), `openjaw-browser-${randomUUID15().slice(0, 8)}.png`);
7152
7164
  const screenshot = await browser.screenshot({ fullPage: false, path: screenshotPath });
7153
7165
  const snapshot = await browser.snapshot({ full: false });
7154
7166
  return {
@@ -7843,7 +7855,7 @@ var init_outlook_desktop = __esm({
7843
7855
  }
7844
7856
  }
7845
7857
  sleep(ms) {
7846
- return new Promise((resolve5) => setTimeout(resolve5, ms));
7858
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
7847
7859
  }
7848
7860
  };
7849
7861
  }
@@ -8441,7 +8453,7 @@ var init_outlook_web = __esm({
8441
8453
  await this.browser.typeChars(text);
8442
8454
  }
8443
8455
  sleep(ms) {
8444
- return new Promise((resolve5) => setTimeout(resolve5, ms));
8456
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
8445
8457
  }
8446
8458
  };
8447
8459
  }
@@ -8675,8 +8687,8 @@ var init_token_pool = __esm({
8675
8687
  if (!existsSync8(legacyDir))
8676
8688
  return;
8677
8689
  try {
8678
- const { readdirSync: readdirSync7 } = __require("node:fs");
8679
- const files = readdirSync7(legacyDir);
8690
+ const { readdirSync: readdirSync8 } = __require("node:fs");
8691
+ const files = readdirSync8(legacyDir);
8680
8692
  for (const file2 of files) {
8681
8693
  if (!file2.endsWith(".json"))
8682
8694
  continue;
@@ -8856,7 +8868,7 @@ var init_cdp_token_extractor = __esm({
8856
8868
  }
8857
8869
  logger_default.info("CDP: reloading tab for token refresh", { url: targetTab.url, audience });
8858
8870
  await this.reloadTab(targetTab);
8859
- await new Promise((resolve5) => setTimeout(resolve5, PAGE_RELOAD_WAIT_MS));
8871
+ await new Promise((resolve6) => setTimeout(resolve6, PAGE_RELOAD_WAIT_MS));
8860
8872
  if (!targetTab.webSocketDebuggerUrl)
8861
8873
  return [];
8862
8874
  const freshPages = await this.listPages();
@@ -8973,19 +8985,19 @@ var init_cdp_token_extractor = __esm({
8973
8985
  * Evaluate a JS expression in a tab and return the string result.
8974
8986
  */
8975
8987
  async evaluateInTab(wsUrl, expression) {
8976
- return new Promise((resolve5) => {
8988
+ return new Promise((resolve6) => {
8977
8989
  let ws;
8978
8990
  try {
8979
8991
  ws = new WebSocket(wsUrl);
8980
8992
  } catch (err) {
8981
8993
  logger_default.warn("CDP: WebSocket constructor failed", { wsUrl: wsUrl.substring(0, 60), error: String(err) });
8982
- resolve5(null);
8994
+ resolve6(null);
8983
8995
  return;
8984
8996
  }
8985
8997
  const timer = setTimeout(() => {
8986
8998
  logger_default.warn("CDP: evaluateInTab timeout", { wsUrl: wsUrl.substring(0, 60) });
8987
8999
  ws.close();
8988
- resolve5(null);
9000
+ resolve6(null);
8989
9001
  }, CDP_TIMEOUT_MS);
8990
9002
  ws.on("open", () => {
8991
9003
  ws.send(JSON.stringify({
@@ -8999,18 +9011,18 @@ var init_cdp_token_extractor = __esm({
8999
9011
  if (resp.id === 1) {
9000
9012
  clearTimeout(timer);
9001
9013
  ws.close();
9002
- resolve5(resp.result?.result?.value ?? null);
9014
+ resolve6(resp.result?.result?.value ?? null);
9003
9015
  }
9004
9016
  });
9005
9017
  ws.on("error", (err) => {
9006
9018
  logger_default.warn("CDP: evaluateInTab WS error", { error: String(err), wsUrl: wsUrl.substring(0, 60) });
9007
9019
  clearTimeout(timer);
9008
- resolve5(null);
9020
+ resolve6(null);
9009
9021
  });
9010
9022
  });
9011
9023
  }
9012
9024
  async extractFromTab(wsUrl) {
9013
- return new Promise((resolve5, reject) => {
9025
+ return new Promise((resolve6, reject) => {
9014
9026
  const ws = new WebSocket(wsUrl);
9015
9027
  const timer = setTimeout(() => {
9016
9028
  ws.close();
@@ -9030,14 +9042,14 @@ var init_cdp_token_extractor = __esm({
9030
9042
  ws.close();
9031
9043
  const value = resp.result?.result?.value;
9032
9044
  if (!value) {
9033
- resolve5([]);
9045
+ resolve6([]);
9034
9046
  return;
9035
9047
  }
9036
9048
  try {
9037
9049
  const tokens = JSON.parse(value);
9038
- resolve5(tokens);
9050
+ resolve6(tokens);
9039
9051
  } catch {
9040
- resolve5([]);
9052
+ resolve6([]);
9041
9053
  }
9042
9054
  }
9043
9055
  });
@@ -9053,11 +9065,11 @@ var init_cdp_token_extractor = __esm({
9053
9065
  async reloadTab(page) {
9054
9066
  if (!page.webSocketDebuggerUrl)
9055
9067
  return;
9056
- return new Promise((resolve5, reject) => {
9068
+ return new Promise((resolve6, reject) => {
9057
9069
  const ws = new WebSocket(page.webSocketDebuggerUrl);
9058
9070
  const timer = setTimeout(() => {
9059
9071
  ws.close();
9060
- resolve5();
9072
+ resolve6();
9061
9073
  }, 1e4);
9062
9074
  ws.on("open", () => {
9063
9075
  ws.send(JSON.stringify({
@@ -9071,7 +9083,7 @@ var init_cdp_token_extractor = __esm({
9071
9083
  if (resp.id === 1) {
9072
9084
  clearTimeout(timer);
9073
9085
  ws.close();
9074
- resolve5();
9086
+ resolve6();
9075
9087
  }
9076
9088
  });
9077
9089
  ws.on("error", (err) => {
@@ -10732,13 +10744,13 @@ function createMemoryTools(config) {
10732
10744
  const todos = input.todos;
10733
10745
  try {
10734
10746
  const { appendFile: appendFile2, mkdir: mkdir5 } = await import("node:fs/promises");
10735
- const { existsSync: existsSync33 } = await import("node:fs");
10736
- const { join: join47 } = await import("node:path");
10737
- const { homedir: homedir31 } = await import("node:os");
10738
- const memoryDir = join47(homedir31(), ".openjaw", "memory");
10739
- if (!existsSync33(memoryDir))
10747
+ const { existsSync: existsSync34 } = await import("node:fs");
10748
+ const { join: join48 } = await import("node:path");
10749
+ const { homedir: homedir32 } = await import("node:os");
10750
+ const memoryDir = join48(homedir32(), ".openjaw", "memory");
10751
+ if (!existsSync34(memoryDir))
10740
10752
  await mkdir5(memoryDir, { recursive: true });
10741
- const todoPath = join47(memoryDir, "TODOS.md");
10753
+ const todoPath = join48(memoryDir, "TODOS.md");
10742
10754
  const { writeFile: writeFile5 } = await import("node:fs/promises");
10743
10755
  await writeFile5(todoPath, `# Session Todos
10744
10756
 
@@ -12059,7 +12071,7 @@ var init_teams_desktop = __esm({
12059
12071
  }
12060
12072
  }
12061
12073
  sleep(ms) {
12062
- return new Promise((resolve5) => setTimeout(resolve5, ms));
12074
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
12063
12075
  }
12064
12076
  /**
12065
12077
  * Get the current Teams window state
@@ -12926,7 +12938,7 @@ var init_teams_web = __esm({
12926
12938
  }
12927
12939
  }
12928
12940
  sleep(ms) {
12929
- return new Promise((resolve5) => setTimeout(resolve5, ms));
12941
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
12930
12942
  }
12931
12943
  };
12932
12944
  }
@@ -14324,7 +14336,7 @@ var init_teams_chat_monitor = __esm({
14324
14336
  return this.sentMessages.has(normalized);
14325
14337
  }
14326
14338
  sleep(ms) {
14327
- return new Promise((resolve5) => setTimeout(resolve5, ms));
14339
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
14328
14340
  }
14329
14341
  /**
14330
14342
  * Attempt to reconnect the browser after detecting a disconnection.
@@ -15111,7 +15123,7 @@ ${lines.join("\n")}`;
15111
15123
  globalThis.__teamsSeenMessages.set(chatName, seenIds);
15112
15124
  }
15113
15125
  if (syncMode) {
15114
- return new Promise((resolve5) => {
15126
+ return new Promise((resolve6) => {
15115
15127
  const timer2 = setInterval(async () => {
15116
15128
  try {
15117
15129
  const current = await channel.readCurrentChatMessages();
@@ -15120,7 +15132,7 @@ ${lines.join("\n")}`;
15120
15132
  if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
15121
15133
  clearInterval(timer2);
15122
15134
  seenIds.add(msgId);
15123
- resolve5({
15135
+ resolve6({
15124
15136
  success: true,
15125
15137
  channel: channelType,
15126
15138
  sync: true,
@@ -15197,7 +15209,7 @@ ${lines.join("\n")}`;
15197
15209
  globalThis.__teamsSeenMessages.set(chatName, seenIds);
15198
15210
  }
15199
15211
  if (syncMode) {
15200
- return new Promise((resolve5) => {
15212
+ return new Promise((resolve6) => {
15201
15213
  const timer2 = setInterval(async () => {
15202
15214
  try {
15203
15215
  const current = await channel.readCurrentChatMessages();
@@ -15206,7 +15218,7 @@ ${lines.join("\n")}`;
15206
15218
  if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
15207
15219
  clearInterval(timer2);
15208
15220
  seenIds.add(msgId);
15209
- resolve5({
15221
+ resolve6({
15210
15222
  success: true,
15211
15223
  channel: channelType,
15212
15224
  sync: true,
@@ -15564,7 +15576,7 @@ ${lines.join("\n")}`;
15564
15576
  return allTools;
15565
15577
  }
15566
15578
  function sleep2(ms) {
15567
- return new Promise((resolve5) => setTimeout(resolve5, ms));
15579
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
15568
15580
  }
15569
15581
  var MAX_STORED_ENTRIES, ENTRY_TTL_MS;
15570
15582
  var init_chat = __esm({
@@ -16388,11 +16400,11 @@ function createShellTools(_config, hooks) {
16388
16400
  const shell = input.shell ?? true;
16389
16401
  if (input.background) {
16390
16402
  const { tmpdir: tmpdir13 } = await import("node:os");
16391
- const { join: join47 } = await import("node:path");
16392
- const { randomUUID: randomUUID14 } = await import("node:crypto");
16403
+ const { join: join48 } = await import("node:path");
16404
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
16393
16405
  const { createWriteStream: createWriteStream2 } = await import("node:fs");
16394
- const taskId = randomUUID14().slice(0, 8);
16395
- const outputPath = join47(tmpdir13(), `oj-bg-${taskId}.log`);
16406
+ const taskId = randomUUID15().slice(0, 8);
16407
+ const outputPath = join48(tmpdir13(), `oj-bg-${taskId}.log`);
16396
16408
  const detached = spawn(command, [], {
16397
16409
  shell,
16398
16410
  cwd,
@@ -16412,7 +16424,7 @@ function createShellTools(_config, hooks) {
16412
16424
  message: `Command started in background (PID: ${detached.pid}). Output: ${outputPath}`
16413
16425
  };
16414
16426
  }
16415
- return new Promise((resolve5) => {
16427
+ return new Promise((resolve6) => {
16416
16428
  const proc = spawn(command, [], {
16417
16429
  shell,
16418
16430
  cwd,
@@ -16430,7 +16442,7 @@ function createShellTools(_config, hooks) {
16430
16442
  proc.on("close", (code) => {
16431
16443
  const stdoutResult = truncateOutput(stdout.trim());
16432
16444
  const stderrResult = truncateOutput(stderr.trim());
16433
- resolve5({
16445
+ resolve6({
16434
16446
  command,
16435
16447
  exitCode: code,
16436
16448
  stdout: stdoutResult.text,
@@ -16441,7 +16453,7 @@ function createShellTools(_config, hooks) {
16441
16453
  });
16442
16454
  });
16443
16455
  proc.on("error", (error) => {
16444
- resolve5({
16456
+ resolve6({
16445
16457
  command,
16446
16458
  exitCode: -1,
16447
16459
  stdout: "",
@@ -16475,10 +16487,10 @@ function createShellTools(_config, hooks) {
16475
16487
  },
16476
16488
  requiresConfirmation: false,
16477
16489
  execute: /* @__PURE__ */ __name(async (input) => {
16478
- const { writeFileSync: writeFileSync22, unlinkSync: unlinkSync9 } = await import("node:fs");
16479
- const { join: join47 } = await import("node:path");
16490
+ const { writeFileSync: writeFileSync23, unlinkSync: unlinkSync9 } = await import("node:fs");
16491
+ const { join: join48 } = await import("node:path");
16480
16492
  const { tmpdir: tmpdir13 } = await import("node:os");
16481
- const { randomUUID: randomUUID14 } = await import("node:crypto");
16493
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
16482
16494
  const { execFile: execFile3 } = await import("node:child_process");
16483
16495
  const code = input.code;
16484
16496
  const language = input.language;
@@ -16495,14 +16507,14 @@ function createShellTools(_config, hooks) {
16495
16507
  const interpreter = interpreterMap[language];
16496
16508
  if (!interpreter)
16497
16509
  return { error: `Unsupported language: ${language}` };
16498
- const tmpFile = join47(tmpdir13(), `oj-code-${randomUUID14().slice(0, 8)}${ext}`);
16510
+ const tmpFile = join48(tmpdir13(), `oj-code-${randomUUID15().slice(0, 8)}${ext}`);
16499
16511
  try {
16500
- writeFileSync22(tmpFile, code, "utf-8");
16512
+ writeFileSync23(tmpFile, code, "utf-8");
16501
16513
  const startTime = Date.now();
16502
- const result = await new Promise((resolve5) => {
16514
+ const result = await new Promise((resolve6) => {
16503
16515
  execFile3(interpreter.cmd, [...interpreter.args, tmpFile], { timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout2, stderr2) => {
16504
16516
  const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
16505
- resolve5({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
16517
+ resolve6({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
16506
16518
  });
16507
16519
  });
16508
16520
  const executionTimeMs = Date.now() - startTime;
@@ -16595,7 +16607,7 @@ function createShellTools(_config, hooks) {
16595
16607
  required: ["title", "message"]
16596
16608
  },
16597
16609
  execute: /* @__PURE__ */ __name(async (input) => {
16598
- return new Promise((resolve5) => {
16610
+ return new Promise((resolve6) => {
16599
16611
  notifier.notify({
16600
16612
  title: input.title,
16601
16613
  message: input.message,
@@ -16603,9 +16615,9 @@ function createShellTools(_config, hooks) {
16603
16615
  sound: true
16604
16616
  }, (err) => {
16605
16617
  if (err) {
16606
- resolve5({ error: err.message });
16618
+ resolve6({ error: err.message });
16607
16619
  } else {
16608
- resolve5({ success: true });
16620
+ resolve6({ success: true });
16609
16621
  }
16610
16622
  });
16611
16623
  });
@@ -16709,7 +16721,7 @@ function createShellTools(_config, hooks) {
16709
16721
  },
16710
16722
  execute: /* @__PURE__ */ __name(async (input) => {
16711
16723
  const seconds = Math.min(Math.max(0.1, input.seconds), 60);
16712
- await new Promise((resolve5) => setTimeout(resolve5, seconds * 1e3));
16724
+ await new Promise((resolve6) => setTimeout(resolve6, seconds * 1e3));
16713
16725
  return { waited: seconds, message: `Waited ${seconds} seconds` };
16714
16726
  }, "execute")
16715
16727
  },
@@ -17821,7 +17833,7 @@ var init_office_desktop = __esm({
17821
17833
  return Array.isArray(parsed) ? parsed : [parsed];
17822
17834
  }
17823
17835
  sleep(ms) {
17824
- return new Promise((resolve5) => setTimeout(resolve5, ms));
17836
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
17825
17837
  }
17826
17838
  };
17827
17839
  }
@@ -19502,7 +19514,7 @@ public class Win32Send {
19502
19514
  }
19503
19515
  }
19504
19516
  sleep(ms) {
19505
- return new Promise((resolve5) => setTimeout(resolve5, ms));
19517
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
19506
19518
  }
19507
19519
  };
19508
19520
  }
@@ -19567,7 +19579,7 @@ function getActiveMonitors() {
19567
19579
  return result;
19568
19580
  }
19569
19581
  function sleep3(ms) {
19570
- return new Promise((resolve5) => setTimeout(resolve5, ms));
19582
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
19571
19583
  }
19572
19584
  async function fileHash(filePath) {
19573
19585
  const data = fs.readFileSync(filePath);
@@ -21040,11 +21052,11 @@ function loadFlatSkillsFromDir(dir2, source, priority, out) {
21040
21052
  function loadPackagedSkillsFromDir(dir2, out) {
21041
21053
  for (const entry of safeReadDir(dir2)) {
21042
21054
  if (!entry.isDirectory()) continue;
21043
- const rootDir = join22(dir2, entry.name);
21044
- const entrypoint = findPackageEntrypoint(rootDir);
21055
+ const rootDir2 = join22(dir2, entry.name);
21056
+ const entrypoint = findPackageEntrypoint(rootDir2);
21045
21057
  if (!entrypoint) continue;
21046
- const filePath = join22(rootDir, entrypoint);
21047
- const skill = parseSkillAtPath(filePath, rootDir, entrypoint, "user", `${entry.name}.md`);
21058
+ const filePath = join22(rootDir2, entrypoint);
21059
+ const skill = parseSkillAtPath(filePath, rootDir2, entrypoint, "user", `${entry.name}.md`);
21048
21060
  if (skill) putSkill(out, skill, 2);
21049
21061
  }
21050
21062
  }
@@ -21057,7 +21069,7 @@ function findPackageEntrypoint(dir2) {
21057
21069
  const markdown = entries.filter((entry) => isMarkdown(entry.name));
21058
21070
  return markdown.length === 1 ? markdown[0].name : null;
21059
21071
  }
21060
- function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilename) {
21072
+ function parseSkillAtPath(filePath, rootDir2, entrypoint, source, fallbackFilename) {
21061
21073
  try {
21062
21074
  const content = readFileSync14(filePath, "utf-8").trim();
21063
21075
  if (!content) return null;
@@ -21068,7 +21080,7 @@ function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilenam
21068
21080
  name,
21069
21081
  meta: { ...parsed.meta, name },
21070
21082
  filePath,
21071
- rootDir,
21083
+ rootDir: rootDir2,
21072
21084
  entrypoint,
21073
21085
  source,
21074
21086
  hasFrontmatter: parsed.hasFrontmatter
@@ -21725,12 +21737,12 @@ var init_telegram = __esm({
21725
21737
  Options: ${chunk.choices.join(" | ")}` : "";
21726
21738
  await this.bot.sendMessage(chatId, `\u2753 ${question}${choicesText}`);
21727
21739
  updateStatus("\u2753 Waiting for your response...");
21728
- const userReply = await new Promise((resolve5) => {
21729
- this._pendingReplyResolver = resolve5;
21740
+ const userReply = await new Promise((resolve6) => {
21741
+ this._pendingReplyResolver = resolve6;
21730
21742
  setTimeout(() => {
21731
- if (this._pendingReplyResolver === resolve5) {
21743
+ if (this._pendingReplyResolver === resolve6) {
21732
21744
  this._pendingReplyResolver = null;
21733
- resolve5("[No response \u2014 timed out]");
21745
+ resolve6("[No response \u2014 timed out]");
21734
21746
  }
21735
21747
  }, 5 * 60 * 1e3);
21736
21748
  });
@@ -22328,7 +22340,7 @@ async function promptConsent(servers) {
22328
22340
  input: process.stdin,
22329
22341
  output: process.stderr
22330
22342
  });
22331
- const ask = /* @__PURE__ */ __name((question) => new Promise((resolve5) => rl.question(question, resolve5)), "ask");
22343
+ const ask = /* @__PURE__ */ __name((question) => new Promise((resolve6) => rl.question(question, resolve6)), "ask");
22332
22344
  const w = process.stderr.columns || 80;
22333
22345
  const inner = w - 4;
22334
22346
  const hLine = "\u2500".repeat(inner);
@@ -24383,7 +24395,7 @@ try {
24383
24395
  }
24384
24396
  `;
24385
24397
  const encoded = Buffer.from(script, "utf16le").toString("base64");
24386
- return new Promise((resolve5) => {
24398
+ return new Promise((resolve6) => {
24387
24399
  const proc = spawn3("powershell.exe", ["-NoProfile", "-STA", "-EncodedCommand", encoded], {
24388
24400
  stdio: ["pipe", "pipe", "pipe"],
24389
24401
  windowsHide: true
@@ -24398,22 +24410,22 @@ try {
24398
24410
  });
24399
24411
  const timer = setTimeout(() => {
24400
24412
  if (!proc.killed) proc.kill();
24401
- resolve5(null);
24413
+ resolve6(null);
24402
24414
  }, (timeoutSeconds + 5) * 1e3);
24403
24415
  proc.on("exit", () => {
24404
24416
  clearTimeout(timer);
24405
24417
  const output = stdout.replace(/#< CLIXML[\s\S]*/m, "").trim();
24406
24418
  if (output === "NO_SPEECH" || output.startsWith("ERROR") || !output) {
24407
- resolve5(null);
24419
+ resolve6(null);
24408
24420
  return;
24409
24421
  }
24410
24422
  const parts = output.split("|");
24411
24423
  const text = parts[0]?.trim();
24412
24424
  const confidence = parseFloat(parts[1] || "0");
24413
24425
  if (text) {
24414
- resolve5({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
24426
+ resolve6({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
24415
24427
  } else {
24416
- resolve5(null);
24428
+ resolve6(null);
24417
24429
  }
24418
24430
  try {
24419
24431
  if (existsSync21(resultFile)) unlinkSync5(resultFile);
@@ -24750,6 +24762,7 @@ var init_PromptInput = __esm({
24750
24762
  { name: "/repl", description: "\u{1F527} Start interactive code REPL" },
24751
24763
  { name: "/voice", description: "\u{1F50A} Toggle voice output (TTS)" },
24752
24764
  { name: "/fork", description: "\u{1F500} Spawn background sub-agent" },
24765
+ { name: "/workflow", description: "\u{1F9ED} Run advisory dynamic workflow" },
24753
24766
  { name: "/tasks", description: "List background tasks" },
24754
24767
  { name: "/clear", description: "Clear conversation history" },
24755
24768
  { name: "/compact", description: "Summarize old messages to free context" },
@@ -24782,6 +24795,12 @@ var init_PromptInput = __esm({
24782
24795
  { name: "pause", description: "Pause a task: /schedule pause <id>" },
24783
24796
  { name: "resume", description: "Resume a task: /schedule resume <id>" }
24784
24797
  ],
24798
+ "/workflow": [
24799
+ { name: "status", description: "Open live worker status" },
24800
+ { name: "list", description: "List recent workflows" },
24801
+ { name: "show <id>", description: "Show workflow summary" },
24802
+ { name: "cancel <id>", description: "Cancel a run or worker" }
24803
+ ],
24785
24804
  "/repl": [
24786
24805
  { name: "python", description: "Python interactive shell" },
24787
24806
  { name: "node", description: "Node.js interactive shell" },
@@ -25985,8 +26004,8 @@ function isSensitivePath(resolved) {
25985
26004
  if (re.test(normalized)) return true;
25986
26005
  }
25987
26006
  for (const re of SENSITIVE_FILE_PATTERNS) {
25988
- const basename4 = path2.basename(resolved);
25989
- if (re.test(basename4)) return true;
26007
+ const basename5 = path2.basename(resolved);
26008
+ if (re.test(basename5)) return true;
25990
26009
  }
25991
26010
  return false;
25992
26011
  }
@@ -26104,34 +26123,34 @@ ${output}
26104
26123
  }
26105
26124
  }
26106
26125
  function expandUrl(ref, warnings) {
26107
- return new Promise((resolve5) => {
26126
+ return new Promise((resolve6) => {
26108
26127
  const url = ref.target;
26109
26128
  const mod = url.startsWith("https") ? https : http;
26110
26129
  const req = mod.get(url, { timeout: 15e3 }, (res) => {
26111
26130
  if (res.statusCode && (res.statusCode >= 300 && res.statusCode < 400) && res.headers.location) {
26112
26131
  const redirectMod = res.headers.location.startsWith("https") ? https : http;
26113
26132
  const req2 = redirectMod.get(res.headers.location, { timeout: 15e3 }, (res2) => {
26114
- collectResponse(res2, ref, warnings, resolve5);
26133
+ collectResponse(res2, ref, warnings, resolve6);
26115
26134
  });
26116
26135
  req2.on("error", (e) => {
26117
26136
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26118
- resolve5(null);
26137
+ resolve6(null);
26119
26138
  });
26120
26139
  return;
26121
26140
  }
26122
- collectResponse(res, ref, warnings, resolve5);
26141
+ collectResponse(res, ref, warnings, resolve6);
26123
26142
  });
26124
26143
  req.on("error", (e) => {
26125
26144
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26126
- resolve5(null);
26145
+ resolve6(null);
26127
26146
  });
26128
26147
  });
26129
26148
  }
26130
- function collectResponse(res, ref, warnings, resolve5) {
26149
+ function collectResponse(res, ref, warnings, resolve6) {
26131
26150
  if (res.statusCode && res.statusCode >= 400) {
26132
26151
  warnings.push(`\u26A0\uFE0F @url returned HTTP ${res.statusCode}: ${ref.target}`);
26133
26152
  res.resume();
26134
- resolve5(null);
26153
+ resolve6(null);
26135
26154
  return;
26136
26155
  }
26137
26156
  const chunks = [];
@@ -26143,14 +26162,14 @@ function collectResponse(res, ref, warnings, resolve5) {
26143
26162
  text = text.slice(0, 5e4) + "\n\u2026 (truncated)";
26144
26163
  }
26145
26164
  const tokens = estimateTokens2(text);
26146
- resolve5(`\u{1F310} @url:${ref.target} (${tokens} tokens)
26165
+ resolve6(`\u{1F310} @url:${ref.target} (${tokens} tokens)
26147
26166
  \`\`\`
26148
26167
  ${text}
26149
26168
  \`\`\``);
26150
26169
  });
26151
26170
  res.on("error", (e) => {
26152
26171
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26153
- resolve5(null);
26172
+ resolve6(null);
26154
26173
  });
26155
26174
  }
26156
26175
  function stripHtml(html) {
@@ -26651,13 +26670,13 @@ Type /resume <id> to resume.` });
26651
26670
  if (input === "/export") {
26652
26671
  try {
26653
26672
  const { writeFile: writeFile5, mkdir: mkdir5 } = await import("node:fs/promises");
26654
- const { join: join47 } = await import("node:path");
26655
- const { homedir: homedir31 } = await import("node:os");
26656
- const { existsSync: existsSync33 } = await import("node:fs");
26657
- const exportDir = join47(homedir31(), ".openjaw-agent", "exports");
26658
- if (!existsSync33(exportDir)) await mkdir5(exportDir, { recursive: true });
26673
+ const { join: join48 } = await import("node:path");
26674
+ const { homedir: homedir32 } = await import("node:os");
26675
+ const { existsSync: existsSync34 } = await import("node:fs");
26676
+ const exportDir = join48(homedir32(), ".openjaw-agent", "exports");
26677
+ if (!existsSync34(exportDir)) await mkdir5(exportDir, { recursive: true });
26659
26678
  const filename = `session-${agentLoop.sessionId}.md`;
26660
- const filepath = join47(exportDir, filename);
26679
+ const filepath = join48(exportDir, filename);
26661
26680
  const lines = [
26662
26681
  `# OpenJaw Agent Session ${agentLoop.sessionId}`,
26663
26682
  `Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -26876,9 +26895,9 @@ ${list}` });
26876
26895
  const cmd = lang === "python" ? "python" : lang === "node" ? "node" : "pwsh";
26877
26896
  const args = lang === "python" ? ["-i", "-u"] : lang === "node" ? ["-i"] : ["-NoProfile", "-NoLogo"];
26878
26897
  try {
26879
- const { spawn: spawn9 } = await import("node:child_process");
26898
+ const { spawn: spawn8 } = await import("node:child_process");
26880
26899
  if (replRef.current) replRef.current.kill();
26881
- const proc = spawn9(cmd, args, {
26900
+ const proc = spawn8(cmd, args, {
26882
26901
  stdio: ["pipe", "pipe", "pipe"],
26883
26902
  windowsHide: true
26884
26903
  });
@@ -26918,7 +26937,7 @@ ${list}` });
26918
26937
  if (replRef.current && replLangRef.current && !input.startsWith("/")) {
26919
26938
  const proc = replRef.current;
26920
26939
  proc.stdin?.write(input + "\n");
26921
- await new Promise((resolve5) => setTimeout(resolve5, 800));
26940
+ await new Promise((resolve6) => setTimeout(resolve6, 800));
26922
26941
  const flush = proc._ojFlush;
26923
26942
  const output = flush ? flush() : "";
26924
26943
  if (output.trim()) {
@@ -27232,14 +27251,14 @@ Options: ${chunk.choices.join(" | ")}` : chunk.content;
27232
27251
  waitingForAskUserRef.current = true;
27233
27252
  setIsRunning(false);
27234
27253
  setWaitingForAskUser(true);
27235
- await new Promise((resolve5) => {
27254
+ await new Promise((resolve6) => {
27236
27255
  const checkInterval = setInterval(() => {
27237
27256
  if (!waitingForAskUserRef.current || !agentLoop.isWaitingForAskUser) {
27238
27257
  clearInterval(checkInterval);
27239
27258
  waitingForAskUserRef.current = false;
27240
27259
  setWaitingForAskUser(false);
27241
27260
  setIsRunning(true);
27242
- resolve5();
27261
+ resolve6();
27243
27262
  }
27244
27263
  }, 200);
27245
27264
  });
@@ -27535,8 +27554,8 @@ async function withTimeout(promise, ms) {
27535
27554
  try {
27536
27555
  return await Promise.race([
27537
27556
  promise,
27538
- new Promise((resolve5) => {
27539
- timeoutId = setTimeout(() => resolve5(SKILL_TIMEOUT), ms);
27557
+ new Promise((resolve6) => {
27558
+ timeoutId = setTimeout(() => resolve6(SKILL_TIMEOUT), ms);
27540
27559
  })
27541
27560
  ]);
27542
27561
  } finally {
@@ -28146,12 +28165,12 @@ var init_teams = __esm({
28146
28165
  Options: ${chunk.choices.join(" | ")}` : "";
28147
28166
  await this.sendToSelfChat(`\u2753 ${question}${choicesText}`);
28148
28167
  if (!answerSent) await updateStatus("\u{1F916} OpenJaw Agent \u2014 \u2753 Waiting for your response...");
28149
- const userReply = await new Promise((resolve5) => {
28150
- this._pendingReplyResolver = resolve5;
28168
+ const userReply = await new Promise((resolve6) => {
28169
+ this._pendingReplyResolver = resolve6;
28151
28170
  setTimeout(() => {
28152
- if (this._pendingReplyResolver === resolve5) {
28171
+ if (this._pendingReplyResolver === resolve6) {
28153
28172
  this._pendingReplyResolver = null;
28154
- resolve5("[No response \u2014 timed out]");
28173
+ resolve6("[No response \u2014 timed out]");
28155
28174
  }
28156
28175
  }, 5 * 60 * 1e3);
28157
28176
  });
@@ -28635,12 +28654,12 @@ ${err.stack?.split("\n").slice(0, 3).join("\n")}` : String(err);
28635
28654
  }
28636
28655
  /** Wait for the next user message (used by ask_user flow) */
28637
28656
  waitForUserReply(_chatId) {
28638
- return new Promise((resolve5) => {
28639
- this._pendingReplyResolver = resolve5;
28657
+ return new Promise((resolve6) => {
28658
+ this._pendingReplyResolver = resolve6;
28640
28659
  setTimeout(() => {
28641
- if (this._pendingReplyResolver === resolve5) {
28660
+ if (this._pendingReplyResolver === resolve6) {
28642
28661
  this._pendingReplyResolver = null;
28643
- resolve5("[No response \u2014 timed out]");
28662
+ resolve6("[No response \u2014 timed out]");
28644
28663
  }
28645
28664
  }, 5 * 60 * 1e3);
28646
28665
  });
@@ -28931,9 +28950,9 @@ var init_wechat2 = __esm({
28931
28950
  try {
28932
28951
  const qrTerminal = await import("qrcode-terminal");
28933
28952
  const mod = qrTerminal.default || qrTerminal;
28934
- const qrAscii = await new Promise((resolve5, reject) => {
28953
+ const qrAscii = await new Promise((resolve6, reject) => {
28935
28954
  try {
28936
- mod.generate(qrUrl, { small: true }, (out) => resolve5(out));
28955
+ mod.generate(qrUrl, { small: true }, (out) => resolve6(out));
28937
28956
  } catch (e) {
28938
28957
  reject(e);
28939
28958
  }
@@ -29199,12 +29218,12 @@ Scan URL manually: ${qrUrl}` });
29199
29218
  const choicesText = chunk.choices?.length ? `
29200
29219
  \u9009\u9879: ${chunk.choices.join(" | ")}` : "";
29201
29220
  await this.sendText(userId, `\u2753 ${question}${choicesText}`, contextToken);
29202
- const userReply = await new Promise((resolve5) => {
29203
- this._pendingReplyResolver = resolve5;
29221
+ const userReply = await new Promise((resolve6) => {
29222
+ this._pendingReplyResolver = resolve6;
29204
29223
  setTimeout(() => {
29205
- if (this._pendingReplyResolver === resolve5) {
29224
+ if (this._pendingReplyResolver === resolve6) {
29206
29225
  this._pendingReplyResolver = null;
29207
- resolve5("[No response \u2014 timed out]");
29226
+ resolve6("[No response \u2014 timed out]");
29208
29227
  }
29209
29228
  }, 5 * 60 * 1e3);
29210
29229
  });
@@ -30705,8 +30724,8 @@ function createPromptCollector(bus, getSessionId) {
30705
30724
  }, "emit");
30706
30725
  const register = /* @__PURE__ */ __name((kind) => {
30707
30726
  const requestId = randomUUID12();
30708
- const promise = new Promise((resolve5) => {
30709
- pending.set(requestId, { kind, resolve: resolve5 });
30727
+ const promise = new Promise((resolve6) => {
30728
+ pending.set(requestId, { kind, resolve: resolve6 });
30710
30729
  });
30711
30730
  return { promise, requestId };
30712
30731
  }, "register");
@@ -31686,11 +31705,11 @@ function detectTerminal() {
31686
31705
  }
31687
31706
  return process.env.TERM ?? null;
31688
31707
  }
31689
- function supportsOsc52Clipboard(terminal = env.terminal) {
31690
- return OSC52_CAPABLE_TERMINALS.includes(terminal ?? "");
31708
+ function supportsOsc52Clipboard(terminal2 = env.terminal) {
31709
+ return OSC52_CAPABLE_TERMINALS.includes(terminal2 ?? "");
31691
31710
  }
31692
31711
  function execFileNoThrow(file2, args, options = {}) {
31693
- return new Promise((resolve5) => {
31712
+ return new Promise((resolve6) => {
31694
31713
  const child = spawn4(file2, args, {
31695
31714
  cwd: options.useCwd ? process.cwd() : void 0,
31696
31715
  env: options.env,
@@ -31713,13 +31732,13 @@ function execFileNoThrow(file2, args, options = {}) {
31713
31732
  if (timer) {
31714
31733
  clearTimeout(timer);
31715
31734
  }
31716
- resolve5({ stdout, stderr, code: 1, error: String(error) });
31735
+ resolve6({ stdout, stderr, code: 1, error: String(error) });
31717
31736
  });
31718
31737
  child.on("close", (code) => {
31719
31738
  if (timer) {
31720
31739
  clearTimeout(timer);
31721
31740
  }
31722
- resolve5({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
31741
+ resolve6({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
31723
31742
  });
31724
31743
  if (options.input) {
31725
31744
  child.stdin?.write(options.input);
@@ -31751,7 +31770,7 @@ function shouldEmitClipboardSequence(env2 = process.env) {
31751
31770
  }
31752
31771
  return !!env2["SSH_CONNECTION"] || !env2["TMUX"] && !env2["STY"];
31753
31772
  }
31754
- function shouldUseNativeClipboard(env2 = process.env, terminal = env.terminal) {
31773
+ function shouldUseNativeClipboard(env2 = process.env, terminal2 = env.terminal) {
31755
31774
  if (env2.SSH_CONNECTION) {
31756
31775
  return false;
31757
31776
  }
@@ -31761,7 +31780,7 @@ function shouldUseNativeClipboard(env2 = process.env, terminal = env.terminal) {
31761
31780
  if (!shouldEmitClipboardSequence(env2)) {
31762
31781
  return true;
31763
31782
  }
31764
- return !supportsOsc52Clipboard(terminal);
31783
+ return !supportsOsc52Clipboard(terminal2);
31765
31784
  }
31766
31785
  function tmuxPassthrough(payload) {
31767
31786
  return `${ESC}Ptmux;${payload.replaceAll(ESC, ESC + ESC)}${ST}`;
@@ -33197,7 +33216,7 @@ function needsAltScreenResizeScrollbackClear(env2 = process.env) {
33197
33216
  function supportsExtendedKeys() {
33198
33217
  return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? "");
33199
33218
  }
33200
- function writeDiffToTerminal(terminal, diff2, skipSyncMarkers = false, onDrain) {
33219
+ function writeDiffToTerminal(terminal2, diff2, skipSyncMarkers = false, onDrain) {
33201
33220
  if (diff2.length === 0) {
33202
33221
  return { bytes: 0, backpressure: false };
33203
33222
  }
@@ -33242,7 +33261,7 @@ function writeDiffToTerminal(terminal, diff2, skipSyncMarkers = false, onDrain)
33242
33261
  if (useSync) {
33243
33262
  buffer += ESU;
33244
33263
  }
33245
- const wrote = onDrain ? terminal.stdout.write(buffer, () => onDrain()) : terminal.stdout.write(buffer);
33264
+ const wrote = onDrain ? terminal2.stdout.write(buffer, () => onDrain()) : terminal2.stdout.write(buffer);
33246
33265
  return { bytes: Buffer.byteLength(buffer, "utf8"), backpressure: !wrote };
33247
33266
  }
33248
33267
  function logForDebugging(_message, _options = {}) {
@@ -36692,8 +36711,8 @@ function setTerminalFocused(v) {
36692
36711
  cb();
36693
36712
  }
36694
36713
  if (!v) {
36695
- for (const resolve5 of resolvers) {
36696
- resolve5();
36714
+ for (const resolve6 of resolvers) {
36715
+ resolve6();
36697
36716
  }
36698
36717
  resolvers.clear();
36699
36718
  }
@@ -42931,11 +42950,11 @@ $ npm install --save-dev react-devtools-core
42931
42950
  * and the terminal doesn't respond, the promise remains pending.
42932
42951
  */
42933
42952
  send(query) {
42934
- return new Promise((resolve5) => {
42953
+ return new Promise((resolve6) => {
42935
42954
  this.queue.push({
42936
42955
  kind: "query",
42937
42956
  match: query.match,
42938
- resolve: /* @__PURE__ */ __name((r) => resolve5(r), "resolve")
42957
+ resolve: /* @__PURE__ */ __name((r) => resolve6(r), "resolve")
42939
42958
  });
42940
42959
  this.stdout.write(query.request);
42941
42960
  });
@@ -42950,8 +42969,8 @@ $ npm install --save-dev react-devtools-core
42950
42969
  * Safe to call with no pending queries — still waits for a round-trip.
42951
42970
  */
42952
42971
  flush() {
42953
- return new Promise((resolve5) => {
42954
- this.queue.push({ kind: "sentinel", resolve: resolve5 });
42972
+ return new Promise((resolve6) => {
42973
+ this.queue.push({ kind: "sentinel", resolve: resolve6 });
42955
42974
  this.stdout.write(SENTINEL);
42956
42975
  });
42957
42976
  }
@@ -45431,8 +45450,8 @@ $ npm install --save-dev react-devtools-core
45431
45450
  }
45432
45451
  }
45433
45452
  async waitUntilExit() {
45434
- this.exitPromise ||= new Promise((resolve5, reject) => {
45435
- this.resolveExitPromise = resolve5;
45453
+ this.exitPromise ||= new Promise((resolve6, reject) => {
45454
+ this.resolveExitPromise = resolve6;
45436
45455
  this.rejectExitPromise = reject;
45437
45456
  });
45438
45457
  return this.exitPromise;
@@ -45923,10 +45942,10 @@ async function writeClipboardText(text, platform2 = process.platform, start = sp
45923
45942
  const candidates = writeClipboardCommands(platform2, env2);
45924
45943
  for (const { cmd, args } of candidates) {
45925
45944
  try {
45926
- const ok = await new Promise((resolve5) => {
45945
+ const ok = await new Promise((resolve6) => {
45927
45946
  const child = start(cmd, [...args], { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
45928
- child.once("error", () => resolve5(false));
45929
- child.once("close", (code) => resolve5(code === 0));
45947
+ child.once("error", () => resolve6(false));
45948
+ child.once("close", (code) => resolve6(code === 0));
45930
45949
  child.stdin?.end(text);
45931
45950
  });
45932
45951
  if (ok) {
@@ -45985,8 +46004,8 @@ async function readOsc52Clipboard(querier, timeoutMs = 500) {
45985
46004
  if (!querier) {
45986
46005
  return null;
45987
46006
  }
45988
- const timeout = new Promise((resolve5) => {
45989
- setTimeout(() => resolve5(void 0), timeoutMs);
46007
+ const timeout = new Promise((resolve6) => {
46008
+ setTimeout(() => resolve6(void 0), timeoutMs);
45990
46009
  });
45991
46010
  const query = querier.send({
45992
46011
  request: buildOsc52ClipboardQuery(),
@@ -46143,12 +46162,12 @@ async function backupFile(filePath, ops) {
46143
46162
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
46144
46163
  await ops.copyFile(filePath, `${filePath}.backup.${stamp}`);
46145
46164
  }
46146
- async function configureTerminalKeybindings(terminal, options) {
46165
+ async function configureTerminalKeybindings(terminal2, options) {
46147
46166
  const env2 = options?.env ?? process.env;
46148
46167
  const platform2 = options?.platform ?? process.platform;
46149
46168
  const homeDir = options?.homeDir ?? homedir25();
46150
46169
  const ops = { ...DEFAULT_FILE_OPS, ...options?.fileOps ?? {} };
46151
- const meta = TERMINAL_META[terminal];
46170
+ const meta = TERMINAL_META[terminal2];
46152
46171
  if (isRemoteShellSession(env2)) {
46153
46172
  return {
46154
46173
  success: false,
@@ -46343,6 +46362,7 @@ var init_overlayStore = __esm({
46343
46362
  buildOverlayState = /* @__PURE__ */ __name(() => ({
46344
46363
  agents: false,
46345
46364
  agentsInitialHistoryIndex: 0,
46365
+ agentsWorkflowId: null,
46346
46366
  approval: null,
46347
46367
  clarify: null,
46348
46368
  confirm: null,
@@ -46365,6 +46385,7 @@ var init_overlayStore = __esm({
46365
46385
  ...buildOverlayState(),
46366
46386
  agents: $overlayState.get().agents,
46367
46387
  agentsInitialHistoryIndex: $overlayState.get().agentsInitialHistoryIndex,
46388
+ agentsWorkflowId: $overlayState.get().agentsWorkflowId,
46368
46389
  mcpHub: $overlayState.get().mcpHub,
46369
46390
  modelPicker: $overlayState.get().modelPicker,
46370
46391
  modelPickerMode: $overlayState.get().modelPickerMode,
@@ -47164,13 +47185,25 @@ var init_debug = __esm({
47164
47185
  }
47165
47186
  });
47166
47187
 
47188
+ // src/app/workflowStore.ts
47189
+ import { atom as atom3 } from "nanostores";
47190
+ var $workflowSnapshots, setWorkflowSnapshot;
47191
+ var init_workflowStore = __esm({
47192
+ "src/app/workflowStore.ts"() {
47193
+ "use strict";
47194
+ $workflowSnapshots = atom3({});
47195
+ setWorkflowSnapshot = /* @__PURE__ */ __name((snapshot) => $workflowSnapshots.set({ ...$workflowSnapshots.get(), [snapshot.id]: snapshot }), "setWorkflowSnapshot");
47196
+ }
47197
+ });
47198
+
47167
47199
  // src/app/slash/commands/openjaw.ts
47168
- var fmt, money, stub, eventLine, parseScheduleInput, showUsage, openjawCommands;
47200
+ var fmt, money, stub, eventLine, parseScheduleInput, asWorkflowSnapshot, workflowLine, workflowRows, showUsage, openjawCommands;
47169
47201
  var init_openjaw = __esm({
47170
47202
  "src/app/slash/commands/openjaw.ts"() {
47171
47203
  "use strict";
47172
47204
  init_usage();
47173
47205
  init_overlayStore();
47206
+ init_workflowStore();
47174
47207
  fmt = /* @__PURE__ */ __name((n) => (n ?? 0).toLocaleString(), "fmt");
47175
47208
  money = /* @__PURE__ */ __name((n) => `$${(n ?? 0).toFixed(4)}`, "money");
47176
47209
  stub = /* @__PURE__ */ __name((ctx, command, message) => {
@@ -47210,6 +47243,14 @@ var init_openjaw = __esm({
47210
47243
  }
47211
47244
  return null;
47212
47245
  }, "parseScheduleInput");
47246
+ asWorkflowSnapshot = /* @__PURE__ */ __name((run) => run ? run : null, "asWorkflowSnapshot");
47247
+ workflowLine = /* @__PURE__ */ __name((run) => {
47248
+ const active = run.workers.filter((worker) => worker.status === "running" || worker.status === "queued").length;
47249
+ const done = run.workers.filter((worker) => worker.status === "completed").length;
47250
+ return `${run.id} \xB7 ${run.status} \xB7 ${done}/${run.workers.length} done${active ? ` \xB7 ${active} active` : ""} \xB7 ${run.goal}`;
47251
+ }, "workflowLine");
47252
+ workflowRows = /* @__PURE__ */ __name((runs) => runs.map((run) => [run.id, `${run.status} \xB7 ${run.workerCount}/${run.plannedWorkerCount} workers
47253
+ ${run.goal}`]), "workflowRows");
47213
47254
  showUsage = /* @__PURE__ */ __name((ctx, render2) => {
47214
47255
  ctx.gateway.rpc("session.usage", { session_id: ctx.sid }).then(ctx.guarded(render2)).catch(ctx.guardedErr);
47215
47256
  }, "showUsage");
@@ -47231,6 +47272,10 @@ var init_openjaw = __esm({
47231
47272
  if (!r.messages?.length) {
47232
47273
  ctx.transcript.sys("connect: no output");
47233
47274
  }
47275
+ if (!ctx.sid && r.config_updates) {
47276
+ ctx.transcript.sys("provider connected \u2014 starting OpenJaw session\u2026");
47277
+ ctx.session.newSession();
47278
+ }
47234
47279
  })
47235
47280
  ).catch(ctx.guardedErr);
47236
47281
  }, "run")
@@ -47324,6 +47369,70 @@ var init_openjaw = __esm({
47324
47369
  ).catch(ctx.guardedErr);
47325
47370
  }, "run")
47326
47371
  },
47372
+ {
47373
+ aliases: ["wf"],
47374
+ help: "start or inspect an advisory dynamic workflow",
47375
+ name: "workflow",
47376
+ usage: "/workflow <goal> | /workflow status [id] | list | show [id] | cancel <id>",
47377
+ run: /* @__PURE__ */ __name((arg, ctx) => {
47378
+ const text = arg.trim();
47379
+ if (!text) {
47380
+ return ctx.transcript.sys("usage: /workflow <goal> | /workflow status [id] | list | show [id] | cancel <id>");
47381
+ }
47382
+ const [subRaw, ...rest] = text.split(/\s+/);
47383
+ const sub = subRaw?.toLowerCase() ?? "";
47384
+ const remainder = rest.join(" ").trim();
47385
+ if (sub === "status") {
47386
+ return ctx.gateway.rpc("workflow.status", { id: remainder, session_id: ctx.sid }).then(
47387
+ ctx.guarded((r) => {
47388
+ const snapshot = asWorkflowSnapshot(r.run);
47389
+ if (!snapshot) return ctx.transcript.sys(r.error || "no workflows yet");
47390
+ setWorkflowSnapshot(snapshot);
47391
+ patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0, agentsWorkflowId: snapshot.id });
47392
+ ctx.transcript.sys(`workflow status \xB7 ${workflowLine(snapshot)}`);
47393
+ })
47394
+ ).catch(ctx.guardedErr);
47395
+ }
47396
+ if (sub === "list" || sub === "ls") {
47397
+ return ctx.gateway.rpc("workflow.list", { limit: 30, session_id: ctx.sid }).then(
47398
+ ctx.guarded((r) => {
47399
+ const runs = r.runs ?? [];
47400
+ if (!runs.length) return ctx.transcript.sys("no workflows yet");
47401
+ ctx.transcript.panel("Workflows", [{ rows: workflowRows(runs) }]);
47402
+ })
47403
+ ).catch(ctx.guardedErr);
47404
+ }
47405
+ if (sub === "show") {
47406
+ return ctx.gateway.rpc("workflow.show", { id: remainder, session_id: ctx.sid }).then(
47407
+ ctx.guarded((r) => {
47408
+ const snapshot = asWorkflowSnapshot(r.run);
47409
+ if (!snapshot) return ctx.transcript.sys(r.error || "no workflows yet");
47410
+ setWorkflowSnapshot(snapshot);
47411
+ ctx.transcript.page(snapshot.summary || workflowLine(snapshot), `Workflow ${snapshot.id}`);
47412
+ })
47413
+ ).catch(ctx.guardedErr);
47414
+ }
47415
+ if (sub === "cancel" || sub === "stop") {
47416
+ if (!remainder) return ctx.transcript.sys("usage: /workflow cancel <runId|workerId>");
47417
+ return ctx.gateway.rpc("workflow.cancel", { id: remainder, session_id: ctx.sid }).then(
47418
+ ctx.guarded((r) => {
47419
+ const snapshot = asWorkflowSnapshot(r.run);
47420
+ if (snapshot) setWorkflowSnapshot(snapshot);
47421
+ ctx.transcript.sys(r.ok ? `workflow cancel requested: ${remainder}` : r.error || `not found: ${remainder}`);
47422
+ })
47423
+ ).catch(ctx.guardedErr);
47424
+ }
47425
+ return ctx.gateway.rpc("workflow.start", { goal: text, session_id: ctx.sid }).then(
47426
+ ctx.guarded((r) => {
47427
+ const snapshot = asWorkflowSnapshot(r.run);
47428
+ if (!snapshot) return ctx.transcript.sys(r.error || "workflow failed to start");
47429
+ setWorkflowSnapshot(snapshot);
47430
+ patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0, agentsWorkflowId: snapshot.id });
47431
+ ctx.transcript.sys(`workflow ${snapshot.id} started \xB7 ${snapshot.plannedWorkerCount} workers \xB7 concurrency ${snapshot.concurrency}`);
47432
+ })
47433
+ ).catch(ctx.guardedErr);
47434
+ }, "run")
47435
+ },
47327
47436
  {
47328
47437
  help: "schedule a recurring prompt",
47329
47438
  name: "schedule",
@@ -47456,7 +47565,7 @@ var init_openjaw = __esm({
47456
47565
  });
47457
47566
 
47458
47567
  // src/app/delegationStore.ts
47459
- import { atom as atom3 } from "nanostores";
47568
+ import { atom as atom4 } from "nanostores";
47460
47569
  var buildState, $delegationState, getDelegationState, patchDelegationState, $overlaySectionsOpen, toggleOverlaySection, applyDelegationStatus;
47461
47570
  var init_delegationStore = __esm({
47462
47571
  "src/app/delegationStore.ts"() {
@@ -47467,10 +47576,10 @@ var init_delegationStore = __esm({
47467
47576
  paused: false,
47468
47577
  updatedAt: null
47469
47578
  }), "buildState");
47470
- $delegationState = atom3(buildState());
47579
+ $delegationState = atom4(buildState());
47471
47580
  getDelegationState = /* @__PURE__ */ __name(() => $delegationState.get(), "getDelegationState");
47472
47581
  patchDelegationState = /* @__PURE__ */ __name((next) => $delegationState.set({ ...$delegationState.get(), ...next }), "patchDelegationState");
47473
- $overlaySectionsOpen = atom3({});
47582
+ $overlaySectionsOpen = atom4({});
47474
47583
  toggleOverlaySection = /* @__PURE__ */ __name((title, defaultOpen) => {
47475
47584
  const state = $overlaySectionsOpen.get();
47476
47585
  const current = title in state ? state[title] : defaultOpen;
@@ -47496,7 +47605,7 @@ var init_delegationStore = __esm({
47496
47605
  });
47497
47606
 
47498
47607
  // src/app/spawnHistoryStore.ts
47499
- import { atom as atom4 } from "nanostores";
47608
+ import { atom as atom5 } from "nanostores";
47500
47609
  function summarizeLabel(subagents) {
47501
47610
  const top = subagents.filter((s) => s.parentId == null || subagents.every((o) => o.id !== s.parentId)).slice(0, 2).map((s) => s.goal || "subagent").join(" \xB7 ");
47502
47611
  return top || `${subagents.length} agent${subagents.length === 1 ? "" : "s"}`;
@@ -47539,8 +47648,8 @@ var init_spawnHistoryStore = __esm({
47539
47648
  "src/app/spawnHistoryStore.ts"() {
47540
47649
  "use strict";
47541
47650
  HISTORY_LIMIT = 10;
47542
- $spawnHistory = atom4([]);
47543
- $spawnDiff = atom4(null);
47651
+ $spawnHistory = atom5([]);
47652
+ $spawnDiff = atom5(null);
47544
47653
  getSpawnHistory = /* @__PURE__ */ __name(() => $spawnHistory.get(), "getSpawnHistory");
47545
47654
  clearDiffPair = /* @__PURE__ */ __name(() => $spawnDiff.set(null), "clearDiffPair");
47546
47655
  setDiffPair = /* @__PURE__ */ __name((pair) => $spawnDiff.set(pair), "setDiffPair");
@@ -47850,15 +47959,15 @@ var init_ops = __esm({
47850
47959
  }
47851
47960
  const [a, b] = parts;
47852
47961
  const history = getSpawnHistory();
47853
- const resolve5 = /* @__PURE__ */ __name((token) => {
47962
+ const resolve6 = /* @__PURE__ */ __name((token) => {
47854
47963
  const n = parseInt(token, 10);
47855
47964
  if (Number.isFinite(n) && n >= 1 && n <= history.length) {
47856
47965
  return history[n - 1] ?? null;
47857
47966
  }
47858
47967
  return null;
47859
47968
  }, "resolve");
47860
- const baseline = resolve5(a);
47861
- const candidate = resolve5(b);
47969
+ const baseline = resolve6(a);
47970
+ const candidate = resolve6(b);
47862
47971
  if (!baseline || !candidate) {
47863
47972
  return ctx.transcript.sys(`replay-diff: could not resolve indices \xB7 history has ${history.length} entries`);
47864
47973
  }
@@ -48731,76 +48840,20 @@ var init_session2 = __esm({
48731
48840
  }
48732
48841
  });
48733
48842
 
48734
- // src/lib/externalCli.ts
48735
- import { spawn as spawn6 } from "node:child_process";
48736
- var resolveHermesBin, launchHermesCommand;
48737
- var init_externalCli = __esm({
48738
- "src/lib/externalCli.ts"() {
48739
- "use strict";
48740
- resolveHermesBin = /* @__PURE__ */ __name(() => process.env.OPENJAW_BIN?.trim() || "hermes", "resolveHermesBin");
48741
- launchHermesCommand = /* @__PURE__ */ __name((args) => new Promise((resolve5) => {
48742
- const child = spawn6(resolveHermesBin(), args, { stdio: "inherit" });
48743
- child.on("error", (err) => resolve5({ code: null, error: err.message }));
48744
- child.on("exit", (code) => resolve5({ code }));
48745
- }), "launchHermesCommand");
48746
- }
48747
- });
48748
-
48749
- // src/app/setupHandoff.ts
48750
- async function runExternalSetup({ args, ctx, done, launcher, suspend }) {
48751
- const { gateway, session, transcript } = ctx;
48752
- transcript.sys(`launching \`hermes ${args.join(" ")}\`\u2026`);
48753
- patchUiState({ status: "setup running\u2026" });
48754
- let result = { code: null };
48755
- await suspend(async () => {
48756
- result = await launcher(args);
48757
- });
48758
- if (result.error) {
48759
- transcript.sys(`error launching hermes: ${result.error}`);
48760
- patchUiState({ status: "setup required" });
48761
- return;
48762
- }
48763
- if (result.code !== 0) {
48764
- transcript.sys(`hermes ${args[0]} exited with code ${result.code}`);
48765
- patchUiState({ status: "setup required" });
48766
- return;
48767
- }
48768
- const setup = await gateway.rpc("setup.status", {});
48769
- if (setup?.provider_configured === false) {
48770
- transcript.sys("still no provider configured");
48771
- patchUiState({ status: "setup required" });
48772
- return;
48773
- }
48774
- transcript.sys(done);
48775
- session.newSession();
48776
- }
48777
- var init_setupHandoff = __esm({
48778
- "src/app/setupHandoff.ts"() {
48779
- "use strict";
48780
- init_uiStore();
48781
- __name(runExternalSetup, "runExternalSetup");
48782
- }
48783
- });
48784
-
48785
48843
  // src/app/slash/commands/setup.ts
48786
48844
  var setupCommands;
48787
48845
  var init_setup = __esm({
48788
48846
  "src/app/slash/commands/setup.ts"() {
48789
48847
  "use strict";
48790
- init_entry_exports();
48791
- init_externalCli();
48792
- init_setupHandoff();
48848
+ init_overlayStore();
48793
48849
  setupCommands = [
48794
48850
  {
48795
- help: "run full setup wizard (launches `hermes setup`)",
48851
+ help: "first-run setup help; opens provider connection picker",
48796
48852
  name: "setup",
48797
- run: /* @__PURE__ */ __name((arg, ctx) => void runExternalSetup({
48798
- args: ["setup", ...arg.split(/\s+/).filter(Boolean)],
48799
- ctx,
48800
- done: "setup complete \u2014 starting session\u2026",
48801
- launcher: launchHermesCommand,
48802
- suspend: withInkSuspended
48803
- }), "run")
48853
+ run: /* @__PURE__ */ __name((_arg, ctx) => {
48854
+ ctx.transcript.sys("OpenJaw setup: run /connect to set up a provider, then /model to choose a model.");
48855
+ patchOverlayState({ modelPicker: true, modelPickerMode: "connect" });
48856
+ }, "run")
48804
48857
  }
48805
48858
  ];
48806
48859
  }
@@ -48956,17 +49009,856 @@ var init_catalog = __esm({
48956
49009
  }
48957
49010
  });
48958
49011
 
48959
- // src/rpcHandlers.ts
48960
- import { spawn as spawn7 } from "node:child_process";
48961
- import { randomUUID as randomUUID13 } from "node:crypto";
48962
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readFileSync as readFileSync28, rmSync, statSync as statSync4, writeFileSync as writeFileSync19 } from "node:fs";
49012
+ // src/workflows/planner.ts
49013
+ function resolveDynamicWorkflowConfig(config) {
49014
+ const raw = config.features?.dynamic_workflows ?? {};
49015
+ return {
49016
+ enabled: raw.enabled ?? DEFAULT_DYNAMIC_WORKFLOW_CONFIG.enabled,
49017
+ plannerMode: "adaptive",
49018
+ hardMaxWorkers: clampInt(raw.hard_max_workers, DEFAULT_DYNAMIC_WORKFLOW_CONFIG.hardMaxWorkers, 1, 1e4),
49019
+ hardMaxConcurrentWorkers: clampInt(
49020
+ raw.hard_max_concurrent_workers,
49021
+ DEFAULT_DYNAMIC_WORKFLOW_CONFIG.hardMaxConcurrentWorkers,
49022
+ 1,
49023
+ 2048
49024
+ ),
49025
+ workerTimeoutMs: clampInt(raw.worker_timeout_ms, DEFAULT_DYNAMIC_WORKFLOW_CONFIG.workerTimeoutMs, 1e4, 36e5),
49026
+ persistHistory: raw.persist_history ?? DEFAULT_DYNAMIC_WORKFLOW_CONFIG.persistHistory
49027
+ };
49028
+ }
49029
+ function initialConcurrency(plannedWorkers, limits) {
49030
+ if (plannedWorkers <= 0) return 0;
49031
+ return Math.min(Math.ceil(Math.sqrt(plannedWorkers) * 2), plannedWorkers, limits.hardMaxConcurrentWorkers);
49032
+ }
49033
+ function adjustConcurrency(current, plannedWorkers, limits, event) {
49034
+ if (plannedWorkers <= 0) return 0;
49035
+ if (event === "rate_limited") return Math.max(1, Math.floor(current / 2));
49036
+ return Math.min(current + 1, plannedWorkers, limits.hardMaxConcurrentWorkers);
49037
+ }
49038
+ function planWorkflow(goal, runId, limits) {
49039
+ const taskGoals = deriveTaskGoals(goal);
49040
+ const specs = [];
49041
+ const maxWorkers = limits.hardMaxWorkers;
49042
+ for (const taskGoal of taskGoals) {
49043
+ if (specs.length >= maxWorkers) break;
49044
+ const id = `${runId}-w${specs.length + 1}`;
49045
+ specs.push({
49046
+ depth: 0,
49047
+ goal: taskGoal,
49048
+ id,
49049
+ index: specs.length,
49050
+ parentId: null,
49051
+ prompt: buildTaskPrompt(goal, taskGoal),
49052
+ role: "task"
49053
+ });
49054
+ }
49055
+ const taskSpecs = [...specs];
49056
+ const verifierEvery = taskSpecs.length >= 8 ? 4 : 3;
49057
+ for (let i = 0; i < taskSpecs.length && specs.length < maxWorkers; i += verifierEvery) {
49058
+ const batch = taskSpecs.slice(i, i + verifierEvery);
49059
+ const id = `${runId}-w${specs.length + 1}`;
49060
+ specs.push({
49061
+ depth: 1,
49062
+ dependsOn: batch.map((item) => item.id),
49063
+ goal: `Verify findings from workers ${batch.map((item) => item.index + 1).join(", ")}`,
49064
+ id,
49065
+ index: specs.length,
49066
+ parentId: batch[0]?.id ?? null,
49067
+ prompt: buildVerifierPrompt(goal, batch),
49068
+ role: "verifier"
49069
+ });
49070
+ }
49071
+ return { concurrency: initialConcurrency(specs.length, limits), specs };
49072
+ }
49073
+ function deriveTaskGoals(goal) {
49074
+ const explicit = extractExplicitTasks(goal);
49075
+ const words = goal.trim().split(/\s+/).filter(Boolean).length;
49076
+ const complexity = Math.min(12, Math.max(3, Math.ceil(words / 35)));
49077
+ const inferred = inferLanes(goal);
49078
+ const seeds = explicit.length > 1 ? explicit : inferred;
49079
+ const out = [];
49080
+ for (const item of seeds) {
49081
+ pushUnique(out, item);
49082
+ }
49083
+ while (out.length < complexity) {
49084
+ const next = DEFAULT_LANES[out.length % DEFAULT_LANES.length];
49085
+ pushUnique(out, next);
49086
+ if (out.length >= DEFAULT_LANES.length && explicit.length <= 1) break;
49087
+ }
49088
+ return out.slice(0, Math.max(1, out.length));
49089
+ }
49090
+ function extractExplicitTasks(goal) {
49091
+ return goal.split(/\r?\n/).map((line) => line.trim().replace(/^[-*+]\s+/, "").replace(/^\d+[.)]\s+/, "").trim()).filter((line) => line.length >= 12);
49092
+ }
49093
+ function inferLanes(goal) {
49094
+ const lower = goal.toLowerCase();
49095
+ const lanes = ["Map the current state and locate relevant files or evidence"];
49096
+ if (/code|repo|branch|test|build|bug|feature|implement/.test(lower)) {
49097
+ lanes.push("Review implementation risks, edge cases, and integration points");
49098
+ lanes.push("Inspect validation strategy and likely test coverage");
49099
+ }
49100
+ if (/research|latest|current|external|claude|compare|similar/.test(lower)) {
49101
+ lanes.push("Research external context and comparable workflow behavior");
49102
+ }
49103
+ if (/ui|status|overlay|navigate|progress|worker/.test(lower)) {
49104
+ lanes.push("Evaluate user experience, status visibility, and navigation requirements");
49105
+ }
49106
+ if (/security|safe|permission|mutat|write|approval/.test(lower)) {
49107
+ lanes.push("Analyze safety boundaries, permissions, and failure modes");
49108
+ }
49109
+ lanes.push("Draft concrete recommendations and acceptance criteria");
49110
+ return lanes;
49111
+ }
49112
+ function pushUnique(items, value) {
49113
+ const normalized = value.trim();
49114
+ if (!normalized) return;
49115
+ if (!items.some((item) => item.toLowerCase() === normalized.toLowerCase())) items.push(normalized);
49116
+ }
49117
+ function buildTaskPrompt(overallGoal, taskGoal) {
49118
+ return [
49119
+ `Overall workflow goal: ${overallGoal}`,
49120
+ `Your assigned read-only worker goal: ${taskGoal}`,
49121
+ "",
49122
+ "Work independently. Use only read/search/analysis tools. Do not modify files, send messages, update memory, or run shell/code execution.",
49123
+ "Return concise findings with evidence, confidence, and open questions."
49124
+ ].join("\n");
49125
+ }
49126
+ function buildVerifierPrompt(overallGoal, batch) {
49127
+ return [
49128
+ `Overall workflow goal: ${overallGoal}`,
49129
+ `Verify the likely claims and gaps for these worker lanes: ${batch.map((item) => item.goal).join("; ")}`,
49130
+ "",
49131
+ "Use read-only checks. Label findings as verified, partially verified, or unverified. Do not modify anything."
49132
+ ].join("\n");
49133
+ }
49134
+ var DEFAULT_DYNAMIC_WORKFLOW_CONFIG, clampInt, DEFAULT_LANES;
49135
+ var init_planner = __esm({
49136
+ "src/workflows/planner.ts"() {
49137
+ "use strict";
49138
+ DEFAULT_DYNAMIC_WORKFLOW_CONFIG = {
49139
+ enabled: true,
49140
+ plannerMode: "adaptive",
49141
+ hardMaxWorkers: 1024,
49142
+ hardMaxConcurrentWorkers: 128,
49143
+ workerTimeoutMs: 18e4,
49144
+ persistHistory: true
49145
+ };
49146
+ clampInt = /* @__PURE__ */ __name((value, fallback, min, max) => {
49147
+ const parsed = typeof value === "number" ? value : Number(value);
49148
+ if (!Number.isFinite(parsed)) return fallback;
49149
+ return Math.min(max, Math.max(min, Math.floor(parsed)));
49150
+ }, "clampInt");
49151
+ __name(resolveDynamicWorkflowConfig, "resolveDynamicWorkflowConfig");
49152
+ __name(initialConcurrency, "initialConcurrency");
49153
+ __name(adjustConcurrency, "adjustConcurrency");
49154
+ __name(planWorkflow, "planWorkflow");
49155
+ __name(deriveTaskGoals, "deriveTaskGoals");
49156
+ __name(extractExplicitTasks, "extractExplicitTasks");
49157
+ __name(inferLanes, "inferLanes");
49158
+ DEFAULT_LANES = [
49159
+ "Map the current state and locate relevant files or evidence",
49160
+ "Review implementation risks, edge cases, and integration points",
49161
+ "Inspect validation strategy and likely test coverage",
49162
+ "Analyze safety boundaries, permissions, and failure modes",
49163
+ "Draft concrete recommendations and acceptance criteria"
49164
+ ];
49165
+ __name(pushUnique, "pushUnique");
49166
+ __name(buildTaskPrompt, "buildTaskPrompt");
49167
+ __name(buildVerifierPrompt, "buildVerifierPrompt");
49168
+ }
49169
+ });
49170
+
49171
+ // src/workflows/persistence.ts
49172
+ import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync7, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
48963
49173
  import { homedir as homedir28 } from "node:os";
48964
- import { basename as basename3, extname as extname4, join as join42 } from "node:path";
49174
+ import { basename as basename3, join as join42, resolve as resolve4 } from "node:path";
49175
+ function ensureDir(path3) {
49176
+ if (!existsSync31(path3)) mkdirSync17(path3, { recursive: true });
49177
+ }
49178
+ function safeSegment(value) {
49179
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 80) || "default";
49180
+ }
49181
+ function readJson(path3) {
49182
+ try {
49183
+ return JSON.parse(readFileSync28(path3, "utf8"));
49184
+ } catch {
49185
+ return null;
49186
+ }
49187
+ }
49188
+ function saveWorkflowSnapshot(snapshot) {
49189
+ ensureDir(workflowDir());
49190
+ const path3 = join42(workflowDir(), `${safeSegment(snapshot.id)}.json`);
49191
+ writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
49192
+ `, "utf8");
49193
+ return path3;
49194
+ }
49195
+ function loadWorkflowSnapshot(id) {
49196
+ const path3 = join42(workflowDir(), `${safeSegment(id)}.json`);
49197
+ return readJson(path3);
49198
+ }
49199
+ function listWorkflowSnapshots(limit = 30) {
49200
+ if (!existsSync31(workflowDir())) return [];
49201
+ const entries = [];
49202
+ for (const entry of readdirSync7(workflowDir(), { withFileTypes: true })) {
49203
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
49204
+ const path3 = join42(workflowDir(), entry.name);
49205
+ const snapshot = readJson(path3);
49206
+ if (!snapshot) continue;
49207
+ entries.push({
49208
+ finishedAt: snapshot.finishedAt,
49209
+ goal: snapshot.goal,
49210
+ id: snapshot.id,
49211
+ path: path3,
49212
+ plannedWorkerCount: snapshot.plannedWorkerCount,
49213
+ startedAt: snapshot.startedAt,
49214
+ status: snapshot.status,
49215
+ workerCount: snapshot.workers.length
49216
+ });
49217
+ }
49218
+ return entries.sort((a, b) => (b.finishedAt ?? b.startedAt) - (a.finishedAt ?? a.startedAt)).slice(0, Math.max(1, limit));
49219
+ }
49220
+ function saveSpawnTreeSnapshot(input) {
49221
+ const sessionId = safeSegment(input.session_id ?? "default");
49222
+ const dir2 = join42(spawnTreeDir(), sessionId);
49223
+ ensureDir(dir2);
49224
+ const stamp = new Date((input.finished_at ?? Date.now() / 1e3) * 1e3).toISOString().replace(/[:.]/g, "-");
49225
+ const path3 = join42(dir2, `${stamp}-${Math.random().toString(36).slice(2, 8)}.json`);
49226
+ const snapshot = {
49227
+ count: input.subagents?.length ?? 0,
49228
+ finished_at: input.finished_at,
49229
+ label: input.label,
49230
+ session_id: input.session_id,
49231
+ started_at: input.started_at,
49232
+ subagents: input.subagents ?? []
49233
+ };
49234
+ writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
49235
+ `, "utf8");
49236
+ return path3;
49237
+ }
49238
+ function listSpawnTreeSnapshots(sessionId = "default", limit = 30) {
49239
+ const dir2 = join42(spawnTreeDir(), safeSegment(sessionId));
49240
+ if (!existsSync31(dir2)) return [];
49241
+ const entries = [];
49242
+ for (const entry of readdirSync7(dir2, { withFileTypes: true })) {
49243
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
49244
+ const path3 = join42(dir2, entry.name);
49245
+ const snapshot = readJson(path3);
49246
+ if (!snapshot) continue;
49247
+ entries.push({
49248
+ count: snapshot.count,
49249
+ finished_at: snapshot.finished_at,
49250
+ label: snapshot.label,
49251
+ path: path3,
49252
+ session_id: snapshot.session_id,
49253
+ started_at: snapshot.started_at
49254
+ });
49255
+ }
49256
+ return entries.sort((a, b) => (b.finished_at ?? 0) - (a.finished_at ?? 0)).slice(0, Math.max(1, limit));
49257
+ }
49258
+ function loadSpawnTreeSnapshot(path3) {
49259
+ const root = resolve4(spawnTreeDir());
49260
+ const resolved = resolve4(path3);
49261
+ if (!resolved.startsWith(root)) return null;
49262
+ const snapshot = readJson(resolved);
49263
+ return snapshot ? { ...snapshot, path: resolved } : null;
49264
+ }
49265
+ var rootDir, workflowDir, spawnTreeDir;
49266
+ var init_persistence = __esm({
49267
+ "src/workflows/persistence.ts"() {
49268
+ "use strict";
49269
+ rootDir = /* @__PURE__ */ __name(() => join42(homedir28(), ".openjaw-agent"), "rootDir");
49270
+ workflowDir = /* @__PURE__ */ __name(() => join42(rootDir(), "workflows"), "workflowDir");
49271
+ spawnTreeDir = /* @__PURE__ */ __name(() => join42(rootDir(), "spawn-trees"), "spawnTreeDir");
49272
+ __name(ensureDir, "ensureDir");
49273
+ __name(safeSegment, "safeSegment");
49274
+ __name(readJson, "readJson");
49275
+ __name(saveWorkflowSnapshot, "saveWorkflowSnapshot");
49276
+ __name(loadWorkflowSnapshot, "loadWorkflowSnapshot");
49277
+ __name(listWorkflowSnapshots, "listWorkflowSnapshots");
49278
+ __name(saveSpawnTreeSnapshot, "saveSpawnTreeSnapshot");
49279
+ __name(listSpawnTreeSnapshots, "listSpawnTreeSnapshots");
49280
+ __name(loadSpawnTreeSnapshot, "loadSpawnTreeSnapshot");
49281
+ }
49282
+ });
49283
+
49284
+ // src/workflows/readOnlyTools.ts
49285
+ function isWorkflowReadOnlyTool(name) {
49286
+ return READ_ONLY_TOOLS.has(name);
49287
+ }
49288
+ var READ_ONLY_TOOLS, ReadOnlyToolRuntime;
49289
+ var init_readOnlyTools = __esm({
49290
+ "src/workflows/readOnlyTools.ts"() {
49291
+ "use strict";
49292
+ READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
49293
+ "browser_snapshot",
49294
+ "browser_extract",
49295
+ "file_info",
49296
+ "file_list",
49297
+ "file_read",
49298
+ "glob",
49299
+ "grep",
49300
+ "image_view",
49301
+ "web_extract",
49302
+ "web_fetch",
49303
+ "web_search"
49304
+ ]);
49305
+ __name(isWorkflowReadOnlyTool, "isWorkflowReadOnlyTool");
49306
+ ReadOnlyToolRuntime = class {
49307
+ constructor(inner) {
49308
+ this.inner = inner;
49309
+ }
49310
+ inner;
49311
+ static {
49312
+ __name(this, "ReadOnlyToolRuntime");
49313
+ }
49314
+ listTools() {
49315
+ return this.inner.listTools().filter((tool) => isWorkflowReadOnlyTool(tool.name));
49316
+ }
49317
+ async execute(name, input) {
49318
+ if (!isWorkflowReadOnlyTool(name)) {
49319
+ throw new Error(`Tool ${name} is not available in advisory workflow workers`);
49320
+ }
49321
+ return await this.inner.execute(name, input);
49322
+ }
49323
+ };
49324
+ }
49325
+ });
49326
+
49327
+ // src/workflows/manager.ts
49328
+ import { randomUUID as randomUUID13 } from "node:crypto";
49329
+ function buildPlannerSystemPrompt(limits) {
49330
+ return [
49331
+ "You are the OpenJaw dynamic workflow planner.",
49332
+ "Return ONLY valid JSON. Do not include markdown fences or prose.",
49333
+ "Create a task graph for advisory read-only worker agents. Do not answer the user task.",
49334
+ "Use enough workers to represent genuinely different research lanes, viewpoints, critics, verifiers, and a final synthesizer. Do not default to a small fixed count.",
49335
+ "For panel-review prompts, create separate panelist workers for each requested philosophy and a critic/risk worker before the synthesizer.",
49336
+ `Physical caps: at most ${limits.hardMaxWorkers} total workers and ${limits.hardMaxConcurrentWorkers} concurrent workers. Normal plans should be smaller than the cap but may contain dozens of workers when useful.`,
49337
+ 'Schema: {"language":"optional output language","workers":[{"id":"kebab-id","role":"researcher|panelist|critic|verifier|synthesizer","philosophy":"optional","goal":"specific worker goal","depends_on":["worker-id"]}]}',
49338
+ "Always include exactly one synthesizer worker that depends on the most important research, panelist, critic, and verifier workers."
49339
+ ].join("\n");
49340
+ }
49341
+ function parseModelPlan(content, goal, runId, limits) {
49342
+ const parsed = JSON.parse(extractJsonObject(content));
49343
+ const workers = Array.isArray(parsed.workers) ? parsed.workers : [];
49344
+ const specs = [];
49345
+ const usedIds = /* @__PURE__ */ new Set();
49346
+ for (const worker of workers.slice(0, limits.hardMaxWorkers)) {
49347
+ const rawGoal = String(worker.goal ?? "").trim();
49348
+ if (!rawGoal) continue;
49349
+ const id = uniqueWorkerId(runId, worker.id || rawGoal, usedIds, specs.length + 1);
49350
+ const role = normalizeRole(worker.role);
49351
+ const dependsOn = Array.isArray(worker.depends_on) ? worker.depends_on.map(String).map((value) => scopedWorkerId(runId, value)).filter((value) => usedIds.has(value)) : [];
49352
+ specs.push({
49353
+ depth: dependsOn.length > 0 || role !== "task" ? 1 : 0,
49354
+ dependsOn,
49355
+ goal: rawGoal,
49356
+ id,
49357
+ index: specs.length,
49358
+ parentId: dependsOn[0] ?? null,
49359
+ prompt: buildModelWorkerPrompt(goal, rawGoal, worker.role, worker.philosophy, parsed.language),
49360
+ role
49361
+ });
49362
+ }
49363
+ return ensureSynthesizer({ concurrency: 0, specs }, goal, runId, limits);
49364
+ }
49365
+ function ensureSynthesizer(plan, goal, runId, limits) {
49366
+ const specs = plan.specs.slice(0, limits.hardMaxWorkers);
49367
+ const existing = specs.find((spec) => spec.role === "synthesizer");
49368
+ const dependencies = specs.filter((spec) => spec.role !== "synthesizer").map((spec) => spec.id);
49369
+ if (existing) {
49370
+ existing.dependsOn = existing.dependsOn?.length ? existing.dependsOn : dependencies;
49371
+ existing.depth = Math.max(existing.depth, 1);
49372
+ existing.parentId = existing.dependsOn[0] ?? null;
49373
+ } else if (specs.length < limits.hardMaxWorkers) {
49374
+ specs.push({
49375
+ depth: 1,
49376
+ dependsOn: dependencies,
49377
+ goal: "Synthesize the panel and research outputs into the final answer",
49378
+ id: `${runId}-synthesizer`,
49379
+ index: specs.length,
49380
+ parentId: dependencies[0] ?? null,
49381
+ prompt: buildModelWorkerPrompt(goal, "Synthesize all completed worker outputs into the final answer for the user.", "synthesizer", void 0, void 0),
49382
+ role: "synthesizer"
49383
+ });
49384
+ }
49385
+ return { concurrency: Math.min(Math.ceil(Math.sqrt(specs.length) * 2), specs.length, limits.hardMaxConcurrentWorkers), specs };
49386
+ }
49387
+ function buildModelWorkerPrompt(overallGoal, workerGoal, role, philosophy, language) {
49388
+ return [
49389
+ `Overall workflow goal: ${overallGoal}`,
49390
+ `Worker role: ${role || "researcher"}`,
49391
+ philosophy ? `Investment/work philosophy: ${philosophy}` : "",
49392
+ language ? `Final language preference: ${language}` : "",
49393
+ `Assigned goal: ${workerGoal}`,
49394
+ "",
49395
+ "Use only read/search/analysis tools. Do not modify files, send messages, update memory, or run shell/code execution.",
49396
+ "If prior worker outputs are provided below, use them as context and explicitly resolve agreements, disagreements, and uncertainty.",
49397
+ "Return evidence-backed findings. A synthesizer must produce the final answer directly for the user."
49398
+ ].filter(Boolean).join("\n");
49399
+ }
49400
+ function normalizeRole(role) {
49401
+ const lower = String(role ?? "").toLowerCase();
49402
+ if (lower.includes("synth") || lower.includes("chair")) return "synthesizer";
49403
+ if (lower.includes("verify") || lower.includes("critic") || lower.includes("risk")) return "verifier";
49404
+ return "task";
49405
+ }
49406
+ function extractJsonObject(content) {
49407
+ const trimmed = content.trim().replace(/^```(?:json)?\s*/i, "").replace(/```$/i, "").trim();
49408
+ const start = trimmed.indexOf("{");
49409
+ const end = trimmed.lastIndexOf("}");
49410
+ if (start < 0 || end < start) throw new Error("planner response did not contain a JSON object");
49411
+ return trimmed.slice(start, end + 1);
49412
+ }
49413
+ function uniqueWorkerId(runId, value, used, index) {
49414
+ const base = scopedWorkerId(runId, value) || `${runId}-w${index}`;
49415
+ let candidate = base;
49416
+ let suffix = 2;
49417
+ while (used.has(candidate)) {
49418
+ candidate = `${base}-${suffix}`;
49419
+ suffix += 1;
49420
+ }
49421
+ used.add(candidate);
49422
+ return candidate;
49423
+ }
49424
+ function scopedWorkerId(runId, value) {
49425
+ const slug = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
49426
+ return slug.startsWith(`${runId}-`) ? slug : `${runId}-${slug || "worker"}`;
49427
+ }
49428
+ function firstParagraph(value) {
49429
+ return value.split(/\n\s*\n/)[0]?.trim().slice(0, 800) || value.trim().slice(0, 800);
49430
+ }
49431
+ function inferVerificationState(value) {
49432
+ const lower = value.toLowerCase();
49433
+ if (lower.includes("partially verified") || lower.includes("partial")) return "partial";
49434
+ if (lower.includes("unverified") || lower.includes("not verified")) return "unverified";
49435
+ if (lower.includes("verified")) return "verified";
49436
+ return "pending";
49437
+ }
49438
+ function dependencyOutputContext(run, spec) {
49439
+ const dependencyIds = spec.dependsOn ?? [];
49440
+ if (dependencyIds.length === 0) return "";
49441
+ const blocks = dependencyIds.map((id) => run.workers.find((worker) => worker.id === id)).filter((worker) => Boolean(worker)).map((worker) => [
49442
+ `## Worker ${worker.id}`,
49443
+ `Role: ${worker.workerRole ?? "task"}`,
49444
+ `Goal: ${worker.goal}`,
49445
+ `Status: ${worker.status}`,
49446
+ worker.summary ? `Summary: ${worker.summary}` : "",
49447
+ worker.details ? `Details:
49448
+ ${worker.details}` : ""
49449
+ ].filter(Boolean).join("\n"));
49450
+ return blocks.length ? `# Prior worker outputs
49451
+ ${blocks.join("\n\n")}` : "";
49452
+ }
49453
+ var terminal, WorkflowManager;
49454
+ var init_manager = __esm({
49455
+ "src/workflows/manager.ts"() {
49456
+ "use strict";
49457
+ init_agent_loop();
49458
+ init_providers();
49459
+ init_planner();
49460
+ init_persistence();
49461
+ init_readOnlyTools();
49462
+ terminal = /* @__PURE__ */ new Set(["cancelled", "completed", "failed"]);
49463
+ WorkflowManager = class {
49464
+ constructor(config, toolRuntime, systemPromptFn, bus, options = {}) {
49465
+ this.config = config;
49466
+ this.toolRuntime = toolRuntime;
49467
+ this.systemPromptFn = systemPromptFn;
49468
+ this.bus = bus;
49469
+ this.planner = options.planner;
49470
+ this.runner = options.runner ?? this.defaultRunner;
49471
+ }
49472
+ config;
49473
+ toolRuntime;
49474
+ systemPromptFn;
49475
+ bus;
49476
+ static {
49477
+ __name(this, "WorkflowManager");
49478
+ }
49479
+ runs = /* @__PURE__ */ new Map();
49480
+ planner;
49481
+ runner;
49482
+ async start(goal, sessionId) {
49483
+ const limits = resolveDynamicWorkflowConfig(this.config);
49484
+ if (!limits.enabled) {
49485
+ throw new Error("dynamic workflows are disabled in config");
49486
+ }
49487
+ const id = `wf-${randomUUID13().slice(0, 8)}`;
49488
+ const plan = await this.buildPlan(goal, id, limits);
49489
+ const now2 = Date.now();
49490
+ const run = {
49491
+ abortController: new AbortController(),
49492
+ activeAborts: /* @__PURE__ */ new Map(),
49493
+ concurrency: plan.concurrency,
49494
+ config: limits,
49495
+ goal,
49496
+ id,
49497
+ plannedWorkerCount: plan.specs.length,
49498
+ startedAt: now2,
49499
+ status: "running",
49500
+ workers: plan.specs.map((spec) => this.workerFromSpec(spec, id, plan.specs.length, now2))
49501
+ };
49502
+ this.runs.set(id, run);
49503
+ this.emitWorkflow("workflow.start", run, sessionId);
49504
+ for (const worker of run.workers) {
49505
+ this.emitSubagent("subagent.spawn_requested", run, worker, sessionId);
49506
+ }
49507
+ void this.executeRun(run, plan.specs, sessionId);
49508
+ return { run: this.snapshot(run), started: true };
49509
+ }
49510
+ status(id) {
49511
+ const run = id ? this.runs.get(id) : this.latestRun();
49512
+ if (run) return this.snapshot(run);
49513
+ if (id) return loadWorkflowSnapshot(id);
49514
+ const first = listWorkflowSnapshots(1)[0];
49515
+ return first ? loadWorkflowSnapshot(first.id) : null;
49516
+ }
49517
+ list(limit = 30) {
49518
+ const live = [...this.runs.values()].map((run) => ({
49519
+ finishedAt: run.finishedAt,
49520
+ goal: run.goal,
49521
+ id: run.id,
49522
+ plannedWorkerCount: run.plannedWorkerCount,
49523
+ startedAt: run.startedAt,
49524
+ status: run.status,
49525
+ workerCount: run.workers.length
49526
+ }));
49527
+ const persisted = listWorkflowSnapshots(limit);
49528
+ const seen = new Set(live.map((entry) => entry.id));
49529
+ return [...live, ...persisted.filter((entry) => !seen.has(entry.id))].sort((a, b) => (b.finishedAt ?? b.startedAt) - (a.finishedAt ?? a.startedAt)).slice(0, Math.max(1, limit));
49530
+ }
49531
+ cancel(id) {
49532
+ const run = this.runs.get(id);
49533
+ if (run) {
49534
+ this.cancelRun(run);
49535
+ return { found: true, run: this.snapshot(run) };
49536
+ }
49537
+ for (const candidate of this.runs.values()) {
49538
+ const worker = candidate.workers.find((item) => item.id === id);
49539
+ if (!worker) continue;
49540
+ candidate.activeAborts.get(worker.id)?.();
49541
+ this.updateWorker(candidate, worker.id, {
49542
+ currentStep: "cancel requested",
49543
+ status: "interrupted"
49544
+ });
49545
+ return { found: true, run: this.snapshot(candidate), workerId: worker.id };
49546
+ }
49547
+ return { found: false };
49548
+ }
49549
+ async buildPlan(goal, runId, limits) {
49550
+ if (this.planner) {
49551
+ return ensureSynthesizer(await this.planner(goal, runId, limits), goal, runId, limits);
49552
+ }
49553
+ try {
49554
+ const provider = createProvider(this.config);
49555
+ const controller = new AbortController();
49556
+ const timer = setTimeout(() => controller.abort(), Math.min(6e4, limits.workerTimeoutMs));
49557
+ try {
49558
+ const result = await provider.chat({
49559
+ systemPrompt: buildPlannerSystemPrompt(limits),
49560
+ messages: [{ role: "user", content: goal }],
49561
+ tools: [],
49562
+ signal: controller.signal
49563
+ });
49564
+ const parsed = parseModelPlan(result.text ?? "", goal, runId, limits);
49565
+ if (parsed.specs.length > 0) return parsed;
49566
+ } finally {
49567
+ clearTimeout(timer);
49568
+ }
49569
+ } catch {
49570
+ }
49571
+ return ensureSynthesizer(planWorkflow(goal, runId, limits), goal, runId, limits);
49572
+ }
49573
+ async executeRun(run, specs, sessionId) {
49574
+ const queue = [...specs];
49575
+ const active = /* @__PURE__ */ new Map();
49576
+ let stableCompletions = 0;
49577
+ try {
49578
+ while ((queue.length > 0 || active.size > 0) && !run.abortController.signal.aborted) {
49579
+ const ready = queue.filter((spec) => this.dependenciesFinished(run, spec));
49580
+ while (ready.length > 0 && active.size < run.concurrency && !run.abortController.signal.aborted) {
49581
+ const spec = ready.shift();
49582
+ queue.splice(queue.indexOf(spec), 1);
49583
+ const promise = this.executeWorker(run, spec, sessionId).then(() => {
49584
+ stableCompletions += 1;
49585
+ if (stableCompletions % Math.max(2, run.concurrency) === 0) {
49586
+ run.concurrency = adjustConcurrency(run.concurrency, run.plannedWorkerCount, run.config, "stable_completion");
49587
+ }
49588
+ }).catch((err) => {
49589
+ const message = err instanceof Error ? err.message : String(err);
49590
+ if (/rate|429|quota|throttle/i.test(message)) {
49591
+ run.concurrency = adjustConcurrency(run.concurrency, run.plannedWorkerCount, run.config, "rate_limited");
49592
+ }
49593
+ }).finally(() => active.delete(spec.id));
49594
+ active.set(spec.id, promise);
49595
+ }
49596
+ if (active.size === 0) break;
49597
+ await Promise.race(active.values());
49598
+ }
49599
+ if (run.abortController.signal.aborted) {
49600
+ for (const worker of run.workers) {
49601
+ if (worker.status === "queued" || worker.status === "running") {
49602
+ this.updateWorker(run, worker.id, { currentStep: "cancelled", status: "interrupted" });
49603
+ }
49604
+ }
49605
+ run.status = "cancelled";
49606
+ run.summary = this.summarize(run, "cancelled");
49607
+ } else if (run.workers.some((worker) => worker.status === "failed")) {
49608
+ run.status = "failed";
49609
+ run.summary = this.summarize(run, "failed");
49610
+ } else {
49611
+ run.status = "completed";
49612
+ run.summary = this.summarize(run, "completed");
49613
+ }
49614
+ } catch (err) {
49615
+ run.status = "failed";
49616
+ run.summary = `Workflow failed: ${err instanceof Error ? err.message : String(err)}`;
49617
+ } finally {
49618
+ run.finishedAt = Date.now();
49619
+ if (run.config.persistHistory) saveWorkflowSnapshot(this.snapshot(run));
49620
+ this.emitWorkflow(run.status === "failed" ? "workflow.error" : "workflow.complete", run, sessionId);
49621
+ }
49622
+ }
49623
+ async executeWorker(run, spec, sessionId) {
49624
+ if (run.abortController.signal.aborted) return;
49625
+ this.updateWorker(run, spec.id, {
49626
+ currentStep: "starting",
49627
+ startedAt: Date.now(),
49628
+ status: "running"
49629
+ });
49630
+ this.emitSubagent("subagent.start", run, this.worker(run, spec.id), sessionId);
49631
+ const controller = new AbortController();
49632
+ const timeoutMs = spec.role === "synthesizer" ? run.config.workerTimeoutMs * 3 : run.config.workerTimeoutMs;
49633
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
49634
+ run.activeAborts.set(spec.id, () => controller.abort());
49635
+ const signal = AbortSignal.any([controller.signal, run.abortController.signal]);
49636
+ try {
49637
+ const result = await this.runner({
49638
+ config: this.config,
49639
+ run: this.snapshot(run),
49640
+ signal,
49641
+ spec,
49642
+ systemPromptFn: this.systemPromptFn,
49643
+ toolRuntime: new ReadOnlyToolRuntime(this.toolRuntime),
49644
+ update: /* @__PURE__ */ __name((patch) => {
49645
+ this.updateWorker(run, spec.id, patch);
49646
+ this.emitSubagent("subagent.progress", run, this.worker(run, spec.id), sessionId);
49647
+ }, "update")
49648
+ });
49649
+ this.updateWorker(run, spec.id, {
49650
+ currentStep: "complete",
49651
+ details: result,
49652
+ durationSeconds: (Date.now() - (this.worker(run, spec.id).startedAt ?? Date.now())) / 1e3,
49653
+ status: signal.aborted ? "interrupted" : "completed",
49654
+ summary: firstParagraph(result),
49655
+ verificationState: spec.role === "verifier" ? inferVerificationState(result) : "not_applicable"
49656
+ });
49657
+ } catch (err) {
49658
+ const message = err instanceof Error ? err.message : String(err);
49659
+ this.updateWorker(run, spec.id, {
49660
+ currentStep: signal.aborted ? "interrupted" : "failed",
49661
+ details: message,
49662
+ durationSeconds: (Date.now() - (this.worker(run, spec.id).startedAt ?? Date.now())) / 1e3,
49663
+ status: signal.aborted ? "interrupted" : "failed",
49664
+ summary: message,
49665
+ verificationState: "unverified"
49666
+ });
49667
+ if (!signal.aborted) throw err;
49668
+ } finally {
49669
+ clearTimeout(timeout);
49670
+ run.activeAborts.delete(spec.id);
49671
+ this.emitSubagent("subagent.complete", run, this.worker(run, spec.id), sessionId);
49672
+ if (run.config.persistHistory) saveWorkflowSnapshot(this.snapshot(run));
49673
+ }
49674
+ }
49675
+ defaultRunner = /* @__PURE__ */ __name(async (ctx) => {
49676
+ const workerConfig = {
49677
+ ...ctx.config,
49678
+ features: { ...ctx.config.features, skill_auto_suggest: false }
49679
+ };
49680
+ const loop = new AgentLoop(workerConfig, ctx.toolRuntime);
49681
+ const sections = await ctx.systemPromptFn();
49682
+ const systemPrompt = [
49683
+ ...sections,
49684
+ "# Advisory Workflow Worker Rules",
49685
+ "You are a read-only workflow worker. Never modify files, execute shell/code, send messages, update memory, or perform browser actions that change state.",
49686
+ "Use available read/search tools only. Return concise evidence-backed findings."
49687
+ ].filter(Boolean).join("\n\n");
49688
+ ctx.signal.addEventListener("abort", () => loop.abort(), { once: true });
49689
+ let answer = "";
49690
+ let streamedText = "";
49691
+ let outputTail = [];
49692
+ let toolCount = 0;
49693
+ const userPrompt = [ctx.spec.prompt, dependencyOutputContext(ctx.run, ctx.spec)].filter(Boolean).join("\n\n");
49694
+ for await (const chunk of loop.run(userPrompt, systemPrompt)) {
49695
+ if (ctx.signal.aborted) {
49696
+ loop.abort();
49697
+ break;
49698
+ }
49699
+ if (chunk.type === "thinking" && chunk.content.trim()) {
49700
+ streamedText += chunk.content;
49701
+ ctx.update({ currentStep: chunk.content.trim().slice(0, 180), thinking: [chunk.content.trim().slice(0, 500)] });
49702
+ }
49703
+ if (chunk.type === "tool_call") {
49704
+ toolCount += 1;
49705
+ ctx.update({ currentStep: `using ${chunk.toolName ?? "tool"}`, toolCount, tools: [`${chunk.toolName ?? "tool"}(...)`] });
49706
+ }
49707
+ if (chunk.type === "tool_result") {
49708
+ outputTail = [...outputTail, { isError: /error/i.test(chunk.content), preview: chunk.content.slice(0, 240), tool: chunk.toolName ?? "tool" }].slice(-8);
49709
+ ctx.update({ outputTail });
49710
+ }
49711
+ if (chunk.type === "answer" && chunk.content.trim()) answer = chunk.content.trim();
49712
+ }
49713
+ const result = answer || streamedText.trim();
49714
+ if (ctx.signal.aborted) {
49715
+ if (result) {
49716
+ return `${result}
49717
+
49718
+ [Partial output preserved after worker interruption.]`;
49719
+ }
49720
+ throw new Error("worker interrupted");
49721
+ }
49722
+ return result || "Worker completed with no final answer.";
49723
+ }, "defaultRunner");
49724
+ workerFromSpec(spec, workflowId, taskCount, now2) {
49725
+ return {
49726
+ currentStep: "queued",
49727
+ depth: spec.depth,
49728
+ goal: spec.goal,
49729
+ id: spec.id,
49730
+ index: spec.index,
49731
+ notes: [],
49732
+ parentId: spec.parentId,
49733
+ startedAt: now2,
49734
+ status: "queued",
49735
+ taskCount,
49736
+ thinking: [],
49737
+ toolCount: 0,
49738
+ tools: [],
49739
+ verificationState: spec.role === "verifier" ? "pending" : "not_applicable",
49740
+ workflowId,
49741
+ workerRole: spec.role
49742
+ };
49743
+ }
49744
+ dependenciesFinished(run, spec) {
49745
+ return (spec.dependsOn ?? []).every((id) => {
49746
+ const status = run.workers.find((worker) => worker.id === id)?.status;
49747
+ return status === "completed" || status === "failed" || status === "interrupted";
49748
+ });
49749
+ }
49750
+ updateWorker(run, id, patch) {
49751
+ run.workers = run.workers.map((worker) => worker.id === id ? { ...worker, ...patch } : worker);
49752
+ }
49753
+ worker(run, id) {
49754
+ return run.workers.find((worker) => worker.id === id) ?? run.workers[0];
49755
+ }
49756
+ snapshot(run) {
49757
+ return {
49758
+ concurrency: run.concurrency,
49759
+ finishedAt: run.finishedAt,
49760
+ goal: run.goal,
49761
+ id: run.id,
49762
+ plannedWorkerCount: run.plannedWorkerCount,
49763
+ startedAt: run.startedAt,
49764
+ status: run.status,
49765
+ summary: run.summary,
49766
+ workers: run.workers.map((worker) => ({ ...worker }))
49767
+ };
49768
+ }
49769
+ latestRun() {
49770
+ const runs = [...this.runs.values()].sort((a, b) => b.startedAt - a.startedAt);
49771
+ return runs[0] ?? null;
49772
+ }
49773
+ cancelRun(run) {
49774
+ if (terminal.has(run.status)) return;
49775
+ run.abortController.abort();
49776
+ for (const abort of run.activeAborts.values()) abort();
49777
+ run.status = "cancelled";
49778
+ }
49779
+ summarize(run, status) {
49780
+ const synthesizer = run.workers.find((worker) => worker.workerRole === "synthesizer" && worker.details && worker.details !== "worker interrupted");
49781
+ if ((status === "completed" || status === "cancelled" || status === "failed") && synthesizer?.details) {
49782
+ return [synthesizer.details, "No files were modified by advisory workflow workers."].join("\n\n");
49783
+ }
49784
+ const counts = run.workers.reduce((acc, worker) => {
49785
+ acc[worker.status] = (acc[worker.status] ?? 0) + 1;
49786
+ return acc;
49787
+ }, {});
49788
+ const highlights = run.workers.filter((worker) => worker.summary).slice(0, 8).map((worker) => `- ${worker.goal}: ${worker.summary}`).join("\n");
49789
+ return [
49790
+ `Workflow ${status}: ${run.goal}`,
49791
+ `Workers: ${run.workers.length} planned \xB7 ${counts.completed ?? 0} completed \xB7 ${counts.failed ?? 0} failed \xB7 ${counts.interrupted ?? 0} interrupted.`,
49792
+ "No files were modified by advisory workflow workers.",
49793
+ highlights ? `
49794
+ Key worker summaries:
49795
+ ${highlights}` : ""
49796
+ ].filter(Boolean).join("\n");
49797
+ }
49798
+ emitWorkflow(type, run, sessionId) {
49799
+ this.bus.emitEvent({
49800
+ payload: this.snapshot(run),
49801
+ session_id: sessionId ?? void 0,
49802
+ type
49803
+ });
49804
+ }
49805
+ emitSubagent(type, run, worker, sessionId) {
49806
+ this.bus.emitEvent({
49807
+ payload: {
49808
+ cost_usd: worker.costUsd,
49809
+ current_step: worker.currentStep,
49810
+ depth: worker.depth,
49811
+ details: worker.details,
49812
+ duration_seconds: worker.durationSeconds,
49813
+ files_read: worker.filesRead,
49814
+ files_written: worker.filesWritten,
49815
+ goal: worker.goal,
49816
+ input_tokens: worker.inputTokens,
49817
+ output_tail: worker.outputTail?.map((entry) => ({ is_error: entry.isError, preview: entry.preview, tool: entry.tool })),
49818
+ output_tokens: worker.outputTokens,
49819
+ parent_id: worker.parentId,
49820
+ status: worker.status,
49821
+ subagent_id: worker.id,
49822
+ summary: worker.summary,
49823
+ task_count: worker.taskCount,
49824
+ task_index: worker.index,
49825
+ text: worker.currentStep ?? worker.summary,
49826
+ tool_count: worker.toolCount,
49827
+ verification_state: worker.verificationState,
49828
+ workflow_id: run.id,
49829
+ worker_role: worker.workerRole
49830
+ },
49831
+ session_id: sessionId ?? void 0,
49832
+ type
49833
+ });
49834
+ }
49835
+ };
49836
+ __name(buildPlannerSystemPrompt, "buildPlannerSystemPrompt");
49837
+ __name(parseModelPlan, "parseModelPlan");
49838
+ __name(ensureSynthesizer, "ensureSynthesizer");
49839
+ __name(buildModelWorkerPrompt, "buildModelWorkerPrompt");
49840
+ __name(normalizeRole, "normalizeRole");
49841
+ __name(extractJsonObject, "extractJsonObject");
49842
+ __name(uniqueWorkerId, "uniqueWorkerId");
49843
+ __name(scopedWorkerId, "scopedWorkerId");
49844
+ __name(firstParagraph, "firstParagraph");
49845
+ __name(inferVerificationState, "inferVerificationState");
49846
+ __name(dependencyOutputContext, "dependencyOutputContext");
49847
+ }
49848
+ });
49849
+
49850
+ // src/rpcHandlers.ts
49851
+ import { spawn as spawn6 } from "node:child_process";
49852
+ import { randomUUID as randomUUID14 } from "node:crypto";
49853
+ import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync29, rmSync, statSync as statSync4, writeFileSync as writeFileSync20 } from "node:fs";
49854
+ import { homedir as homedir29 } from "node:os";
49855
+ import { basename as basename4, extname as extname4, join as join43 } from "node:path";
48965
49856
  function registerRpcHandlers(options) {
48966
49857
  const { agentConfig, agentLoop, bridgeEmitter, bridgeManager, bus, mcpManager, systemPromptFn, toolRegistry, voiceManager } = options;
48967
49858
  let currentRun = null;
48968
49859
  const pendingResponders = /* @__PURE__ */ new Map();
48969
49860
  const promptCollector = createPromptCollector(bus, () => agentLoop.sessionId);
49861
+ const workflowManager = new WorkflowManager(agentConfig, toolRegistry, systemPromptFn, bus);
48970
49862
  const OAUTH_FLOW_TTL_MS = 15 * 60 * 1e3;
48971
49863
  const oauthFlows = /* @__PURE__ */ new Map();
48972
49864
  const cleanupOAuthFlow = /* @__PURE__ */ __name((flowId, abort) => {
@@ -48985,7 +49877,7 @@ function registerRpcHandlers(options) {
48985
49877
  const user = bridgeUser(rawEvent, source);
48986
49878
  const text = formatBridgeText(rawEvent, source, user);
48987
49879
  bus.emitEvent({
48988
- payload: { source, task_id: randomUUID13(), text, user },
49880
+ payload: { source, task_id: randomUUID14(), text, user },
48989
49881
  session_id: agentLoop.sessionId,
48990
49882
  type: "bridge.message"
48991
49883
  });
@@ -48994,7 +49886,7 @@ function registerRpcHandlers(options) {
48994
49886
  bus.registerRpc("setup.status", () => {
48995
49887
  const hasConfigKey = Boolean(agentConfig.llm.api_key && agentConfig.llm.api_key !== "proxy-token");
48996
49888
  const hasEnvKey = Boolean(
48997
- process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GITHUB_TOKEN
49889
+ process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GITHUB_COPILOT_TOKEN || process.env.GITHUB_TOKEN
48998
49890
  );
48999
49891
  const hasStoredCredential = PROVIDERS2.some((p) => {
49000
49892
  try {
@@ -49342,12 +50234,12 @@ ${helpMessage}` : field.label;
49342
50234
  const id = String(params.session_id ?? agentLoop.sessionId) || agentLoop.sessionId;
49343
50235
  const data = loadSession(id);
49344
50236
  const messages = data?.messages ?? agentLoop.history;
49345
- const exportDir = join42(homedir28(), ".openjaw-agent", "exports");
49346
- if (!existsSync31(exportDir)) {
49347
- mkdirSync17(exportDir, { recursive: true });
50237
+ const exportDir = join43(homedir29(), ".openjaw-agent", "exports");
50238
+ if (!existsSync32(exportDir)) {
50239
+ mkdirSync18(exportDir, { recursive: true });
49348
50240
  }
49349
50241
  const safeId = id.replace(/[^a-zA-Z0-9_.-]/g, "_") || agentLoop.sessionId;
49350
- const file2 = join42(exportDir, `session-${safeId}.md`);
50242
+ const file2 = join43(exportDir, `session-${safeId}.md`);
49351
50243
  const lines = [
49352
50244
  `# OpenJaw Agent Session ${id}`,
49353
50245
  `Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -49360,7 +50252,7 @@ ${helpMessage}` : field.label;
49360
50252
  for (const message of messages) {
49361
50253
  lines.push(...sessionMessageToMarkdown(message));
49362
50254
  }
49363
- writeFileSync19(file2, lines.join("\n"), "utf-8");
50255
+ writeFileSync20(file2, lines.join("\n"), "utf-8");
49364
50256
  return { file: file2, message_count: messages.length };
49365
50257
  });
49366
50258
  bus.registerRpc("session.interrupt", () => {
@@ -49377,8 +50269,8 @@ ${helpMessage}` : field.label;
49377
50269
  return { deleted: "" };
49378
50270
  }
49379
50271
  try {
49380
- const file2 = join42(homedir28(), ".openjaw-agent", "sessions", `${id}.json`);
49381
- if (existsSync31(file2)) {
50272
+ const file2 = join43(homedir29(), ".openjaw-agent", "sessions", `${id}.json`);
50273
+ if (existsSync32(file2)) {
49382
50274
  rmSync(file2);
49383
50275
  }
49384
50276
  return { deleted: id };
@@ -49451,11 +50343,11 @@ ${helpMessage}` : field.label;
49451
50343
  bus.registerRpc("shell.exec", async (params) => {
49452
50344
  const command = String(params.command ?? "");
49453
50345
  if (!command) return { code: -1, stderr: "empty command" };
49454
- return await new Promise((resolve5) => {
50346
+ return await new Promise((resolve6) => {
49455
50347
  const isWin = process.platform === "win32";
49456
50348
  const shell = isWin ? "powershell.exe" : "sh";
49457
50349
  const args = isWin ? ["-NoProfile", "-Command", command] : ["-c", command];
49458
- const child = spawn7(shell, args, { timeout: 3e4 });
50350
+ const child = spawn6(shell, args, { timeout: 3e4 });
49459
50351
  let stdout = "";
49460
50352
  let stderr = "";
49461
50353
  let truncated = false;
@@ -49478,10 +50370,10 @@ ${helpMessage}` : field.label;
49478
50370
  if (truncated) {
49479
50371
  stderr += "\n[output truncated to 1MB]";
49480
50372
  }
49481
- resolve5({ code: code ?? -1, stderr, stdout });
50373
+ resolve6({ code: code ?? -1, stderr, stdout });
49482
50374
  });
49483
50375
  child.on("error", (err) => {
49484
- resolve5({ code: -1, stderr: err.message });
50376
+ resolve6({ code: -1, stderr: err.message });
49485
50377
  });
49486
50378
  });
49487
50379
  });
@@ -49603,7 +50495,7 @@ ${helpMessage}` : field.label;
49603
50495
  return agentConfig.llm.copilot_enterprise_url;
49604
50496
  })();
49605
50497
  const flow = await startCopilotDeviceFlow(clientId, enterpriseUrl);
49606
- const flowId = randomUUID13();
50498
+ const flowId = randomUUID14();
49607
50499
  const controller = new AbortController();
49608
50500
  const timer = setTimeout(() => {
49609
50501
  cleanupOAuthFlow(flowId, true);
@@ -49767,7 +50659,7 @@ ${helpMessage}` : field.label;
49767
50659
  Object.assign(agentConfig.llm, result.configUpdates);
49768
50660
  bus.emitEvent({ payload: sessionInfoSnapshot(agentLoop, toolRegistry), session_id: agentLoop.sessionId, type: "session.info" });
49769
50661
  }
49770
- return { messages };
50662
+ return { config_updates: result.configUpdates, messages };
49771
50663
  });
49772
50664
  bus.registerRpc("provider.disconnect", (params) => {
49773
50665
  const provider = String(params.provider ?? "").trim();
@@ -49827,6 +50719,32 @@ ${helpMessage}` : field.label;
49827
50719
  tasks: forks.map((task) => ({ id: task.id, prompt: task.prompt, status: task.status }))
49828
50720
  };
49829
50721
  });
50722
+ bus.registerRpc("workflow.start", async (params) => {
50723
+ const goal = String(params.goal ?? params.prompt ?? "").trim();
50724
+ if (!goal) return { error: "usage: /workflow <goal>", ok: false };
50725
+ const result = await workflowManager.start(goal, agentLoop.sessionId);
50726
+ return { ok: true, run: result.run };
50727
+ });
50728
+ bus.registerRpc("workflow.status", (params) => {
50729
+ const id = String(params.id ?? params.run_id ?? "").trim();
50730
+ const run = workflowManager.status(id || void 0);
50731
+ return run ? { ok: true, run } : { error: id ? `workflow not found: ${id}` : "no workflows yet", ok: false };
50732
+ });
50733
+ bus.registerRpc("workflow.list", (params) => {
50734
+ const limit = Number(params.limit ?? 30);
50735
+ return { ok: true, runs: workflowManager.list(Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 30) };
50736
+ });
50737
+ bus.registerRpc("workflow.show", (params) => {
50738
+ const id = String(params.id ?? params.run_id ?? "").trim();
50739
+ const run = workflowManager.status(id || void 0);
50740
+ return run ? { ok: true, run, summary: run.summary ?? "" } : { error: id ? `workflow not found: ${id}` : "no workflows yet", ok: false };
50741
+ });
50742
+ bus.registerRpc("workflow.cancel", (params) => {
50743
+ const id = String(params.id ?? params.run_id ?? params.worker_id ?? "").trim();
50744
+ if (!id) return { error: "usage: /workflow cancel <runId|workerId>", ok: false };
50745
+ const result = workflowManager.cancel(id);
50746
+ return result.found ? { ok: true, ...result } : { error: `workflow or worker not found: ${id}`, ok: false };
50747
+ });
49830
50748
  bus.registerRpc("schedule.add", (params) => {
49831
50749
  const prompt = String(params.prompt ?? "").trim();
49832
50750
  const raw = String(params.schedule ?? "").trim();
@@ -50097,13 +51015,13 @@ ${helpMessage}` : field.label;
50097
51015
  const firstSpace = raw.search(/\s/);
50098
51016
  const path3 = firstSpace > 0 ? raw.slice(0, firstSpace) : raw;
50099
51017
  const remainder = firstSpace > 0 ? raw.slice(firstSpace).trim() : "";
50100
- if (!existsSync31(path3)) {
51018
+ if (!existsSync32(path3)) {
50101
51019
  throw new Error(`image.attach: file not found: ${path3}`);
50102
51020
  }
50103
51021
  let buffer;
50104
51022
  let fileSize = 0;
50105
51023
  try {
50106
- buffer = readFileSync28(path3);
51024
+ buffer = readFileSync29(path3);
50107
51025
  fileSize = statSync4(path3).size;
50108
51026
  } catch (err) {
50109
51027
  throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
@@ -50113,12 +51031,12 @@ ${helpMessage}` : field.label;
50113
51031
  pendingImage = {
50114
51032
  base64: buffer.toString("base64"),
50115
51033
  mimeType,
50116
- name: basename3(path3)
51034
+ name: basename4(path3)
50117
51035
  };
50118
51036
  const tokenEstimate = Math.max(1, Math.ceil(buffer.byteLength / 750));
50119
51037
  return {
50120
51038
  height: 0,
50121
- name: basename3(path3),
51039
+ name: basename4(path3),
50122
51040
  remainder,
50123
51041
  token_estimate: tokenEstimate,
50124
51042
  width: 0
@@ -50149,13 +51067,13 @@ ${helpMessage}` : field.label;
50149
51067
  }
50150
51068
  });
50151
51069
  bus.registerRpc("reload.env", () => {
50152
- const envPath = join42(homedir28(), ".openjaw-agent", ".env");
50153
- if (!existsSync31(envPath)) {
51070
+ const envPath = join43(homedir29(), ".openjaw-agent", ".env");
51071
+ if (!existsSync32(envPath)) {
50154
51072
  return { updated: 0 };
50155
51073
  }
50156
51074
  let updated = 0;
50157
51075
  try {
50158
- const raw = readFileSync28(envPath, "utf-8");
51076
+ const raw = readFileSync29(envPath, "utf-8");
50159
51077
  for (const line of raw.split(/\r?\n/)) {
50160
51078
  const trimmed = line.trim();
50161
51079
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -50195,8 +51113,25 @@ ${helpMessage}` : field.label;
50195
51113
  error: "checkpoints are not enabled in this build",
50196
51114
  success: false
50197
51115
  }));
50198
- bus.registerRpc("spawn_tree.list", () => ({ entries: [] }));
50199
- bus.registerRpc("spawn_tree.load", () => ({ subagents: [] }));
51116
+ bus.registerRpc("spawn_tree.save", (params) => ({
51117
+ path: saveSpawnTreeSnapshot({
51118
+ finished_at: typeof params.finished_at === "number" ? params.finished_at : Date.now() / 1e3,
51119
+ label: typeof params.label === "string" ? params.label : void 0,
51120
+ session_id: typeof params.session_id === "string" ? params.session_id : agentLoop.sessionId ?? "default",
51121
+ started_at: typeof params.started_at === "number" || params.started_at === null ? params.started_at : null,
51122
+ subagents: Array.isArray(params.subagents) ? params.subagents : []
51123
+ })
51124
+ }));
51125
+ bus.registerRpc("spawn_tree.list", (params) => ({
51126
+ entries: listSpawnTreeSnapshots(
51127
+ typeof params.session_id === "string" ? params.session_id : agentLoop.sessionId ?? "default",
51128
+ typeof params.limit === "number" ? params.limit : 30
51129
+ )
51130
+ }));
51131
+ bus.registerRpc("spawn_tree.load", (params) => {
51132
+ const path3 = String(params.path ?? "").trim();
51133
+ return path3 ? loadSpawnTreeSnapshot(path3) ?? { subagents: [] } : { subagents: [] };
51134
+ });
50200
51135
  bus.registerRpc("skills.reload", async () => {
50201
51136
  try {
50202
51137
  clearSkillsCache();
@@ -50280,9 +51215,19 @@ ${helpMessage}` : field.label;
50280
51215
  }
50281
51216
  return { output: name ? `unknown command: /${name}` : "(no command)", type: "exec" };
50282
51217
  });
50283
- bus.registerRpc("delegation.status", () => ({ delegated: [], paused: false }));
51218
+ bus.registerRpc("delegation.status", () => ({
51219
+ active: [],
51220
+ max_concurrent_children: agentConfig.features?.dynamic_workflows?.hard_max_concurrent_workers ?? 128,
51221
+ max_spawn_depth: agentConfig.features?.dynamic_workflows?.hard_max_workers ?? 1024,
51222
+ paused: false
51223
+ }));
50284
51224
  bus.registerRpc("delegation.pause", (params) => ({ paused: Boolean(params.paused) }));
50285
- bus.registerRpc("subagent.interrupt", () => ({ ok: true }));
51225
+ bus.registerRpc("subagent.interrupt", (params) => {
51226
+ const id = String(params.subagent_id ?? "").trim();
51227
+ if (!id) return { found: false };
51228
+ const result = workflowManager.cancel(id);
51229
+ return { found: result.found, subagent_id: id };
51230
+ });
50286
51231
  syncMcpTools(mcpManager, toolRegistry);
50287
51232
  bus.emitEvent({
50288
51233
  payload: sessionInfoSnapshot(agentLoop, toolRegistry),
@@ -50330,6 +51275,8 @@ var init_rpcHandlers = __esm({
50330
51275
  init_registry2();
50331
51276
  init_usage();
50332
51277
  init_usageSnapshot();
51278
+ init_manager();
51279
+ init_persistence();
50333
51280
  init_registry3();
50334
51281
  PROVIDERS2 = ["anthropic", "openai", "github-copilot"];
50335
51282
  PROVIDER_LABELS = {
@@ -50515,8 +51462,8 @@ var init_rpcHandlers = __esm({
50515
51462
  ${raw}`;
50516
51463
  return `${header}: ${raw}`;
50517
51464
  }, "formatBridgeText");
50518
- runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve5) => {
50519
- const child = spawn7(command, args, { timeout, windowsHide: true });
51465
+ runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve6) => {
51466
+ const child = spawn6(command, args, { timeout, windowsHide: true });
50520
51467
  let stdout = "";
50521
51468
  let stderr = "";
50522
51469
  let truncated = false;
@@ -50539,9 +51486,9 @@ ${raw}`;
50539
51486
  if (truncated) {
50540
51487
  stderr += "\n[output truncated to 1MB]";
50541
51488
  }
50542
- resolve5({ code: code ?? -1, stderr, stdout });
51489
+ resolve6({ code: code ?? -1, stderr, stdout });
50543
51490
  });
50544
- child.on("error", (err) => resolve5({ code: -1, stderr: err.message, stdout }));
51491
+ child.on("error", (err) => resolve6({ code: -1, stderr: err.message, stdout }));
50545
51492
  }), "runProcess");
50546
51493
  contentToText = /* @__PURE__ */ __name((content) => {
50547
51494
  if (typeof content === "string") return content;
@@ -50699,14 +51646,14 @@ var init_memoryMonitor = __esm({
50699
51646
  });
50700
51647
 
50701
51648
  // src/lib/openExternalUrl.ts
50702
- import { spawn as spawn8 } from "node:child_process";
51649
+ import { spawn as spawn7 } from "node:child_process";
50703
51650
  import { platform } from "node:os";
50704
51651
  function openExternalUrl(rawUrl, dependencies = {}) {
50705
51652
  const url = parseSafeUrl(rawUrl);
50706
51653
  if (!url) {
50707
51654
  return false;
50708
51655
  }
50709
- const spawnFn = dependencies.spawn ?? spawn8;
51656
+ const spawnFn = dependencies.spawn ?? spawn7;
50710
51657
  const platformId = dependencies.platform?.() ?? platform();
50711
51658
  const command = openCommand(platformId);
50712
51659
  if (!command) {
@@ -51496,7 +52443,7 @@ async function terminalParityHints(env2 = process.env, options) {
51496
52443
  hints.push({
51497
52444
  key: "remote",
51498
52445
  tone: "warn",
51499
- message: "SSH session detected \xB7 text clipboard can bridge via OSC52, but image clipboard and local screenshot paths still depend on the machine running Hermes"
52446
+ message: "SSH session detected \xB7 text clipboard can bridge via OSC52, but image clipboard and local screenshot paths still depend on the machine running OpenJaw"
51500
52447
  });
51501
52448
  }
51502
52449
  return hints;
@@ -51601,13 +52548,13 @@ var init_setup2 = __esm({
51601
52548
  SETUP_REQUIRED_TITLE = "Setup Required";
51602
52549
  buildSetupRequiredSections = /* @__PURE__ */ __name(() => [
51603
52550
  {
51604
- text: "Hermes needs a model provider before the TUI can start a session."
52551
+ text: "OpenJaw needs a model provider before the TUI can start a session. Run /connect to set up a provider, then /model to choose a model."
51605
52552
  },
51606
52553
  {
51607
52554
  rows: [
51608
- ["/model", "configure provider + model in-place"],
51609
- ["/setup", "run full first-time setup wizard in-place"],
51610
- ["Ctrl+C", "exit and run `hermes setup` manually"]
52555
+ ["/connect", "set up provider credentials in-place"],
52556
+ ["/model", "choose or switch model after connecting"],
52557
+ ["Ctrl+C", "exit and restart OpenJaw after setup if needed"]
51611
52558
  ],
51612
52559
  title: "Actions"
51613
52560
  }
@@ -51910,7 +52857,7 @@ var init_reasoning2 = __esm({
51910
52857
  });
51911
52858
 
51912
52859
  // src/app/turnStore.ts
51913
- import { atom as atom5 } from "nanostores";
52860
+ import { atom as atom6 } from "nanostores";
51914
52861
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
51915
52862
  var buildTurnState, $turnState, getTurnState, subscribeTurn, useTurnSelector, patchTurnState, toggleTodoCollapsed, archiveDoneTodos, archiveTodosAtTurnEnd, resetTurnState;
51916
52863
  var init_turnStore = __esm({
@@ -51934,7 +52881,7 @@ var init_turnStore = __esm({
51934
52881
  tools: [],
51935
52882
  turnTrail: []
51936
52883
  }), "buildTurnState");
51937
- $turnState = atom5(buildTurnState());
52884
+ $turnState = atom6(buildTurnState());
51938
52885
  getTurnState = /* @__PURE__ */ __name(() => $turnState.get(), "getTurnState");
51939
52886
  subscribeTurn = /* @__PURE__ */ __name((cb) => $turnState.listen(() => cb()), "subscribeTurn");
51940
52887
  useTurnSelector = /* @__PURE__ */ __name((selector) => useSyncExternalStore4(
@@ -52534,7 +53481,9 @@ ${stripped}
52534
53481
  ...base,
52535
53482
  apiCalls: p.api_calls ?? base.apiCalls,
52536
53483
  costUsd: p.cost_usd ?? base.costUsd,
53484
+ currentStep: p.current_step ?? base.currentStep,
52537
53485
  depth: p.depth ?? base.depth,
53486
+ details: p.details ?? base.details,
52538
53487
  filesRead: p.files_read ?? base.filesRead,
52539
53488
  filesWritten: p.files_written ?? base.filesWritten,
52540
53489
  goal: p.goal || base.goal,
@@ -52548,6 +53497,9 @@ ${stripped}
52548
53497
  taskCount: p.task_count ?? base.taskCount,
52549
53498
  toolCount: p.tool_count ?? base.toolCount,
52550
53499
  toolsets: p.toolsets ?? base.toolsets,
53500
+ verificationState: p.verification_state ?? base.verificationState,
53501
+ workflowId: p.workflow_id ?? base.workflowId,
53502
+ workerRole: p.worker_role ?? base.workerRole,
52551
53503
  ...patch(base)
52552
53504
  };
52553
53505
  const subagents = existing ? state.subagents.map((item) => item.id === id ? next : item) : [...state.subagents, next].sort((a, b) => a.depth - b.depth || a.index - b.index);
@@ -52634,7 +53586,7 @@ function createGatewayEventHandler(ctx) {
52634
53586
  setTimeout(async () => {
52635
53587
  let sid = getUiState().sid;
52636
53588
  for (let i = 0; !sid && i < 40; i += 1) {
52637
- await new Promise((resolve5) => setTimeout(resolve5, 100));
53589
+ await new Promise((resolve6) => setTimeout(resolve6, 100));
52638
53590
  sid = getUiState().sid;
52639
53591
  }
52640
53592
  if (!sid) {
@@ -52981,6 +53933,26 @@ function createGatewayEventHandler(ctx) {
52981
53933
  { createIfMissing: false }
52982
53934
  );
52983
53935
  return;
53936
+ case "workflow.start":
53937
+ case "workflow.progress":
53938
+ setWorkflowSnapshot(ev.payload);
53939
+ return;
53940
+ case "workflow.complete": {
53941
+ setWorkflowSnapshot(ev.payload);
53942
+ const text = String(ev.payload?.summary ?? "").trim();
53943
+ if (text) {
53944
+ appendMessage({ role: "assistant", text });
53945
+ }
53946
+ return;
53947
+ }
53948
+ case "workflow.error": {
53949
+ setWorkflowSnapshot(ev.payload);
53950
+ const text = String(ev.payload?.summary ?? "").trim();
53951
+ if (text) {
53952
+ sys(text);
53953
+ }
53954
+ return;
53955
+ }
52984
53956
  case "message.delta":
52985
53957
  turnController.recordMessageDelta(ev.payload ?? {});
52986
53958
  return;
@@ -53015,7 +53987,7 @@ function createGatewayEventHandler(ctx) {
53015
53987
  }
53016
53988
  };
53017
53989
  }
53018
- var NO_PROVIDER_RE, statusFromBusy, applySkin, dropBgTask, pushUnique, pushThinking, pushNote, pushTool;
53990
+ var NO_PROVIDER_RE, statusFromBusy, applySkin, dropBgTask, pushUnique2, pushThinking, pushNote, pushTool;
53019
53991
  var init_createGatewayEventHandler = __esm({
53020
53992
  "src/app/createGatewayEventHandler.ts"() {
53021
53993
  "use strict";
@@ -53030,6 +54002,7 @@ var init_createGatewayEventHandler = __esm({
53030
54002
  init_overlayStore();
53031
54003
  init_turnController();
53032
54004
  init_uiStore();
54005
+ init_workflowStore();
53033
54006
  NO_PROVIDER_RE = /\bNo (?:LLM|inference) provider configured\b/i;
53034
54007
  statusFromBusy = /* @__PURE__ */ __name(() => getUiState().busy ? "running\u2026" : "ready", "statusFromBusy");
53035
54008
  applySkin = /* @__PURE__ */ __name((s) => patchUiState({
@@ -53047,10 +54020,10 @@ var init_createGatewayEventHandler = __esm({
53047
54020
  next.delete(taskId);
53048
54021
  return { ...state, bgTasks: next };
53049
54022
  }), "dropBgTask");
53050
- pushUnique = /* @__PURE__ */ __name((max) => (xs, x) => xs.at(-1) === x ? xs : [...xs, x].slice(-max), "pushUnique");
53051
- pushThinking = pushUnique(6);
53052
- pushNote = pushUnique(6);
53053
- pushTool = pushUnique(8);
54023
+ pushUnique2 = /* @__PURE__ */ __name((max) => (xs, x) => xs.at(-1) === x ? xs : [...xs, x].slice(-max), "pushUnique");
54024
+ pushThinking = pushUnique2(6);
54025
+ pushNote = pushUnique2(6);
54026
+ pushTool = pushUnique2(8);
53054
54027
  __name(createGatewayEventHandler, "createGatewayEventHandler");
53055
54028
  }
53056
54029
  });
@@ -53194,12 +54167,12 @@ var init_createSlashHandler = __esm({
53194
54167
  });
53195
54168
 
53196
54169
  // src/app/inputSelectionStore.ts
53197
- import { atom as atom6 } from "nanostores";
54170
+ import { atom as atom7 } from "nanostores";
53198
54171
  var $inputSelection, setInputSelection, getInputSelection;
53199
54172
  var init_inputSelectionStore = __esm({
53200
54173
  "src/app/inputSelectionStore.ts"() {
53201
54174
  "use strict";
53202
- $inputSelection = atom6(null);
54175
+ $inputSelection = atom7(null);
53203
54176
  setInputSelection = /* @__PURE__ */ __name((next) => $inputSelection.set(next), "setInputSelection");
53204
54177
  getInputSelection = /* @__PURE__ */ __name(() => $inputSelection.get(), "getInputSelection");
53205
54178
  }
@@ -53360,21 +54333,21 @@ var init_useCompletion = __esm({
53360
54333
  });
53361
54334
 
53362
54335
  // src/lib/history.ts
53363
- import { appendFileSync as appendFileSync4, existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync29 } from "node:fs";
53364
- import { homedir as homedir29 } from "node:os";
53365
- import { join as join43 } from "node:path";
54336
+ import { appendFileSync as appendFileSync4, existsSync as existsSync33, mkdirSync as mkdirSync19, readFileSync as readFileSync30 } from "node:fs";
54337
+ import { homedir as homedir30 } from "node:os";
54338
+ import { join as join44 } from "node:path";
53366
54339
  function load() {
53367
54340
  if (cache3) {
53368
54341
  return cache3;
53369
54342
  }
53370
54343
  try {
53371
- if (!existsSync32(file)) {
54344
+ if (!existsSync33(file)) {
53372
54345
  cache3 = [];
53373
54346
  return cache3;
53374
54347
  }
53375
54348
  const entries = [];
53376
54349
  let current = [];
53377
- for (const line of readFileSync29(file, "utf8").split("\n")) {
54350
+ for (const line of readFileSync30(file, "utf8").split("\n")) {
53378
54351
  if (line.startsWith("+")) {
53379
54352
  current.push(line.slice(1));
53380
54353
  } else if (current.length) {
@@ -53405,8 +54378,8 @@ function append(line) {
53405
54378
  items.splice(0, items.length - MAX);
53406
54379
  }
53407
54380
  try {
53408
- if (!existsSync32(dir)) {
53409
- mkdirSync18(dir, { recursive: true });
54381
+ if (!existsSync33(dir)) {
54382
+ mkdirSync19(dir, { recursive: true });
53410
54383
  }
53411
54384
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
53412
54385
  const encoded = trimmed.split("\n").map((l) => `+${l}`).join("\n");
@@ -53422,8 +54395,8 @@ var init_history = __esm({
53422
54395
  "src/lib/history.ts"() {
53423
54396
  "use strict";
53424
54397
  MAX = 1e3;
53425
- dir = process.env.OPENJAW_HOME ?? join43(homedir29(), ".openjaw-agent");
53426
- file = join43(dir, ".openjaw-agent_history");
54398
+ dir = process.env.OPENJAW_HOME ?? join44(homedir30(), ".openjaw-agent");
54399
+ file = join44(dir, ".openjaw-agent_history");
53427
54400
  cache3 = null;
53428
54401
  __name(load, "load");
53429
54402
  __name(append, "append");
@@ -53517,7 +54490,7 @@ var init_useQueue = __esm({
53517
54490
 
53518
54491
  // src/lib/editor.ts
53519
54492
  import { accessSync, constants } from "node:fs";
53520
- import { delimiter, join as join44 } from "node:path";
54493
+ import { delimiter, join as join45 } from "node:path";
53521
54494
  var FALLBACKS, isExecutable, resolveEditor;
53522
54495
  var init_editor = __esm({
53523
54496
  "src/lib/editor.ts"() {
@@ -53540,7 +54513,7 @@ var init_editor = __esm({
53540
54513
  return ["notepad.exe"];
53541
54514
  }
53542
54515
  const dirs = (env2.PATH ?? "").split(delimiter).filter(Boolean);
53543
- const found = FALLBACKS.flatMap((name) => dirs.map((d) => join44(d, name))).find(isExecutable);
54516
+ const found = FALLBACKS.flatMap((name) => dirs.map((d) => join45(d, name))).find(isExecutable);
53544
54517
  return [found ?? "vi"];
53545
54518
  }, "resolveEditor");
53546
54519
  }
@@ -53548,9 +54521,9 @@ var init_editor = __esm({
53548
54521
 
53549
54522
  // src/app/useComposerState.ts
53550
54523
  import { spawnSync } from "node:child_process";
53551
- import { mkdtempSync, readFileSync as readFileSync30, rmSync as rmSync2, writeFileSync as writeFileSync20 } from "node:fs";
54524
+ import { mkdtempSync, readFileSync as readFileSync31, rmSync as rmSync2, writeFileSync as writeFileSync21 } from "node:fs";
53552
54525
  import { tmpdir as tmpdir12 } from "node:os";
53553
- import { join as join45 } from "node:path";
54526
+ import { join as join46 } from "node:path";
53554
54527
  import { useStore } from "@nanostores/react";
53555
54528
  import { useCallback as useCallback7, useMemo as useMemo6, useState as useState12 } from "react";
53556
54529
  function insertAtCursor(value, cursor, text) {
@@ -53707,10 +54680,10 @@ function useComposerState({
53707
54680
  [handleResolvedPaste, onClipboardPaste, querier]
53708
54681
  );
53709
54682
  const openEditor = useCallback7(async () => {
53710
- const dir2 = mkdtempSync(join45(tmpdir12(), "hermes-"));
53711
- const file2 = join45(dir2, "prompt.md");
54683
+ const dir2 = mkdtempSync(join46(tmpdir12(), "hermes-"));
54684
+ const file2 = join46(dir2, "prompt.md");
53712
54685
  const [cmd, ...args] = resolveEditor();
53713
- writeFileSync20(file2, [...inputBuf, input].join("\n"));
54686
+ writeFileSync21(file2, [...inputBuf, input].join("\n"));
53714
54687
  let exitCode = null;
53715
54688
  await withInkSuspended(async () => {
53716
54689
  exitCode = spawnSync(cmd, [...args, file2], { stdio: "inherit" }).status;
@@ -53719,7 +54692,7 @@ function useComposerState({
53719
54692
  if (exitCode !== 0) {
53720
54693
  return;
53721
54694
  }
53722
- const text = readFileSync30(file2, "utf8").trimEnd();
54695
+ const text = readFileSync31(file2, "utf8").trimEnd();
53723
54696
  if (!text) {
53724
54697
  return;
53725
54698
  }
@@ -54137,11 +55110,11 @@ function applyVoiceRecordResponse(response, starting, voice, sys) {
54137
55110
  }
54138
55111
  }
54139
55112
  function useInputHandlers(ctx) {
54140
- const { actions, composer, gateway, terminal, voice, wheelStep } = ctx;
55113
+ const { actions, composer, gateway, terminal: terminal2, voice, wheelStep } = ctx;
54141
55114
  const { actions: cActions, refs: cRefs, state: cState } = composer;
54142
55115
  const overlay = useStore2($overlayState);
54143
55116
  const isBlocked = useStore2($isBlocked);
54144
- const pagerPageSize = Math.max(5, (terminal.stdout?.rows ?? 24) - 6);
55117
+ const pagerPageSize = Math.max(5, (terminal2.stdout?.rows ?? 24) - 6);
54145
55118
  const scrollIdleTimer = useRef10(null);
54146
55119
  const wheelAccelRef = useRef10(initWheelAccelForHost());
54147
55120
  const precisionWheelRef = useRef10(initPrecisionWheel());
@@ -54156,13 +55129,13 @@ function useInputHandlers(ctx) {
54156
55129
  turnController.relaxStreaming();
54157
55130
  }, TYPING_IDLE_MS);
54158
55131
  }
54159
- terminal.scrollWithSelection(delta);
55132
+ terminal2.scrollWithSelection(delta);
54160
55133
  }, "scrollTranscript");
54161
55134
  const copySelection = /* @__PURE__ */ __name(() => {
54162
- terminal.selection.copySelection();
55135
+ terminal2.selection.copySelection();
54163
55136
  }, "copySelection");
54164
55137
  const clearSelection2 = /* @__PURE__ */ __name(() => {
54165
- terminal.selection.clearSelection();
55138
+ terminal2.selection.clearSelection();
54166
55139
  }, "clearSelection");
54167
55140
  const cancelOverlayFromCtrlC = /* @__PURE__ */ __name(() => {
54168
55141
  if (overlay.clarify) {
@@ -54361,7 +55334,7 @@ function useInputHandlers(ctx) {
54361
55334
  return scrollTranscript(1);
54362
55335
  }
54363
55336
  if (key.pageUp || key.pageDown) {
54364
- const viewport = terminal.scrollRef.current?.getViewportHeight() ?? Math.max(6, (terminal.stdout?.rows ?? 24) - 8);
55337
+ const viewport = terminal2.scrollRef.current?.getViewportHeight() ?? Math.max(6, (terminal2.stdout?.rows ?? 24) - 8);
54365
55338
  const step = Math.max(4, Math.floor(viewport / 2));
54366
55339
  return scrollTranscript(key.pageUp ? -step : step);
54367
55340
  }
@@ -54371,7 +55344,7 @@ function useInputHandlers(ctx) {
54371
55344
  if (key.escape && cState.queueEditIdx !== null) {
54372
55345
  return cActions.clearIn();
54373
55346
  }
54374
- if (key.escape && terminal.hasSelection) {
55347
+ if (key.escape && terminal2.hasSelection) {
54375
55348
  return clearSelection2();
54376
55349
  }
54377
55350
  if (key.escape && live.focusedPane === "transcript") {
@@ -54397,7 +55370,7 @@ function useInputHandlers(ctx) {
54397
55370
  }
54398
55371
  }
54399
55372
  if (isCopyShortcut(key, ch)) {
54400
- if (terminal.hasSelection) {
55373
+ if (terminal2.hasSelection) {
54401
55374
  return copySelection();
54402
55375
  }
54403
55376
  const inputSel = getInputSelection();
@@ -54432,7 +55405,7 @@ function useInputHandlers(ctx) {
54432
55405
  }
54433
55406
  if (isAction(key, ch, "l")) {
54434
55407
  clearSelection2();
54435
- forceRedraw(terminal.stdout ?? process.stdout);
55408
+ forceRedraw(terminal2.stdout ?? process.stdout);
54436
55409
  return;
54437
55410
  }
54438
55411
  if (isVoiceToggleKey(key, ch, voice.recordKey)) {
@@ -54582,7 +55555,7 @@ var init_useLongRunToolCharms = __esm({
54582
55555
  });
54583
55556
 
54584
55557
  // src/app/useSessionLifecycle.ts
54585
- import { writeFileSync as writeFileSync21 } from "node:fs";
55558
+ import { writeFileSync as writeFileSync22 } from "node:fs";
54586
55559
  import { useCallback as useCallback8 } from "react";
54587
55560
  function useSessionLifecycle(opts) {
54588
55561
  const {
@@ -54765,7 +55738,7 @@ var init_useSessionLifecycle = __esm({
54765
55738
  return;
54766
55739
  }
54767
55740
  try {
54768
- writeFileSync21(file2, JSON.stringify({ session_id: sessionId }), { mode: 384 });
55741
+ writeFileSync22(file2, JSON.stringify({ session_id: sessionId }), { mode: 384 });
54769
55742
  } catch {
54770
55743
  }
54771
55744
  }, "writeActiveSessionFile");
@@ -55622,8 +56595,13 @@ function useMainApp(gw) {
55622
56595
  );
55623
56596
  const onModelSelect = useCallback10((value) => {
55624
56597
  patchOverlayState({ modelPicker: false });
56598
+ if (value === "__openjaw_connect_complete__") {
56599
+ sys("provider connected \u2014 starting OpenJaw session\u2026");
56600
+ session.newSession();
56601
+ return;
56602
+ }
55625
56603
  slashRef.current(`/model ${value}`);
55626
- }, []);
56604
+ }, [session, sys]);
55627
56605
  const hasReasoning = useTurnSelector((state) => Boolean(state.reasoning.trim()));
55628
56606
  const anyPanelVisible = SECTION_NAMES.some(
55629
56607
  (s) => sectionMode(s, ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== "hidden"
@@ -55795,9 +56773,9 @@ __export(perfPane_exports, {
55795
56773
  PerfPane: () => PerfPane,
55796
56774
  logFrameEvent: () => logFrameEvent
55797
56775
  });
55798
- import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync19 } from "node:fs";
55799
- import { homedir as homedir30 } from "node:os";
55800
- import { dirname as dirname7, join as join46 } from "node:path";
56776
+ import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync20 } from "node:fs";
56777
+ import { homedir as homedir31 } from "node:os";
56778
+ import { dirname as dirname7, join as join47 } from "node:path";
55801
56779
  import { Profiler } from "react";
55802
56780
  import { jsx as jsx17 } from "react/jsx-runtime";
55803
56781
  function PerfPane({ children, id }) {
@@ -55813,13 +56791,13 @@ var init_perfPane = __esm({
55813
56791
  init_entry_exports();
55814
56792
  ENABLED = /^(?:1|true|yes|on)$/i.test((process.env.OPENJAW_DEV_PERF ?? "").trim());
55815
56793
  THRESHOLD_MS = Number(process.env.OPENJAW_DEV_PERF_MS ?? "2") || 0;
55816
- LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() || join46(homedir30(), ".openjaw-agent", "perf.log");
56794
+ LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() || join47(homedir31(), ".openjaw-agent", "perf.log");
55817
56795
  logReady = false;
55818
56796
  writeRow = /* @__PURE__ */ __name((row) => {
55819
56797
  if (!logReady) {
55820
56798
  logReady = true;
55821
56799
  try {
55822
- mkdirSync19(dirname7(LOG_PATH2), { recursive: true });
56800
+ mkdirSync20(dirname7(LOG_PATH2), { recursive: true });
55823
56801
  } catch {
55824
56802
  }
55825
56803
  }
@@ -56072,6 +57050,10 @@ function Detail({ id, node, t }) {
56072
57050
  ] }),
56073
57051
  /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", marginTop: 1, children: [
56074
57052
  /* @__PURE__ */ jsx18(Field, { name: "depth", t, value: `${item.depth} \xB7 ${item.status}` }),
57053
+ item.workflowId ? /* @__PURE__ */ jsx18(Field, { name: "workflow", t, value: item.workflowId }) : null,
57054
+ item.workerRole ? /* @__PURE__ */ jsx18(Field, { name: "role", t, value: item.workerRole }) : null,
57055
+ item.verificationState ? /* @__PURE__ */ jsx18(Field, { name: "verification", t, value: item.verificationState }) : null,
57056
+ item.currentStep ? /* @__PURE__ */ jsx18(Field, { name: "current", t, value: item.currentStep }) : null,
56075
57057
  item.model ? /* @__PURE__ */ jsx18(Field, { name: "model", t, value: item.model }) : null,
56076
57058
  item.toolsets?.length ? /* @__PURE__ */ jsx18(Field, { name: "toolsets", t, value: item.toolsets.join(", ") }) : null,
56077
57059
  /* @__PURE__ */ jsx18(Field, { name: "tools", t, value: `${item.toolCount ?? 0} (subtree ${agg.totalTools})` }),
@@ -56146,7 +57128,8 @@ function Detail({ id, node, t }) {
56146
57128
  " ",
56147
57129
  line
56148
57130
  ] }, i)) }) : null,
56149
- item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Summary", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.summary }) }) : null
57131
+ item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Summary", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.summary }) }) : null,
57132
+ item.details && item.details !== item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Details", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.details }) }) : null
56150
57133
  ] });
56151
57134
  }
56152
57135
  function ListRow({
@@ -56246,12 +57229,23 @@ function DiffView({
56246
57229
  ] })
56247
57230
  ] });
56248
57231
  }
56249
- function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56250
- const liveSubagents = useTurnSelector((state) => state.subagents);
57232
+ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t, workflowId = null }) {
57233
+ const allLiveSubagents = useTurnSelector((state) => state.subagents);
56251
57234
  const delegation = useStore4($delegationState);
56252
57235
  const history = useStore4($spawnHistory);
56253
57236
  const diffPair = useStore4($spawnDiff);
57237
+ const workflowSnapshots = useStore4($workflowSnapshots);
56254
57238
  const { stdout } = useStdout();
57239
+ const liveSubagents = workflowId ? allLiveSubagents.filter((item) => item.workflowId === workflowId) : allLiveSubagents;
57240
+ const workflowSnapshot = workflowId ? workflowSnapshots[workflowId] ?? null : null;
57241
+ const workflowReplaySnapshot = workflowSnapshot && liveSubagents.length === 0 ? {
57242
+ finishedAt: workflowSnapshot.finishedAt ?? Date.now(),
57243
+ id: workflowSnapshot.id,
57244
+ label: workflowSnapshot.goal,
57245
+ sessionId: workflowSnapshot.id,
57246
+ startedAt: workflowSnapshot.startedAt,
57247
+ subagents: workflowSnapshot.workers
57248
+ } : null;
56255
57249
  const [historyIndex, setHistoryIndex] = useState14(
56256
57250
  () => Math.max(0, Math.min(history.length, Math.floor(initialHistoryIndex)))
56257
57251
  );
@@ -56263,9 +57257,9 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56263
57257
  const [mode, setMode] = useState14("list");
56264
57258
  const detailScrollRef = useRef14(null);
56265
57259
  const prevLiveCountRef = useRef14(liveSubagents.length);
56266
- const activeSnapshot = historyIndex > 0 ? history[historyIndex - 1] : null;
56267
- const justFinishedSnapshot = historyIndex === 0 && liveSubagents.length === 0 ? history[0] ?? null : null;
56268
- const effectiveSnapshot = activeSnapshot ?? justFinishedSnapshot;
57260
+ const activeSnapshot = !workflowId && historyIndex > 0 ? history[historyIndex - 1] : null;
57261
+ const justFinishedSnapshot = !workflowId && historyIndex === 0 && liveSubagents.length === 0 ? history[0] ?? null : null;
57262
+ const effectiveSnapshot = workflowReplaySnapshot ?? activeSnapshot ?? justFinishedSnapshot;
56269
57263
  const replayMode = effectiveSnapshot != null;
56270
57264
  const subagents = replayMode ? effectiveSnapshot.subagents : liveSubagents;
56271
57265
  const tree = useMemo8(() => buildSubagentTree(subagents), [subagents]);
@@ -56434,7 +57428,7 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56434
57428
  const capsLabel = delegation.maxSpawnDepth ? `caps d${delegation.maxSpawnDepth}/${delegation.maxConcurrentChildren ?? "?"}` : "";
56435
57429
  const title = replayMode && effectiveSnapshot ? `${historyIndex > 0 ? `Replay ${historyIndex}/${history.length}` : "Last turn"} \xB7 finished ${new Date(
56436
57430
  effectiveSnapshot.finishedAt
56437
- ).toLocaleTimeString()}` : `Spawn tree${delegation.paused ? " \xB7 \u23F8 paused" : ""}`;
57431
+ ).toLocaleTimeString()}` : workflowId ? `Workflow ${workflowId}${delegation.paused ? " \xB7 \u23F8 paused" : ""}` : `Spawn tree${delegation.paused ? " \xB7 \u23F8 paused" : ""}`;
56438
57432
  const metaLine = [formatSummary(totals), spark, capsLabel, mix2 ? `\xB7 ${mix2}` : ""].filter(Boolean).join(" ");
56439
57433
  const controlsHint = replayMode ? " \xB7 controls locked" : ` \xB7 x kill \xB7 X subtree \xB7 p ${delegation.paused ? "resume" : "pause"}`;
56440
57434
  if (diffPair) {
@@ -56448,7 +57442,7 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56448
57442
  metaLine
56449
57443
  ] }) : null
56450
57444
  ] }) }),
56451
- rows.length === 0 ? /* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx18(Text9, { color: t.color.muted, children: "No subagents this turn. Trigger delegate_task to populate the tree." }) }) : mode === "list" ? /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
57445
+ rows.length === 0 ? /* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx18(Text9, { color: t.color.muted, children: workflowId ? "No workers for this workflow yet." : "No subagents this turn. Trigger delegate_task to populate the tree." }) }) : mode === "list" ? /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
56452
57446
  /* @__PURE__ */ jsx18(GanttStrip, { cols, cursor, flatNodes: rows, maxRows: 6, now: now2, t }),
56453
57447
  /* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 0, flexShrink: 0, overflow: "hidden", children: rows.slice(listWindowStart, listWindowStart + rowsH).map((node, i) => /* @__PURE__ */ jsx18(
56454
57448
  ListRow,
@@ -56494,6 +57488,7 @@ var init_agentsOverlay = __esm({
56494
57488
  init_overlayStore();
56495
57489
  init_spawnHistoryStore();
56496
57490
  init_turnStore();
57491
+ init_workflowStore();
56497
57492
  init_rpc();
56498
57493
  init_subagentTree();
56499
57494
  init_text();
@@ -58422,7 +59417,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58422
59417
  refreshProviders();
58423
59418
  }
58424
59419
  if (mode === "connect") {
58425
- onCancel();
59420
+ onSelect("__openjaw_connect_complete__");
58426
59421
  return;
58427
59422
  }
58428
59423
  setStage("model");
@@ -58518,7 +59513,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58518
59513
  setKeyInput("");
58519
59514
  setKeySaving(false);
58520
59515
  if (mode === "connect") {
58521
- onCancel();
59516
+ onSelect("__openjaw_connect_complete__");
58522
59517
  return;
58523
59518
  }
58524
59519
  setStage("model");
@@ -58557,7 +59552,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58557
59552
  if (r?.disconnected) {
58558
59553
  setProviders(
58559
59554
  (prev) => prev.map(
58560
- (p) => p.slug === provider.slug ? { ...p, authenticated: false, models: [], model_options: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : "run `hermes model` to configure" } : p
59555
+ (p) => p.slug === provider.slug ? { ...p, authenticated: false, models: [], model_options: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : "run /connect to configure" } : p
58561
59556
  )
58562
59557
  );
58563
59558
  }
@@ -58605,7 +59600,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58605
59600
  return;
58606
59601
  }
58607
59602
  if (mode === "connect") {
58608
- onCancel();
59603
+ onSelect("__openjaw_connect_complete__");
58609
59604
  return;
58610
59605
  }
58611
59606
  setStage("model");
@@ -58693,7 +59688,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58693
59688
  "Configure ",
58694
59689
  provider.name
58695
59690
  ] }),
58696
- /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to ~/.hermes/.env)" }),
59691
+ /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to OpenJaw credentials)" }),
58697
59692
  /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: " " }),
58698
59693
  /* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
58699
59694
  provider.key_env,
@@ -59889,14 +60884,14 @@ __export(fpsStore_exports, {
59889
60884
  $fpsState: () => $fpsState,
59890
60885
  trackFrame: () => trackFrame
59891
60886
  });
59892
- import { atom as atom7 } from "nanostores";
60887
+ import { atom as atom8 } from "nanostores";
59893
60888
  var WINDOW_SIZE, $fpsState, timestamps, totalFrames, trackFrame;
59894
60889
  var init_fpsStore = __esm({
59895
60890
  "src/lib/fpsStore.ts"() {
59896
60891
  "use strict";
59897
60892
  init_env();
59898
60893
  WINDOW_SIZE = 30;
59899
- $fpsState = atom7({ fps: 0, lastDurationMs: 0, totalFrames: 0 });
60894
+ $fpsState = atom8({ fps: 0, lastDurationMs: 0, totalFrames: 0 });
59900
60895
  timestamps = [];
59901
60896
  totalFrames = 0;
59902
60897
  trackFrame = SHOW_FPS ? (durationMs) => {
@@ -61060,7 +62055,7 @@ var init_mathUnicode = __esm({
61060
62055
 
61061
62056
  // src/lib/syntax.ts
61062
62057
  function highlightLine(line, lang, t) {
61063
- const spec = resolve4(lang);
62058
+ const spec = resolve5(lang);
61064
62059
  if (!spec) {
61065
62060
  return [["", line]];
61066
62061
  }
@@ -61092,7 +62087,7 @@ function highlightLine(line, lang, t) {
61092
62087
  }
61093
62088
  return tokens;
61094
62089
  }
61095
- var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve4, isHighlightable, TOKEN_RE;
62090
+ var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve5, isHighlightable, TOKEN_RE;
61096
62091
  var init_syntax = __esm({
61097
62092
  "src/lib/syntax.ts"() {
61098
62093
  "use strict";
@@ -61146,8 +62141,8 @@ var init_syntax = __esm({
61146
62141
  yml: "yaml",
61147
62142
  zsh: "sh"
61148
62143
  };
61149
- resolve4 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
61150
- isHighlightable = /* @__PURE__ */ __name((lang) => resolve4(lang) !== null, "isHighlightable");
62144
+ resolve5 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
62145
+ isHighlightable = /* @__PURE__ */ __name((lang) => resolve5(lang) !== null, "isHighlightable");
61151
62146
  TOKEN_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|\b\d+(?:\.\d+)?\b|[A-Za-z_$][\w$]*/g;
61152
62147
  __name(highlightLine, "highlightLine");
61153
62148
  }
@@ -63271,8 +64266,9 @@ var init_appLayout = __esm({
63271
64266
  {
63272
64267
  gw,
63273
64268
  initialHistoryIndex: overlay.agentsInitialHistoryIndex,
63274
- onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0 }),
63275
- t: ui.theme
64269
+ onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0, agentsWorkflowId: null }),
64270
+ t: ui.theme,
64271
+ workflowId: overlay.agentsWorkflowId
63276
64272
  }
63277
64273
  );
63278
64274
  }, "AgentsOverlayPane"));