@charzhu/openjaw-agent 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -131,13 +131,18 @@ function loadAgentConfig() {
131
131
  copilot_oauth_client_id: parsedLlm?.copilot_oauth_client_id ?? DEFAULT_CONFIG.llm.copilot_oauth_client_id,
132
132
  context_compression: parsedLlm?.context_compression,
133
133
  compression_threshold: parsedLlm?.compression_threshold,
134
- compression_model: parsedLlm?.compression_model
134
+ compression_model: parsedLlm?.compression_model,
135
+ max_tool_rounds: parsedLlm?.max_tool_rounds
135
136
  },
136
137
  telegram: parsed?.telegram ?? void 0,
137
138
  feishu: parsed?.feishu ?? void 0,
138
139
  wechat: parsed?.wechat ?? void 0,
139
140
  features: {
140
- skill_auto_suggest: parsed?.features?.skill_auto_suggest ?? true
141
+ skill_auto_suggest: parsed?.features?.skill_auto_suggest ?? true,
142
+ dynamic_workflows: {
143
+ ...DEFAULT_DYNAMIC_WORKFLOWS,
144
+ ...parsed?.features?.dynamic_workflows ?? {}
145
+ }
141
146
  }
142
147
  };
143
148
  if (config.llm.provider === "anthropic") {
@@ -199,12 +204,20 @@ function updateBridgeConfig(name, values) {
199
204
  );
200
205
  return next;
201
206
  }
202
- var DEFAULT_COPILOT_OAUTH_CLIENT_ID, DEFAULT_CONFIG, configWriteChain;
207
+ var DEFAULT_COPILOT_OAUTH_CLIENT_ID, DEFAULT_DYNAMIC_WORKFLOWS, DEFAULT_CONFIG, configWriteChain;
203
208
  var init_config = __esm({
204
209
  "src/config.ts"() {
205
210
  "use strict";
206
211
  init_packageRoot();
207
212
  DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98";
213
+ DEFAULT_DYNAMIC_WORKFLOWS = {
214
+ enabled: true,
215
+ planner_mode: "adaptive",
216
+ hard_max_workers: 1024,
217
+ hard_max_concurrent_workers: 128,
218
+ worker_timeout_ms: 18e4,
219
+ persist_history: true
220
+ };
208
221
  DEFAULT_CONFIG = {
209
222
  llm: {
210
223
  provider: "anthropic",
@@ -856,7 +869,7 @@ Start-Sleep -Milliseconds 50
856
869
  }
857
870
  }
858
871
  async function wait(duration) {
859
- await new Promise((resolve5) => setTimeout(resolve5, duration * 1e3));
872
+ await new Promise((resolve6) => setTimeout(resolve6, duration * 1e3));
860
873
  return { output: `Waited ${duration} seconds` };
861
874
  }
862
875
  function getDisplayDimensions() {
@@ -2551,7 +2564,7 @@ var init_copilot = __esm({
2551
2564
  handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
2552
2565
  });
2553
2566
  const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
2554
- return new Promise((resolve5, reject) => {
2567
+ return new Promise((resolve6, reject) => {
2555
2568
  const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
2556
2569
  let settled = false;
2557
2570
  const timeout = setTimeout(() => {
@@ -2565,7 +2578,7 @@ var init_copilot = __esm({
2565
2578
  settled = true;
2566
2579
  clearTimeout(timeout);
2567
2580
  ws.close();
2568
- resolve5(value);
2581
+ resolve6(value);
2569
2582
  }, "finish");
2570
2583
  const fail = /* @__PURE__ */ __name((error) => {
2571
2584
  if (settled) return;
@@ -2629,7 +2642,8 @@ var init_copilot = __esm({
2629
2642
  messages.push({
2630
2643
  role: "assistant",
2631
2644
  content: msg.content,
2632
- ...toolCalls?.length ? { tool_calls: toolCalls } : {}
2645
+ ...toolCalls?.length ? { tool_calls: toolCalls } : {},
2646
+ ...msg.reasoningOpaque ? { reasoning_opaque: msg.reasoningOpaque } : {}
2633
2647
  });
2634
2648
  } else {
2635
2649
  for (const result of msg.results) {
@@ -2659,11 +2673,12 @@ var init_copilot = __esm({
2659
2673
  messages: this.buildChatMessages(options),
2660
2674
  tools: options.tools.length > 0 ? options.tools.map(toChatTool) : void 0,
2661
2675
  tool_choice: options.tools.length > 0 ? "auto" : void 0,
2662
- temperature: this.config.temperature
2676
+ temperature: this.config.temperature,
2677
+ stream: true
2663
2678
  };
2664
2679
  const res = await fetch(`${await this.baseUrl(options.signal)}/chat/completions`, {
2665
2680
  method: "POST",
2666
- headers: await this.headers(options),
2681
+ headers: { ...await this.headers(options), Accept: "text/event-stream" },
2667
2682
  body: JSON.stringify(requestBody),
2668
2683
  signal: options.signal
2669
2684
  });
@@ -2671,19 +2686,85 @@ var init_copilot = __esm({
2671
2686
  const detail = await res.text();
2672
2687
  throw new Error(`GitHub Copilot chat error: ${res.status} ${detail}`);
2673
2688
  }
2674
- const data = await res.json();
2675
- const choice = data.choices?.[0];
2676
- if (!choice?.message) throw new Error("No response from GitHub Copilot");
2677
- const toolCalls = (choice.message.tool_calls ?? []).map((tc) => ({
2678
- id: tc.id,
2679
- name: tc.function.name,
2680
- input: safeJsonParse(tc.function.arguments)
2681
- }));
2689
+ if (!res.body) throw new Error("GitHub Copilot chat: no response body for stream");
2690
+ let text = null;
2691
+ let finishReason;
2692
+ let reasoningOpaque;
2693
+ let promptTokens;
2694
+ let completionTokens;
2695
+ const toolSlots = [];
2696
+ const lastSlotByIndex = /* @__PURE__ */ new Map();
2697
+ const reader = res.body.getReader();
2698
+ const decoder = new TextDecoder();
2699
+ let buf = "";
2700
+ for (; ; ) {
2701
+ const { done, value } = await reader.read();
2702
+ if (done) break;
2703
+ buf += decoder.decode(value, { stream: true });
2704
+ let nl;
2705
+ while ((nl = buf.indexOf("\n")) !== -1) {
2706
+ const rawLine = buf.slice(0, nl).trim();
2707
+ buf = buf.slice(nl + 1);
2708
+ if (!rawLine.startsWith("data:")) continue;
2709
+ const payload = rawLine.slice(5).trim();
2710
+ if (payload === "[DONE]") continue;
2711
+ let evt;
2712
+ try {
2713
+ evt = JSON.parse(payload);
2714
+ } catch {
2715
+ continue;
2716
+ }
2717
+ const choice = evt.choices?.[0];
2718
+ if (choice) {
2719
+ const delta = choice.delta;
2720
+ if (delta) {
2721
+ if (typeof delta.content === "string") text = (text ?? "") + delta.content;
2722
+ if (typeof delta.reasoning_opaque === "string") {
2723
+ reasoningOpaque = (reasoningOpaque ?? "") + delta.reasoning_opaque;
2724
+ }
2725
+ const deltaToolCalls = delta.tool_calls;
2726
+ for (const tc of deltaToolCalls ?? []) {
2727
+ const idx = typeof tc.index === "number" ? tc.index : 0;
2728
+ const fn = tc.function;
2729
+ const startsNewCall = typeof tc.id === "string" || typeof fn?.name === "string";
2730
+ let slot;
2731
+ if (startsNewCall) {
2732
+ slot = { args: "" };
2733
+ if (typeof tc.id === "string") slot.id = tc.id;
2734
+ if (fn?.name) slot.name = fn.name;
2735
+ toolSlots.push(slot);
2736
+ lastSlotByIndex.set(idx, slot);
2737
+ } else {
2738
+ slot = lastSlotByIndex.get(idx) ?? { args: "" };
2739
+ if (!lastSlotByIndex.has(idx)) {
2740
+ toolSlots.push(slot);
2741
+ lastSlotByIndex.set(idx, slot);
2742
+ }
2743
+ }
2744
+ if (typeof fn?.arguments === "string") slot.args += fn.arguments;
2745
+ }
2746
+ }
2747
+ if (typeof choice.finish_reason === "string") finishReason = choice.finish_reason;
2748
+ if (typeof choice.reasoning_opaque === "string") {
2749
+ reasoningOpaque = choice.reasoning_opaque;
2750
+ }
2751
+ const msg = choice.message;
2752
+ if (typeof msg?.reasoning_opaque === "string") reasoningOpaque = msg.reasoning_opaque;
2753
+ }
2754
+ const usage2 = evt.usage;
2755
+ if (usage2) {
2756
+ promptTokens = usage2.prompt_tokens;
2757
+ completionTokens = usage2.completion_tokens;
2758
+ }
2759
+ }
2760
+ }
2761
+ const toolCalls = toolSlots.filter((slot) => slot.id && slot.name).map((slot) => ({ id: slot.id, name: slot.name, input: safeJsonParse(slot.args) }));
2682
2762
  return {
2683
- text: choice.message.content,
2763
+ text,
2684
2764
  toolCalls,
2685
- stopReason: toolCalls.length > 0 ? "tool_use" : choice.finish_reason === "length" ? "max_tokens" : "end",
2686
- usage: this.usage(data.usage?.prompt_tokens, data.usage?.completion_tokens)
2765
+ stopReason: toolCalls.length > 0 ? "tool_use" : finishReason === "length" ? "max_tokens" : "end",
2766
+ usage: this.usage(promptTokens, completionTokens),
2767
+ reasoningOpaque
2687
2768
  };
2688
2769
  }
2689
2770
  buildResponsesInput(options) {
@@ -3951,13 +4032,13 @@ function selectToolsForRequest(params) {
3951
4032
  addByName(name);
3952
4033
  }
3953
4034
  const relevantCategories = categoriesForMessage(userMessage);
3954
- for (const tool of allTools) {
3955
- if (selected.size >= maxTools) break;
4035
+ const relevantTools = allTools.map((tool, index) => ({ index, score: toolRelevanceScore(tool.name, userMessage), tool })).filter(({ tool }) => {
3956
4036
  const cat = categoryForTool(tool.name);
3957
- if (cat === "mcp") continue;
3958
- if (relevantCategories.has(cat)) {
3959
- selected.set(tool.name, tool);
3960
- }
4037
+ return cat !== "mcp" && relevantCategories.has(cat) && !selected.has(tool.name);
4038
+ }).sort((a, b) => b.score - a.score || a.index - b.index);
4039
+ for (const { tool } of relevantTools) {
4040
+ if (selected.size >= maxTools) break;
4041
+ selected.set(tool.name, tool);
3961
4042
  }
3962
4043
  if (selected.size < maxTools && relevantCategories.size === 0) {
3963
4044
  for (const tool of allTools) {
@@ -3988,6 +4069,20 @@ function categoriesForMessage(message) {
3988
4069
  }
3989
4070
  return categories;
3990
4071
  }
4072
+ function preloadRelevantCategoriesForRequest(loader, message) {
4073
+ if (typeof loader.loadCategory !== "function") return [];
4074
+ const loaded = [];
4075
+ for (const category of categoriesForMessage(message)) {
4076
+ if (category === "mcp" || category === "meta" || category === "skill") continue;
4077
+ const added = loader.loadCategory(category);
4078
+ if (added > 0) loaded.push(category);
4079
+ }
4080
+ return loaded;
4081
+ }
4082
+ function toolOutputLooksFailed(output) {
4083
+ if (!output) return false;
4084
+ return /^\s*\{?"?(error|success)"?\s*:\s*("|false)/i.test(output) || /\b(error|not found|enoent|failed)\b/i.test(output.slice(0, 300));
4085
+ }
3991
4086
  function normalizeCategory(value) {
3992
4087
  const normalized = value.toLowerCase();
3993
4088
  if (["browser", "email", "teams", "office", "wechat", "memory", "files", "system"].includes(normalized)) {
@@ -4008,6 +4103,20 @@ function categoryForTool(toolName) {
4008
4103
  if (toolName.startsWith("system_") || toolName.startsWith("clipboard_") || ["code_execute", "web_fetch", "web_search", "web_extract", "notify", "sleep", "ask_user", "config"].includes(toolName)) return "system";
4009
4104
  return "mcp";
4010
4105
  }
4106
+ function toolRelevanceScore(toolName, message) {
4107
+ const lower = message.toLowerCase();
4108
+ const name = toolName.toLowerCase();
4109
+ if (/https?:\/\//i.test(message) && ["web_extract", "web_fetch", "web_search"].includes(name)) return 150;
4110
+ if (/(?:^|[\s"'`])(?:~|\.\.?|[A-Za-z]:)?[\\/][^\s"'`]+|\b[\w.-]+\.(html?|md|txt|json|ya?ml|ts|tsx|js|jsx|py|csv|xlsx?|pptx?|pdf)\b/i.test(message) && ["file_read", "file_info", "file_list"].includes(name)) return 145;
4111
+ if (/\b(repo|repository|source code|github|codebase)\b/.test(lower) && ["system_run", "code_execute", "grep", "glob", "web_extract", "web_fetch", "file_read"].includes(name)) return 140;
4112
+ if (/https?:\/\//i.test(message) && ["browser_navigate", "browser_extract", "browser_snapshot"].includes(name)) return 135;
4113
+ if (/\b(powerpoint|pptx?|presentation|slides?|deck)\b/.test(lower) && ["powerpoint_focus", "powerpoint_new_presentation", "powerpoint_new_slide", "powerpoint_read_content", "powerpoint_send_keys", "powerpoint_screenshot"].includes(name)) return 130;
4114
+ if (/\b(powerpoint|pptx?|presentation|slides?|deck)\b/.test(lower) && name.startsWith("powerpoint_")) return 85;
4115
+ if (/\b(excel|spreadsheet|xlsx?|csv|data analysis)\b/.test(lower) && ["excel_focus", "excel_new_workbook", "excel_read_content", "excel_enter_value", "excel_enter_formula"].includes(name)) return 95;
4116
+ if (/\b(word|docx?|document|report)\b/.test(lower) && ["word_focus", "word_new_document", "word_read_content", "word_insert_text", "word_save"].includes(name)) return 95;
4117
+ if (name === "openjaw_load_tools" || name === "invoke_skill") return 90;
4118
+ return 0;
4119
+ }
4011
4120
  var DEFAULT_OPENAI_MAX_TOOLS, MCP_AUTO_GROW_HARD_CAP, BUILTIN_HEADROOM, FOUNDATION_TOOL_NAMES, PROFILE_CATEGORIES, CATEGORY_KEYWORDS;
4012
4121
  var init_tool_exposure = __esm({
4013
4122
  "src/tool-exposure.ts"() {
@@ -4031,10 +4140,10 @@ var init_tool_exposure = __esm({
4031
4140
  CATEGORY_KEYWORDS = [
4032
4141
  { category: "email", patterns: [/\b(email|mail|outlook|inbox|calendar|schedule|meeting|invite|today|tomorrow)\b/i] },
4033
4142
  { category: "teams", patterns: [/\b(teams|chat|channel|message|dm|meeting|standup|today|mention)\b/i] },
4034
- { category: "browser", patterns: [/\b(browser|page|website|web|navigate|click|screenshot|snapshot|console|image|search online)\b/i] },
4035
- { category: "files", patterns: [/\b(file|folder|directory|read|write|edit|grep|glob|find in repo|codebase)\b/i] },
4143
+ { category: "browser", patterns: [/\b(browser|page|website|web|navigate|click|screenshot|snapshot|console|image|search online)\b/i, /https?:\/\//i] },
4144
+ { category: "files", patterns: [/\b(file|folder|directory|read|write|edit|grep|glob|find in repo|codebase|source code|repo|repository|downloads?)\b/i, /(?:^|[\s"'`])(?:~|\.\.?|[A-Za-z]:)?[\\/][^\s"'`]+/i, /\b[\w.-]+\.(html?|md|txt|json|ya?ml|ts|tsx|js|jsx|py|csv|xlsx?|pptx?|pdf)\b/i] },
4036
4145
  { category: "system", patterns: [/\b(shell|command|terminal|run|execute|clipboard|notify|sleep|web search|fetch url|extract url|read url|article|docs?|paper|source page|news|latest|headlines|current events|breaking news)\b/i] },
4037
- { category: "office", patterns: [/\b(word|excel|powerpoint|spreadsheet|document|presentation|slide)\b/i] },
4146
+ { category: "office", patterns: [/\b(word|excel|powerpoint|pptx?|spreadsheet|document|presentation|slide|deck)\b/i] },
4038
4147
  { category: "wechat", patterns: [/\b(wechat|weixin)\b/i] },
4039
4148
  { category: "memory", patterns: [/\b(memory|remember|recall|todo|preference)\b/i] }
4040
4149
  ];
@@ -4046,8 +4155,245 @@ var init_tool_exposure = __esm({
4046
4155
  __name(rememberLoadedToolExposure, "rememberLoadedToolExposure");
4047
4156
  __name(selectToolsForRequest, "selectToolsForRequest");
4048
4157
  __name(categoriesForMessage, "categoriesForMessage");
4158
+ __name(preloadRelevantCategoriesForRequest, "preloadRelevantCategoriesForRequest");
4159
+ __name(toolOutputLooksFailed, "toolOutputLooksFailed");
4049
4160
  __name(normalizeCategory, "normalizeCategory");
4050
4161
  __name(categoryForTool, "categoryForTool");
4162
+ __name(toolRelevanceScore, "toolRelevanceScore");
4163
+ }
4164
+ });
4165
+
4166
+ // src/turn-control.ts
4167
+ var DEFAULT_MAX_TOOL_ROUNDS, IterationBudget;
4168
+ var init_turn_control = __esm({
4169
+ "src/turn-control.ts"() {
4170
+ "use strict";
4171
+ DEFAULT_MAX_TOOL_ROUNDS = 100;
4172
+ IterationBudget = class {
4173
+ static {
4174
+ __name(this, "IterationBudget");
4175
+ }
4176
+ used = 0;
4177
+ graceUsed = false;
4178
+ max;
4179
+ constructor(max = DEFAULT_MAX_TOOL_ROUNDS) {
4180
+ this.max = Math.max(1, Math.floor(max));
4181
+ }
4182
+ get remaining() {
4183
+ return Math.max(0, this.max - this.used);
4184
+ }
4185
+ get consumed() {
4186
+ return this.used;
4187
+ }
4188
+ /** True while there is budget (or the one-time grace round) left to run. */
4189
+ canContinue() {
4190
+ return this.remaining > 0 || !this.graceUsed;
4191
+ }
4192
+ /** True only when the budget is spent and we are on the grace round. */
4193
+ isGraceRound() {
4194
+ return this.remaining === 0 && !this.graceUsed;
4195
+ }
4196
+ /** Consume one round. Returns false if nothing (not even grace) is left. */
4197
+ consume() {
4198
+ if (this.remaining > 0) {
4199
+ this.used += 1;
4200
+ return true;
4201
+ }
4202
+ if (!this.graceUsed) {
4203
+ this.graceUsed = true;
4204
+ return true;
4205
+ }
4206
+ return false;
4207
+ }
4208
+ /** Give a round back (e.g. a round that made no model-visible progress). */
4209
+ refund() {
4210
+ if (this.used > 0) this.used -= 1;
4211
+ }
4212
+ };
4213
+ }
4214
+ });
4215
+
4216
+ // src/tool-guardrails.ts
4217
+ function isNoProgressTracked(toolName) {
4218
+ if (MUTATING_TOOLS.has(toolName)) return false;
4219
+ if (toolName.startsWith("powerpoint_") || toolName.startsWith("word_") || toolName.startsWith("excel_")) {
4220
+ return false;
4221
+ }
4222
+ return true;
4223
+ }
4224
+ function signatureOf(toolName, args) {
4225
+ return `${toolName}\0${canonicalJson(args ?? {})}`;
4226
+ }
4227
+ function canonicalJson(value) {
4228
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
4229
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
4230
+ const obj = value;
4231
+ const keys = Object.keys(obj).sort();
4232
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
4233
+ }
4234
+ function hashOutput(output) {
4235
+ let h = 2166136261;
4236
+ const s = output ?? "";
4237
+ for (let i = 0; i < s.length; i++) {
4238
+ h ^= s.charCodeAt(i);
4239
+ h = Math.imul(h, 16777619);
4240
+ }
4241
+ return (h >>> 0).toString(16);
4242
+ }
4243
+ function appendGuardrailGuidance(result, decision) {
4244
+ if (decision.action !== "warn" && decision.action !== "halt" || !decision.message) return result;
4245
+ const label = decision.action === "halt" ? "Tool loop hard stop" : "Tool loop warning";
4246
+ return `${result || ""}
4247
+
4248
+ [${label}: ${decision.code}; count=${decision.count}; ${decision.message}]`;
4249
+ }
4250
+ function blockedToolResult(decision) {
4251
+ return JSON.stringify({ error: decision.message, guardrail: { code: decision.code, action: decision.action } });
4252
+ }
4253
+ var DEFAULT_GUARDRAIL_THRESHOLDS, MUTATING_TOOLS, ToolLoopGuardrails;
4254
+ var init_tool_guardrails = __esm({
4255
+ "src/tool-guardrails.ts"() {
4256
+ "use strict";
4257
+ DEFAULT_GUARDRAIL_THRESHOLDS = {
4258
+ hardStopEnabled: true,
4259
+ exactFailureWarnAfter: 2,
4260
+ exactFailureBlockAfter: 5,
4261
+ sameToolFailureWarnAfter: 3,
4262
+ sameToolFailureHaltAfter: 8,
4263
+ noProgressWarnAfter: 2,
4264
+ noProgressBlockAfter: 5
4265
+ };
4266
+ MUTATING_TOOLS = /* @__PURE__ */ new Set([
4267
+ "file_write",
4268
+ "file_edit",
4269
+ "file_delete",
4270
+ "notify",
4271
+ "ask_user",
4272
+ "memory_append",
4273
+ "memory_save"
4274
+ ]);
4275
+ __name(isNoProgressTracked, "isNoProgressTracked");
4276
+ __name(signatureOf, "signatureOf");
4277
+ __name(canonicalJson, "canonicalJson");
4278
+ __name(hashOutput, "hashOutput");
4279
+ ToolLoopGuardrails = class {
4280
+ static {
4281
+ __name(this, "ToolLoopGuardrails");
4282
+ }
4283
+ t;
4284
+ exactFailure = /* @__PURE__ */ new Map();
4285
+ sameToolFailure = /* @__PURE__ */ new Map();
4286
+ noProgress = /* @__PURE__ */ new Map();
4287
+ haltDecision = null;
4288
+ constructor(thresholds = {}) {
4289
+ this.t = { ...DEFAULT_GUARDRAIL_THRESHOLDS, ...thresholds };
4290
+ }
4291
+ /** Set once a block/halt has fired; the loop reads this to stop the turn. */
4292
+ get halted() {
4293
+ return this.haltDecision;
4294
+ }
4295
+ /**
4296
+ * Consult before (re-)executing a tool call. Returns `block` if this exact
4297
+ * call has already failed/no-progressed past the hard-stop threshold, so the
4298
+ * loop can skip execution and tell the model to change strategy.
4299
+ */
4300
+ before(toolName, args) {
4301
+ if (!this.t.hardStopEnabled) return { action: "allow", toolName };
4302
+ const sig = signatureOf(toolName, args);
4303
+ const exact = this.exactFailure.get(sig) ?? 0;
4304
+ if (exact >= this.t.exactFailureBlockAfter) {
4305
+ return this.recordHalt({
4306
+ action: "block",
4307
+ code: "repeated_exact_failure_block",
4308
+ message: `Blocked ${toolName}: the same tool call failed ${exact} times with identical arguments. Stop retrying it unchanged; change strategy or explain the blocker.`,
4309
+ toolName,
4310
+ count: exact
4311
+ });
4312
+ }
4313
+ if (isNoProgressTracked(toolName)) {
4314
+ const rec = this.noProgress.get(sig);
4315
+ if (rec && rec.count >= this.t.noProgressBlockAfter) {
4316
+ return this.recordHalt({
4317
+ action: "block",
4318
+ code: "idempotent_no_progress_block",
4319
+ message: `Blocked ${toolName}: this call returned the same result ${rec.count} times. Stop repeating it unchanged; use the result already provided or try a different approach.`,
4320
+ toolName,
4321
+ count: rec.count
4322
+ });
4323
+ }
4324
+ }
4325
+ return { action: "allow", toolName };
4326
+ }
4327
+ /**
4328
+ * Record the outcome after a tool call. Returns a `warn` to append to the
4329
+ * tool result, or a `halt` to stop the turn. `failed` is derived by the
4330
+ * caller from the output shape (reuse tool-exposure's toolOutputLooksFailed).
4331
+ */
4332
+ after(toolName, args, output, failed) {
4333
+ const sig = signatureOf(toolName, args);
4334
+ if (failed) {
4335
+ const exact = (this.exactFailure.get(sig) ?? 0) + 1;
4336
+ this.exactFailure.set(sig, exact);
4337
+ this.noProgress.delete(sig);
4338
+ const same = (this.sameToolFailure.get(toolName) ?? 0) + 1;
4339
+ this.sameToolFailure.set(toolName, same);
4340
+ if (this.t.hardStopEnabled && same >= this.t.sameToolFailureHaltAfter) {
4341
+ return this.recordHalt({
4342
+ action: "halt",
4343
+ code: "same_tool_failure_halt",
4344
+ message: `Stopped ${toolName}: it failed ${same} times this turn. Stop retrying the same failing tool path and choose a different approach.`,
4345
+ toolName,
4346
+ count: same
4347
+ });
4348
+ }
4349
+ if (exact >= this.t.exactFailureWarnAfter) {
4350
+ return {
4351
+ action: "warn",
4352
+ code: "repeated_exact_failure_warning",
4353
+ message: `${toolName} has failed ${exact} times with identical arguments. This looks like a loop; inspect the error and change strategy instead of retrying it unchanged.`,
4354
+ toolName,
4355
+ count: exact
4356
+ };
4357
+ }
4358
+ if (same >= this.t.sameToolFailureWarnAfter) {
4359
+ return {
4360
+ action: "warn",
4361
+ code: "same_tool_failure_warning",
4362
+ message: `${toolName} has failed ${same} times this turn. This looks like a loop. Do not switch to text-only replies; keep using tools, but inspect the latest error and verify your assumptions before retrying.`,
4363
+ toolName,
4364
+ count: same
4365
+ };
4366
+ }
4367
+ return { action: "allow", toolName, count: exact };
4368
+ }
4369
+ this.exactFailure.delete(sig);
4370
+ this.sameToolFailure.delete(toolName);
4371
+ if (!isNoProgressTracked(toolName)) {
4372
+ this.noProgress.delete(sig);
4373
+ return { action: "allow", toolName };
4374
+ }
4375
+ const hash3 = hashOutput(output);
4376
+ const prev = this.noProgress.get(sig);
4377
+ const count = prev && prev.hash === hash3 ? prev.count + 1 : 1;
4378
+ this.noProgress.set(sig, { hash: hash3, count });
4379
+ if (count >= this.t.noProgressWarnAfter) {
4380
+ return {
4381
+ action: "warn",
4382
+ code: "idempotent_no_progress_warning",
4383
+ message: `${toolName} returned the same result ${count} times. Use the result already provided or change the approach instead of repeating it unchanged.`,
4384
+ toolName,
4385
+ count
4386
+ };
4387
+ }
4388
+ return { action: "allow", toolName, count };
4389
+ }
4390
+ recordHalt(decision) {
4391
+ this.haltDecision = decision;
4392
+ return decision;
4393
+ }
4394
+ };
4395
+ __name(appendGuardrailGuidance, "appendGuardrailGuidance");
4396
+ __name(blockedToolResult, "blockedToolResult");
4051
4397
  }
4052
4398
  });
4053
4399
 
@@ -4600,6 +4946,8 @@ var init_agent_loop = __esm({
4600
4946
  init_telemetry();
4601
4947
  init_context_compressor();
4602
4948
  init_tool_exposure();
4949
+ init_turn_control();
4950
+ init_tool_guardrails();
4603
4951
  init_provider_auth();
4604
4952
  init_connect();
4605
4953
  AgentLoop = class {
@@ -5120,15 +5468,25 @@ ${summary}
5120
5468
  const MAX_SAME_ACTIONS = 3;
5121
5469
  let previousCacheReadTokens = 0;
5122
5470
  let maxTokensContinuations = 0;
5471
+ const budget = new IterationBudget(this.config.llm.max_tool_rounds ?? DEFAULT_MAX_TOOL_ROUNDS);
5472
+ const guardrails = new ToolLoopGuardrails();
5123
5473
  for (let step = 0; ; step++) {
5124
5474
  if (signal.aborted) {
5125
5475
  yield { type: "answer", content: "[Interrupted by user]" };
5126
5476
  return;
5127
5477
  }
5478
+ const forceFinalSummary = budget.isGraceRound();
5479
+ if (!budget.consume()) {
5480
+ const reason = "max_iterations";
5481
+ yield { type: "answer", content: `[Reached the maximum of ${budget.max} tool rounds. Stopping.]`, exitReason: reason };
5482
+ return;
5483
+ }
5128
5484
  let responseText = null;
5129
5485
  const responseToolCalls = [];
5130
5486
  let responseStopReason = "end";
5487
+ let responseReasoningOpaque;
5131
5488
  let responseUsage;
5489
+ preloadRelevantCategoriesForRequest(this.toolRegistry, userMessage);
5132
5490
  const allTools = this.toolRegistry.listTools();
5133
5491
  const exposure = selectToolsForRequest({
5134
5492
  config: this.config,
@@ -5136,7 +5494,13 @@ ${summary}
5136
5494
  userMessage,
5137
5495
  state: this._toolExposureState
5138
5496
  });
5139
- const tools = exposure.tools;
5497
+ const tools = forceFinalSummary ? [] : exposure.tools;
5498
+ if (forceFinalSummary) {
5499
+ messages.push({
5500
+ role: "user",
5501
+ content: "[System: You've reached the maximum number of tool-calling rounds. Do not call any more tools. Summarize what you accomplished, what is still incomplete, and any blocker, as your final answer.]"
5502
+ });
5503
+ }
5140
5504
  const chatOptions = {
5141
5505
  systemPrompt,
5142
5506
  messages,
@@ -5219,6 +5583,7 @@ ${summary}
5219
5583
  responseToolCalls.push(...response.toolCalls);
5220
5584
  responseStopReason = response.stopReason;
5221
5585
  responseUsage = response.usage;
5586
+ responseReasoningOpaque = response.reasoningOpaque;
5222
5587
  }
5223
5588
  } catch (apiError) {
5224
5589
  const errMsg = apiError instanceof Error ? apiError.message : String(apiError);
@@ -5345,10 +5710,11 @@ ${summary}
5345
5710
  this.conversationHistory.push({ role: "assistant", content: responseText });
5346
5711
  this.session.messages = this.conversationHistory;
5347
5712
  saveSession(this.session);
5713
+ const exitReason = forceFinalSummary ? "max_iterations" : "completed";
5348
5714
  if (!this.provider.chatStream) {
5349
- yield { type: "answer", content: responseText ?? "" };
5715
+ yield { type: "answer", content: responseText ?? "", exitReason };
5350
5716
  } else {
5351
- yield { type: "answer", content: "" };
5717
+ yield { type: "answer", content: "", exitReason };
5352
5718
  }
5353
5719
  if (this._toolRoundsInRun >= 1 || responseText && responseText.length > 200) {
5354
5720
  void this.postTurnMemorySave(systemPrompt, messages, tools, signal);
@@ -5380,7 +5746,8 @@ ${summary}
5380
5746
  messages.push({
5381
5747
  role: "assistant",
5382
5748
  content: responseText,
5383
- toolCalls: validToolCalls
5749
+ toolCalls: validToolCalls,
5750
+ reasoningOpaque: responseReasoningOpaque
5384
5751
  });
5385
5752
  const computerCalls = validToolCalls.filter((tc) => tc.name === "computer");
5386
5753
  const otherCalls = validToolCalls.filter((tc) => tc.name !== "computer");
@@ -5388,6 +5755,10 @@ ${summary}
5388
5755
  if (signal.aborted) {
5389
5756
  return { id: tc.id, name: tc.name, output: "[Interrupted]", imageData: void 0 };
5390
5757
  }
5758
+ const pre = guardrails.before(tc.name, tc.input);
5759
+ if (pre.action === "block") {
5760
+ return { id: tc.id, name: tc.name, output: blockedToolResult(pre), imageData: void 0 };
5761
+ }
5391
5762
  let output;
5392
5763
  let imageData2;
5393
5764
  try {
@@ -5444,7 +5815,12 @@ ${summary}
5444
5815
  }
5445
5816
  } catch {
5446
5817
  }
5447
- const outputStr = typeof output === "string" ? output : JSON.stringify(output ?? { error: "No output" });
5818
+ let outputStr = typeof output === "string" ? output : JSON.stringify(output ?? { error: "No output" });
5819
+ const failed = toolOutputLooksFailed(outputStr);
5820
+ const verdict = guardrails.after(tc.name, tc.input, outputStr, failed);
5821
+ if (verdict.action === "warn" || verdict.action === "halt") {
5822
+ outputStr = appendGuardrailGuidance(outputStr, verdict);
5823
+ }
5448
5824
  return { id: tc.id, name: tc.name, output: outputStr, imageData: imageData2 };
5449
5825
  }, "executeOne");
5450
5826
  const results = [];
@@ -5472,18 +5848,18 @@ ${summary}
5472
5848
  content: parsed.question,
5473
5849
  choices: parsed.choices ?? void 0
5474
5850
  };
5475
- const userResponse = await new Promise((resolve5) => {
5851
+ const userResponse = await new Promise((resolve6) => {
5476
5852
  if (this._pendingAskUserResponse !== null) {
5477
5853
  const buffered = this._pendingAskUserResponse;
5478
5854
  this._pendingAskUserResponse = null;
5479
- resolve5(buffered);
5855
+ resolve6(buffered);
5480
5856
  return;
5481
5857
  }
5482
- this._askUserResolver = resolve5;
5858
+ this._askUserResolver = resolve6;
5483
5859
  setTimeout(() => {
5484
- if (this._askUserResolver === resolve5) {
5860
+ if (this._askUserResolver === resolve6) {
5485
5861
  this._askUserResolver = null;
5486
- resolve5("[No response from user \u2014 timed out after 5 minutes]");
5862
+ resolve6("[No response from user \u2014 timed out after 5 minutes]");
5487
5863
  }
5488
5864
  }, 5 * 60 * 1e3);
5489
5865
  });
@@ -5504,6 +5880,15 @@ ${summary}
5504
5880
  this.conversationHistory = [...messages];
5505
5881
  this.session.messages = this.conversationHistory;
5506
5882
  saveSession(this.session);
5883
+ if (guardrails.halted) {
5884
+ const reason = "guardrail_halt";
5885
+ yield {
5886
+ type: "answer",
5887
+ content: `[Stopped: ${guardrails.halted.message ?? "tool loop detected"}]`,
5888
+ exitReason: reason
5889
+ };
5890
+ return;
5891
+ }
5507
5892
  if (this._toolRoundsInRun > 0 && this._toolRoundsInRun % 5 === 0) {
5508
5893
  messages.push({
5509
5894
  role: "user",
@@ -6146,7 +6531,7 @@ var init_browser = __esm({
6146
6531
  await Page.domContentEventFired();
6147
6532
  } else if (options.waitFor === "networkidle") {
6148
6533
  await Page.loadEventFired();
6149
- await new Promise((resolve5) => setTimeout(resolve5, 1e3));
6534
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
6150
6535
  }
6151
6536
  const result = await Runtime.evaluate({
6152
6537
  expression: "document.title"
@@ -6917,7 +7302,7 @@ var init_browser = __esm({
6917
7302
  if (exists) {
6918
7303
  return true;
6919
7304
  }
6920
- await new Promise((resolve5) => setTimeout(resolve5, 200));
7305
+ await new Promise((resolve6) => setTimeout(resolve6, 200));
6921
7306
  }
6922
7307
  return false;
6923
7308
  }
@@ -7145,10 +7530,10 @@ function createBrowseTools(config, sharedBrowser) {
7145
7530
  }
7146
7531
  },
7147
7532
  execute: /* @__PURE__ */ __name(async (input) => {
7148
- const { join: join47 } = await import("node:path");
7533
+ const { join: join48 } = await import("node:path");
7149
7534
  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`);
7535
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
7536
+ const screenshotPath = join48(tmpdir13(), `openjaw-browser-${randomUUID15().slice(0, 8)}.png`);
7152
7537
  const screenshot = await browser.screenshot({ fullPage: false, path: screenshotPath });
7153
7538
  const snapshot = await browser.snapshot({ full: false });
7154
7539
  return {
@@ -7843,7 +8228,7 @@ var init_outlook_desktop = __esm({
7843
8228
  }
7844
8229
  }
7845
8230
  sleep(ms) {
7846
- return new Promise((resolve5) => setTimeout(resolve5, ms));
8231
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
7847
8232
  }
7848
8233
  };
7849
8234
  }
@@ -8441,7 +8826,7 @@ var init_outlook_web = __esm({
8441
8826
  await this.browser.typeChars(text);
8442
8827
  }
8443
8828
  sleep(ms) {
8444
- return new Promise((resolve5) => setTimeout(resolve5, ms));
8829
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
8445
8830
  }
8446
8831
  };
8447
8832
  }
@@ -8675,8 +9060,8 @@ var init_token_pool = __esm({
8675
9060
  if (!existsSync8(legacyDir))
8676
9061
  return;
8677
9062
  try {
8678
- const { readdirSync: readdirSync7 } = __require("node:fs");
8679
- const files = readdirSync7(legacyDir);
9063
+ const { readdirSync: readdirSync8 } = __require("node:fs");
9064
+ const files = readdirSync8(legacyDir);
8680
9065
  for (const file2 of files) {
8681
9066
  if (!file2.endsWith(".json"))
8682
9067
  continue;
@@ -8856,7 +9241,7 @@ var init_cdp_token_extractor = __esm({
8856
9241
  }
8857
9242
  logger_default.info("CDP: reloading tab for token refresh", { url: targetTab.url, audience });
8858
9243
  await this.reloadTab(targetTab);
8859
- await new Promise((resolve5) => setTimeout(resolve5, PAGE_RELOAD_WAIT_MS));
9244
+ await new Promise((resolve6) => setTimeout(resolve6, PAGE_RELOAD_WAIT_MS));
8860
9245
  if (!targetTab.webSocketDebuggerUrl)
8861
9246
  return [];
8862
9247
  const freshPages = await this.listPages();
@@ -8973,19 +9358,19 @@ var init_cdp_token_extractor = __esm({
8973
9358
  * Evaluate a JS expression in a tab and return the string result.
8974
9359
  */
8975
9360
  async evaluateInTab(wsUrl, expression) {
8976
- return new Promise((resolve5) => {
9361
+ return new Promise((resolve6) => {
8977
9362
  let ws;
8978
9363
  try {
8979
9364
  ws = new WebSocket(wsUrl);
8980
9365
  } catch (err) {
8981
9366
  logger_default.warn("CDP: WebSocket constructor failed", { wsUrl: wsUrl.substring(0, 60), error: String(err) });
8982
- resolve5(null);
9367
+ resolve6(null);
8983
9368
  return;
8984
9369
  }
8985
9370
  const timer = setTimeout(() => {
8986
9371
  logger_default.warn("CDP: evaluateInTab timeout", { wsUrl: wsUrl.substring(0, 60) });
8987
9372
  ws.close();
8988
- resolve5(null);
9373
+ resolve6(null);
8989
9374
  }, CDP_TIMEOUT_MS);
8990
9375
  ws.on("open", () => {
8991
9376
  ws.send(JSON.stringify({
@@ -8999,18 +9384,18 @@ var init_cdp_token_extractor = __esm({
8999
9384
  if (resp.id === 1) {
9000
9385
  clearTimeout(timer);
9001
9386
  ws.close();
9002
- resolve5(resp.result?.result?.value ?? null);
9387
+ resolve6(resp.result?.result?.value ?? null);
9003
9388
  }
9004
9389
  });
9005
9390
  ws.on("error", (err) => {
9006
9391
  logger_default.warn("CDP: evaluateInTab WS error", { error: String(err), wsUrl: wsUrl.substring(0, 60) });
9007
9392
  clearTimeout(timer);
9008
- resolve5(null);
9393
+ resolve6(null);
9009
9394
  });
9010
9395
  });
9011
9396
  }
9012
9397
  async extractFromTab(wsUrl) {
9013
- return new Promise((resolve5, reject) => {
9398
+ return new Promise((resolve6, reject) => {
9014
9399
  const ws = new WebSocket(wsUrl);
9015
9400
  const timer = setTimeout(() => {
9016
9401
  ws.close();
@@ -9030,14 +9415,14 @@ var init_cdp_token_extractor = __esm({
9030
9415
  ws.close();
9031
9416
  const value = resp.result?.result?.value;
9032
9417
  if (!value) {
9033
- resolve5([]);
9418
+ resolve6([]);
9034
9419
  return;
9035
9420
  }
9036
9421
  try {
9037
9422
  const tokens = JSON.parse(value);
9038
- resolve5(tokens);
9423
+ resolve6(tokens);
9039
9424
  } catch {
9040
- resolve5([]);
9425
+ resolve6([]);
9041
9426
  }
9042
9427
  }
9043
9428
  });
@@ -9053,11 +9438,11 @@ var init_cdp_token_extractor = __esm({
9053
9438
  async reloadTab(page) {
9054
9439
  if (!page.webSocketDebuggerUrl)
9055
9440
  return;
9056
- return new Promise((resolve5, reject) => {
9441
+ return new Promise((resolve6, reject) => {
9057
9442
  const ws = new WebSocket(page.webSocketDebuggerUrl);
9058
9443
  const timer = setTimeout(() => {
9059
9444
  ws.close();
9060
- resolve5();
9445
+ resolve6();
9061
9446
  }, 1e4);
9062
9447
  ws.on("open", () => {
9063
9448
  ws.send(JSON.stringify({
@@ -9071,7 +9456,7 @@ var init_cdp_token_extractor = __esm({
9071
9456
  if (resp.id === 1) {
9072
9457
  clearTimeout(timer);
9073
9458
  ws.close();
9074
- resolve5();
9459
+ resolve6();
9075
9460
  }
9076
9461
  });
9077
9462
  ws.on("error", (err) => {
@@ -10732,13 +11117,13 @@ function createMemoryTools(config) {
10732
11117
  const todos = input.todos;
10733
11118
  try {
10734
11119
  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))
11120
+ const { existsSync: existsSync34 } = await import("node:fs");
11121
+ const { join: join48 } = await import("node:path");
11122
+ const { homedir: homedir32 } = await import("node:os");
11123
+ const memoryDir = join48(homedir32(), ".openjaw", "memory");
11124
+ if (!existsSync34(memoryDir))
10740
11125
  await mkdir5(memoryDir, { recursive: true });
10741
- const todoPath = join47(memoryDir, "TODOS.md");
11126
+ const todoPath = join48(memoryDir, "TODOS.md");
10742
11127
  const { writeFile: writeFile5 } = await import("node:fs/promises");
10743
11128
  await writeFile5(todoPath, `# Session Todos
10744
11129
 
@@ -12059,7 +12444,7 @@ var init_teams_desktop = __esm({
12059
12444
  }
12060
12445
  }
12061
12446
  sleep(ms) {
12062
- return new Promise((resolve5) => setTimeout(resolve5, ms));
12447
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
12063
12448
  }
12064
12449
  /**
12065
12450
  * Get the current Teams window state
@@ -12926,7 +13311,7 @@ var init_teams_web = __esm({
12926
13311
  }
12927
13312
  }
12928
13313
  sleep(ms) {
12929
- return new Promise((resolve5) => setTimeout(resolve5, ms));
13314
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
12930
13315
  }
12931
13316
  };
12932
13317
  }
@@ -14324,7 +14709,7 @@ var init_teams_chat_monitor = __esm({
14324
14709
  return this.sentMessages.has(normalized);
14325
14710
  }
14326
14711
  sleep(ms) {
14327
- return new Promise((resolve5) => setTimeout(resolve5, ms));
14712
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
14328
14713
  }
14329
14714
  /**
14330
14715
  * Attempt to reconnect the browser after detecting a disconnection.
@@ -15111,7 +15496,7 @@ ${lines.join("\n")}`;
15111
15496
  globalThis.__teamsSeenMessages.set(chatName, seenIds);
15112
15497
  }
15113
15498
  if (syncMode) {
15114
- return new Promise((resolve5) => {
15499
+ return new Promise((resolve6) => {
15115
15500
  const timer2 = setInterval(async () => {
15116
15501
  try {
15117
15502
  const current = await channel.readCurrentChatMessages();
@@ -15120,7 +15505,7 @@ ${lines.join("\n")}`;
15120
15505
  if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
15121
15506
  clearInterval(timer2);
15122
15507
  seenIds.add(msgId);
15123
- resolve5({
15508
+ resolve6({
15124
15509
  success: true,
15125
15510
  channel: channelType,
15126
15511
  sync: true,
@@ -15197,7 +15582,7 @@ ${lines.join("\n")}`;
15197
15582
  globalThis.__teamsSeenMessages.set(chatName, seenIds);
15198
15583
  }
15199
15584
  if (syncMode) {
15200
- return new Promise((resolve5) => {
15585
+ return new Promise((resolve6) => {
15201
15586
  const timer2 = setInterval(async () => {
15202
15587
  try {
15203
15588
  const current = await channel.readCurrentChatMessages();
@@ -15206,7 +15591,7 @@ ${lines.join("\n")}`;
15206
15591
  if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
15207
15592
  clearInterval(timer2);
15208
15593
  seenIds.add(msgId);
15209
- resolve5({
15594
+ resolve6({
15210
15595
  success: true,
15211
15596
  channel: channelType,
15212
15597
  sync: true,
@@ -15564,7 +15949,7 @@ ${lines.join("\n")}`;
15564
15949
  return allTools;
15565
15950
  }
15566
15951
  function sleep2(ms) {
15567
- return new Promise((resolve5) => setTimeout(resolve5, ms));
15952
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
15568
15953
  }
15569
15954
  var MAX_STORED_ENTRIES, ENTRY_TTL_MS;
15570
15955
  var init_chat = __esm({
@@ -16388,11 +16773,11 @@ function createShellTools(_config, hooks) {
16388
16773
  const shell = input.shell ?? true;
16389
16774
  if (input.background) {
16390
16775
  const { tmpdir: tmpdir13 } = await import("node:os");
16391
- const { join: join47 } = await import("node:path");
16392
- const { randomUUID: randomUUID14 } = await import("node:crypto");
16776
+ const { join: join48 } = await import("node:path");
16777
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
16393
16778
  const { createWriteStream: createWriteStream2 } = await import("node:fs");
16394
- const taskId = randomUUID14().slice(0, 8);
16395
- const outputPath = join47(tmpdir13(), `oj-bg-${taskId}.log`);
16779
+ const taskId = randomUUID15().slice(0, 8);
16780
+ const outputPath = join48(tmpdir13(), `oj-bg-${taskId}.log`);
16396
16781
  const detached = spawn(command, [], {
16397
16782
  shell,
16398
16783
  cwd,
@@ -16412,7 +16797,7 @@ function createShellTools(_config, hooks) {
16412
16797
  message: `Command started in background (PID: ${detached.pid}). Output: ${outputPath}`
16413
16798
  };
16414
16799
  }
16415
- return new Promise((resolve5) => {
16800
+ return new Promise((resolve6) => {
16416
16801
  const proc = spawn(command, [], {
16417
16802
  shell,
16418
16803
  cwd,
@@ -16430,7 +16815,7 @@ function createShellTools(_config, hooks) {
16430
16815
  proc.on("close", (code) => {
16431
16816
  const stdoutResult = truncateOutput(stdout.trim());
16432
16817
  const stderrResult = truncateOutput(stderr.trim());
16433
- resolve5({
16818
+ resolve6({
16434
16819
  command,
16435
16820
  exitCode: code,
16436
16821
  stdout: stdoutResult.text,
@@ -16441,7 +16826,7 @@ function createShellTools(_config, hooks) {
16441
16826
  });
16442
16827
  });
16443
16828
  proc.on("error", (error) => {
16444
- resolve5({
16829
+ resolve6({
16445
16830
  command,
16446
16831
  exitCode: -1,
16447
16832
  stdout: "",
@@ -16475,10 +16860,10 @@ function createShellTools(_config, hooks) {
16475
16860
  },
16476
16861
  requiresConfirmation: false,
16477
16862
  execute: /* @__PURE__ */ __name(async (input) => {
16478
- const { writeFileSync: writeFileSync22, unlinkSync: unlinkSync9 } = await import("node:fs");
16479
- const { join: join47 } = await import("node:path");
16863
+ const { writeFileSync: writeFileSync23, unlinkSync: unlinkSync9 } = await import("node:fs");
16864
+ const { join: join48 } = await import("node:path");
16480
16865
  const { tmpdir: tmpdir13 } = await import("node:os");
16481
- const { randomUUID: randomUUID14 } = await import("node:crypto");
16866
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
16482
16867
  const { execFile: execFile3 } = await import("node:child_process");
16483
16868
  const code = input.code;
16484
16869
  const language = input.language;
@@ -16495,14 +16880,14 @@ function createShellTools(_config, hooks) {
16495
16880
  const interpreter = interpreterMap[language];
16496
16881
  if (!interpreter)
16497
16882
  return { error: `Unsupported language: ${language}` };
16498
- const tmpFile = join47(tmpdir13(), `oj-code-${randomUUID14().slice(0, 8)}${ext}`);
16883
+ const tmpFile = join48(tmpdir13(), `oj-code-${randomUUID15().slice(0, 8)}${ext}`);
16499
16884
  try {
16500
- writeFileSync22(tmpFile, code, "utf-8");
16885
+ writeFileSync23(tmpFile, code, "utf-8");
16501
16886
  const startTime = Date.now();
16502
- const result = await new Promise((resolve5) => {
16887
+ const result = await new Promise((resolve6) => {
16503
16888
  execFile3(interpreter.cmd, [...interpreter.args, tmpFile], { timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout2, stderr2) => {
16504
16889
  const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
16505
- resolve5({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
16890
+ resolve6({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
16506
16891
  });
16507
16892
  });
16508
16893
  const executionTimeMs = Date.now() - startTime;
@@ -16595,7 +16980,7 @@ function createShellTools(_config, hooks) {
16595
16980
  required: ["title", "message"]
16596
16981
  },
16597
16982
  execute: /* @__PURE__ */ __name(async (input) => {
16598
- return new Promise((resolve5) => {
16983
+ return new Promise((resolve6) => {
16599
16984
  notifier.notify({
16600
16985
  title: input.title,
16601
16986
  message: input.message,
@@ -16603,9 +16988,9 @@ function createShellTools(_config, hooks) {
16603
16988
  sound: true
16604
16989
  }, (err) => {
16605
16990
  if (err) {
16606
- resolve5({ error: err.message });
16991
+ resolve6({ error: err.message });
16607
16992
  } else {
16608
- resolve5({ success: true });
16993
+ resolve6({ success: true });
16609
16994
  }
16610
16995
  });
16611
16996
  });
@@ -16709,7 +17094,7 @@ function createShellTools(_config, hooks) {
16709
17094
  },
16710
17095
  execute: /* @__PURE__ */ __name(async (input) => {
16711
17096
  const seconds = Math.min(Math.max(0.1, input.seconds), 60);
16712
- await new Promise((resolve5) => setTimeout(resolve5, seconds * 1e3));
17097
+ await new Promise((resolve6) => setTimeout(resolve6, seconds * 1e3));
16713
17098
  return { waited: seconds, message: `Waited ${seconds} seconds` };
16714
17099
  }, "execute")
16715
17100
  },
@@ -17821,7 +18206,7 @@ var init_office_desktop = __esm({
17821
18206
  return Array.isArray(parsed) ? parsed : [parsed];
17822
18207
  }
17823
18208
  sleep(ms) {
17824
- return new Promise((resolve5) => setTimeout(resolve5, ms));
18209
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
17825
18210
  }
17826
18211
  };
17827
18212
  }
@@ -19502,7 +19887,7 @@ public class Win32Send {
19502
19887
  }
19503
19888
  }
19504
19889
  sleep(ms) {
19505
- return new Promise((resolve5) => setTimeout(resolve5, ms));
19890
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
19506
19891
  }
19507
19892
  };
19508
19893
  }
@@ -19567,7 +19952,7 @@ function getActiveMonitors() {
19567
19952
  return result;
19568
19953
  }
19569
19954
  function sleep3(ms) {
19570
- return new Promise((resolve5) => setTimeout(resolve5, ms));
19955
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
19571
19956
  }
19572
19957
  async function fileHash(filePath) {
19573
19958
  const data = fs.readFileSync(filePath);
@@ -21040,11 +21425,11 @@ function loadFlatSkillsFromDir(dir2, source, priority, out) {
21040
21425
  function loadPackagedSkillsFromDir(dir2, out) {
21041
21426
  for (const entry of safeReadDir(dir2)) {
21042
21427
  if (!entry.isDirectory()) continue;
21043
- const rootDir = join22(dir2, entry.name);
21044
- const entrypoint = findPackageEntrypoint(rootDir);
21428
+ const rootDir2 = join22(dir2, entry.name);
21429
+ const entrypoint = findPackageEntrypoint(rootDir2);
21045
21430
  if (!entrypoint) continue;
21046
- const filePath = join22(rootDir, entrypoint);
21047
- const skill = parseSkillAtPath(filePath, rootDir, entrypoint, "user", `${entry.name}.md`);
21431
+ const filePath = join22(rootDir2, entrypoint);
21432
+ const skill = parseSkillAtPath(filePath, rootDir2, entrypoint, "user", `${entry.name}.md`);
21048
21433
  if (skill) putSkill(out, skill, 2);
21049
21434
  }
21050
21435
  }
@@ -21057,7 +21442,7 @@ function findPackageEntrypoint(dir2) {
21057
21442
  const markdown = entries.filter((entry) => isMarkdown(entry.name));
21058
21443
  return markdown.length === 1 ? markdown[0].name : null;
21059
21444
  }
21060
- function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilename) {
21445
+ function parseSkillAtPath(filePath, rootDir2, entrypoint, source, fallbackFilename) {
21061
21446
  try {
21062
21447
  const content = readFileSync14(filePath, "utf-8").trim();
21063
21448
  if (!content) return null;
@@ -21068,7 +21453,7 @@ function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilenam
21068
21453
  name,
21069
21454
  meta: { ...parsed.meta, name },
21070
21455
  filePath,
21071
- rootDir,
21456
+ rootDir: rootDir2,
21072
21457
  entrypoint,
21073
21458
  source,
21074
21459
  hasFrontmatter: parsed.hasFrontmatter
@@ -21725,12 +22110,12 @@ var init_telegram = __esm({
21725
22110
  Options: ${chunk.choices.join(" | ")}` : "";
21726
22111
  await this.bot.sendMessage(chatId, `\u2753 ${question}${choicesText}`);
21727
22112
  updateStatus("\u2753 Waiting for your response...");
21728
- const userReply = await new Promise((resolve5) => {
21729
- this._pendingReplyResolver = resolve5;
22113
+ const userReply = await new Promise((resolve6) => {
22114
+ this._pendingReplyResolver = resolve6;
21730
22115
  setTimeout(() => {
21731
- if (this._pendingReplyResolver === resolve5) {
22116
+ if (this._pendingReplyResolver === resolve6) {
21732
22117
  this._pendingReplyResolver = null;
21733
- resolve5("[No response \u2014 timed out]");
22118
+ resolve6("[No response \u2014 timed out]");
21734
22119
  }
21735
22120
  }, 5 * 60 * 1e3);
21736
22121
  });
@@ -22328,7 +22713,7 @@ async function promptConsent(servers) {
22328
22713
  input: process.stdin,
22329
22714
  output: process.stderr
22330
22715
  });
22331
- const ask = /* @__PURE__ */ __name((question) => new Promise((resolve5) => rl.question(question, resolve5)), "ask");
22716
+ const ask = /* @__PURE__ */ __name((question) => new Promise((resolve6) => rl.question(question, resolve6)), "ask");
22332
22717
  const w = process.stderr.columns || 80;
22333
22718
  const inner = w - 4;
22334
22719
  const hLine = "\u2500".repeat(inner);
@@ -24383,7 +24768,7 @@ try {
24383
24768
  }
24384
24769
  `;
24385
24770
  const encoded = Buffer.from(script, "utf16le").toString("base64");
24386
- return new Promise((resolve5) => {
24771
+ return new Promise((resolve6) => {
24387
24772
  const proc = spawn3("powershell.exe", ["-NoProfile", "-STA", "-EncodedCommand", encoded], {
24388
24773
  stdio: ["pipe", "pipe", "pipe"],
24389
24774
  windowsHide: true
@@ -24398,22 +24783,22 @@ try {
24398
24783
  });
24399
24784
  const timer = setTimeout(() => {
24400
24785
  if (!proc.killed) proc.kill();
24401
- resolve5(null);
24786
+ resolve6(null);
24402
24787
  }, (timeoutSeconds + 5) * 1e3);
24403
24788
  proc.on("exit", () => {
24404
24789
  clearTimeout(timer);
24405
24790
  const output = stdout.replace(/#< CLIXML[\s\S]*/m, "").trim();
24406
24791
  if (output === "NO_SPEECH" || output.startsWith("ERROR") || !output) {
24407
- resolve5(null);
24792
+ resolve6(null);
24408
24793
  return;
24409
24794
  }
24410
24795
  const parts = output.split("|");
24411
24796
  const text = parts[0]?.trim();
24412
24797
  const confidence = parseFloat(parts[1] || "0");
24413
24798
  if (text) {
24414
- resolve5({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
24799
+ resolve6({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
24415
24800
  } else {
24416
- resolve5(null);
24801
+ resolve6(null);
24417
24802
  }
24418
24803
  try {
24419
24804
  if (existsSync21(resultFile)) unlinkSync5(resultFile);
@@ -24750,6 +25135,7 @@ var init_PromptInput = __esm({
24750
25135
  { name: "/repl", description: "\u{1F527} Start interactive code REPL" },
24751
25136
  { name: "/voice", description: "\u{1F50A} Toggle voice output (TTS)" },
24752
25137
  { name: "/fork", description: "\u{1F500} Spawn background sub-agent" },
25138
+ { name: "/workflow", description: "\u{1F9ED} Run advisory dynamic workflow" },
24753
25139
  { name: "/tasks", description: "List background tasks" },
24754
25140
  { name: "/clear", description: "Clear conversation history" },
24755
25141
  { name: "/compact", description: "Summarize old messages to free context" },
@@ -24782,6 +25168,12 @@ var init_PromptInput = __esm({
24782
25168
  { name: "pause", description: "Pause a task: /schedule pause <id>" },
24783
25169
  { name: "resume", description: "Resume a task: /schedule resume <id>" }
24784
25170
  ],
25171
+ "/workflow": [
25172
+ { name: "status", description: "Open live worker status" },
25173
+ { name: "list", description: "List recent workflows" },
25174
+ { name: "show <id>", description: "Show workflow summary" },
25175
+ { name: "cancel <id>", description: "Cancel a run or worker" }
25176
+ ],
24785
25177
  "/repl": [
24786
25178
  { name: "python", description: "Python interactive shell" },
24787
25179
  { name: "node", description: "Node.js interactive shell" },
@@ -25985,8 +26377,8 @@ function isSensitivePath(resolved) {
25985
26377
  if (re.test(normalized)) return true;
25986
26378
  }
25987
26379
  for (const re of SENSITIVE_FILE_PATTERNS) {
25988
- const basename4 = path2.basename(resolved);
25989
- if (re.test(basename4)) return true;
26380
+ const basename5 = path2.basename(resolved);
26381
+ if (re.test(basename5)) return true;
25990
26382
  }
25991
26383
  return false;
25992
26384
  }
@@ -26104,34 +26496,34 @@ ${output}
26104
26496
  }
26105
26497
  }
26106
26498
  function expandUrl(ref, warnings) {
26107
- return new Promise((resolve5) => {
26499
+ return new Promise((resolve6) => {
26108
26500
  const url = ref.target;
26109
26501
  const mod = url.startsWith("https") ? https : http;
26110
26502
  const req = mod.get(url, { timeout: 15e3 }, (res) => {
26111
26503
  if (res.statusCode && (res.statusCode >= 300 && res.statusCode < 400) && res.headers.location) {
26112
26504
  const redirectMod = res.headers.location.startsWith("https") ? https : http;
26113
26505
  const req2 = redirectMod.get(res.headers.location, { timeout: 15e3 }, (res2) => {
26114
- collectResponse(res2, ref, warnings, resolve5);
26506
+ collectResponse(res2, ref, warnings, resolve6);
26115
26507
  });
26116
26508
  req2.on("error", (e) => {
26117
26509
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26118
- resolve5(null);
26510
+ resolve6(null);
26119
26511
  });
26120
26512
  return;
26121
26513
  }
26122
- collectResponse(res, ref, warnings, resolve5);
26514
+ collectResponse(res, ref, warnings, resolve6);
26123
26515
  });
26124
26516
  req.on("error", (e) => {
26125
26517
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26126
- resolve5(null);
26518
+ resolve6(null);
26127
26519
  });
26128
26520
  });
26129
26521
  }
26130
- function collectResponse(res, ref, warnings, resolve5) {
26522
+ function collectResponse(res, ref, warnings, resolve6) {
26131
26523
  if (res.statusCode && res.statusCode >= 400) {
26132
26524
  warnings.push(`\u26A0\uFE0F @url returned HTTP ${res.statusCode}: ${ref.target}`);
26133
26525
  res.resume();
26134
- resolve5(null);
26526
+ resolve6(null);
26135
26527
  return;
26136
26528
  }
26137
26529
  const chunks = [];
@@ -26143,14 +26535,14 @@ function collectResponse(res, ref, warnings, resolve5) {
26143
26535
  text = text.slice(0, 5e4) + "\n\u2026 (truncated)";
26144
26536
  }
26145
26537
  const tokens = estimateTokens2(text);
26146
- resolve5(`\u{1F310} @url:${ref.target} (${tokens} tokens)
26538
+ resolve6(`\u{1F310} @url:${ref.target} (${tokens} tokens)
26147
26539
  \`\`\`
26148
26540
  ${text}
26149
26541
  \`\`\``);
26150
26542
  });
26151
26543
  res.on("error", (e) => {
26152
26544
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26153
- resolve5(null);
26545
+ resolve6(null);
26154
26546
  });
26155
26547
  }
26156
26548
  function stripHtml(html) {
@@ -26651,13 +27043,13 @@ Type /resume <id> to resume.` });
26651
27043
  if (input === "/export") {
26652
27044
  try {
26653
27045
  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 });
27046
+ const { join: join48 } = await import("node:path");
27047
+ const { homedir: homedir32 } = await import("node:os");
27048
+ const { existsSync: existsSync34 } = await import("node:fs");
27049
+ const exportDir = join48(homedir32(), ".openjaw-agent", "exports");
27050
+ if (!existsSync34(exportDir)) await mkdir5(exportDir, { recursive: true });
26659
27051
  const filename = `session-${agentLoop.sessionId}.md`;
26660
- const filepath = join47(exportDir, filename);
27052
+ const filepath = join48(exportDir, filename);
26661
27053
  const lines = [
26662
27054
  `# OpenJaw Agent Session ${agentLoop.sessionId}`,
26663
27055
  `Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -26876,9 +27268,9 @@ ${list}` });
26876
27268
  const cmd = lang === "python" ? "python" : lang === "node" ? "node" : "pwsh";
26877
27269
  const args = lang === "python" ? ["-i", "-u"] : lang === "node" ? ["-i"] : ["-NoProfile", "-NoLogo"];
26878
27270
  try {
26879
- const { spawn: spawn9 } = await import("node:child_process");
27271
+ const { spawn: spawn8 } = await import("node:child_process");
26880
27272
  if (replRef.current) replRef.current.kill();
26881
- const proc = spawn9(cmd, args, {
27273
+ const proc = spawn8(cmd, args, {
26882
27274
  stdio: ["pipe", "pipe", "pipe"],
26883
27275
  windowsHide: true
26884
27276
  });
@@ -26918,7 +27310,7 @@ ${list}` });
26918
27310
  if (replRef.current && replLangRef.current && !input.startsWith("/")) {
26919
27311
  const proc = replRef.current;
26920
27312
  proc.stdin?.write(input + "\n");
26921
- await new Promise((resolve5) => setTimeout(resolve5, 800));
27313
+ await new Promise((resolve6) => setTimeout(resolve6, 800));
26922
27314
  const flush = proc._ojFlush;
26923
27315
  const output = flush ? flush() : "";
26924
27316
  if (output.trim()) {
@@ -27232,14 +27624,14 @@ Options: ${chunk.choices.join(" | ")}` : chunk.content;
27232
27624
  waitingForAskUserRef.current = true;
27233
27625
  setIsRunning(false);
27234
27626
  setWaitingForAskUser(true);
27235
- await new Promise((resolve5) => {
27627
+ await new Promise((resolve6) => {
27236
27628
  const checkInterval = setInterval(() => {
27237
27629
  if (!waitingForAskUserRef.current || !agentLoop.isWaitingForAskUser) {
27238
27630
  clearInterval(checkInterval);
27239
27631
  waitingForAskUserRef.current = false;
27240
27632
  setWaitingForAskUser(false);
27241
27633
  setIsRunning(true);
27242
- resolve5();
27634
+ resolve6();
27243
27635
  }
27244
27636
  }, 200);
27245
27637
  });
@@ -27535,8 +27927,8 @@ async function withTimeout(promise, ms) {
27535
27927
  try {
27536
27928
  return await Promise.race([
27537
27929
  promise,
27538
- new Promise((resolve5) => {
27539
- timeoutId = setTimeout(() => resolve5(SKILL_TIMEOUT), ms);
27930
+ new Promise((resolve6) => {
27931
+ timeoutId = setTimeout(() => resolve6(SKILL_TIMEOUT), ms);
27540
27932
  })
27541
27933
  ]);
27542
27934
  } finally {
@@ -28146,12 +28538,12 @@ var init_teams = __esm({
28146
28538
  Options: ${chunk.choices.join(" | ")}` : "";
28147
28539
  await this.sendToSelfChat(`\u2753 ${question}${choicesText}`);
28148
28540
  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;
28541
+ const userReply = await new Promise((resolve6) => {
28542
+ this._pendingReplyResolver = resolve6;
28151
28543
  setTimeout(() => {
28152
- if (this._pendingReplyResolver === resolve5) {
28544
+ if (this._pendingReplyResolver === resolve6) {
28153
28545
  this._pendingReplyResolver = null;
28154
- resolve5("[No response \u2014 timed out]");
28546
+ resolve6("[No response \u2014 timed out]");
28155
28547
  }
28156
28548
  }, 5 * 60 * 1e3);
28157
28549
  });
@@ -28635,12 +29027,12 @@ ${err.stack?.split("\n").slice(0, 3).join("\n")}` : String(err);
28635
29027
  }
28636
29028
  /** Wait for the next user message (used by ask_user flow) */
28637
29029
  waitForUserReply(_chatId) {
28638
- return new Promise((resolve5) => {
28639
- this._pendingReplyResolver = resolve5;
29030
+ return new Promise((resolve6) => {
29031
+ this._pendingReplyResolver = resolve6;
28640
29032
  setTimeout(() => {
28641
- if (this._pendingReplyResolver === resolve5) {
29033
+ if (this._pendingReplyResolver === resolve6) {
28642
29034
  this._pendingReplyResolver = null;
28643
- resolve5("[No response \u2014 timed out]");
29035
+ resolve6("[No response \u2014 timed out]");
28644
29036
  }
28645
29037
  }, 5 * 60 * 1e3);
28646
29038
  });
@@ -28931,9 +29323,9 @@ var init_wechat2 = __esm({
28931
29323
  try {
28932
29324
  const qrTerminal = await import("qrcode-terminal");
28933
29325
  const mod = qrTerminal.default || qrTerminal;
28934
- const qrAscii = await new Promise((resolve5, reject) => {
29326
+ const qrAscii = await new Promise((resolve6, reject) => {
28935
29327
  try {
28936
- mod.generate(qrUrl, { small: true }, (out) => resolve5(out));
29328
+ mod.generate(qrUrl, { small: true }, (out) => resolve6(out));
28937
29329
  } catch (e) {
28938
29330
  reject(e);
28939
29331
  }
@@ -29199,12 +29591,12 @@ Scan URL manually: ${qrUrl}` });
29199
29591
  const choicesText = chunk.choices?.length ? `
29200
29592
  \u9009\u9879: ${chunk.choices.join(" | ")}` : "";
29201
29593
  await this.sendText(userId, `\u2753 ${question}${choicesText}`, contextToken);
29202
- const userReply = await new Promise((resolve5) => {
29203
- this._pendingReplyResolver = resolve5;
29594
+ const userReply = await new Promise((resolve6) => {
29595
+ this._pendingReplyResolver = resolve6;
29204
29596
  setTimeout(() => {
29205
- if (this._pendingReplyResolver === resolve5) {
29597
+ if (this._pendingReplyResolver === resolve6) {
29206
29598
  this._pendingReplyResolver = null;
29207
- resolve5("[No response \u2014 timed out]");
29599
+ resolve6("[No response \u2014 timed out]");
29208
29600
  }
29209
29601
  }, 5 * 60 * 1e3);
29210
29602
  });
@@ -30705,8 +31097,8 @@ function createPromptCollector(bus, getSessionId) {
30705
31097
  }, "emit");
30706
31098
  const register = /* @__PURE__ */ __name((kind) => {
30707
31099
  const requestId = randomUUID12();
30708
- const promise = new Promise((resolve5) => {
30709
- pending.set(requestId, { kind, resolve: resolve5 });
31100
+ const promise = new Promise((resolve6) => {
31101
+ pending.set(requestId, { kind, resolve: resolve6 });
30710
31102
  });
30711
31103
  return { promise, requestId };
30712
31104
  }, "register");
@@ -31686,11 +32078,11 @@ function detectTerminal() {
31686
32078
  }
31687
32079
  return process.env.TERM ?? null;
31688
32080
  }
31689
- function supportsOsc52Clipboard(terminal = env.terminal) {
31690
- return OSC52_CAPABLE_TERMINALS.includes(terminal ?? "");
32081
+ function supportsOsc52Clipboard(terminal2 = env.terminal) {
32082
+ return OSC52_CAPABLE_TERMINALS.includes(terminal2 ?? "");
31691
32083
  }
31692
32084
  function execFileNoThrow(file2, args, options = {}) {
31693
- return new Promise((resolve5) => {
32085
+ return new Promise((resolve6) => {
31694
32086
  const child = spawn4(file2, args, {
31695
32087
  cwd: options.useCwd ? process.cwd() : void 0,
31696
32088
  env: options.env,
@@ -31713,13 +32105,13 @@ function execFileNoThrow(file2, args, options = {}) {
31713
32105
  if (timer) {
31714
32106
  clearTimeout(timer);
31715
32107
  }
31716
- resolve5({ stdout, stderr, code: 1, error: String(error) });
32108
+ resolve6({ stdout, stderr, code: 1, error: String(error) });
31717
32109
  });
31718
32110
  child.on("close", (code) => {
31719
32111
  if (timer) {
31720
32112
  clearTimeout(timer);
31721
32113
  }
31722
- resolve5({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
32114
+ resolve6({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
31723
32115
  });
31724
32116
  if (options.input) {
31725
32117
  child.stdin?.write(options.input);
@@ -31751,7 +32143,7 @@ function shouldEmitClipboardSequence(env2 = process.env) {
31751
32143
  }
31752
32144
  return !!env2["SSH_CONNECTION"] || !env2["TMUX"] && !env2["STY"];
31753
32145
  }
31754
- function shouldUseNativeClipboard(env2 = process.env, terminal = env.terminal) {
32146
+ function shouldUseNativeClipboard(env2 = process.env, terminal2 = env.terminal) {
31755
32147
  if (env2.SSH_CONNECTION) {
31756
32148
  return false;
31757
32149
  }
@@ -31761,7 +32153,7 @@ function shouldUseNativeClipboard(env2 = process.env, terminal = env.terminal) {
31761
32153
  if (!shouldEmitClipboardSequence(env2)) {
31762
32154
  return true;
31763
32155
  }
31764
- return !supportsOsc52Clipboard(terminal);
32156
+ return !supportsOsc52Clipboard(terminal2);
31765
32157
  }
31766
32158
  function tmuxPassthrough(payload) {
31767
32159
  return `${ESC}Ptmux;${payload.replaceAll(ESC, ESC + ESC)}${ST}`;
@@ -33197,7 +33589,7 @@ function needsAltScreenResizeScrollbackClear(env2 = process.env) {
33197
33589
  function supportsExtendedKeys() {
33198
33590
  return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? "");
33199
33591
  }
33200
- function writeDiffToTerminal(terminal, diff2, skipSyncMarkers = false, onDrain) {
33592
+ function writeDiffToTerminal(terminal2, diff2, skipSyncMarkers = false, onDrain) {
33201
33593
  if (diff2.length === 0) {
33202
33594
  return { bytes: 0, backpressure: false };
33203
33595
  }
@@ -33242,7 +33634,7 @@ function writeDiffToTerminal(terminal, diff2, skipSyncMarkers = false, onDrain)
33242
33634
  if (useSync) {
33243
33635
  buffer += ESU;
33244
33636
  }
33245
- const wrote = onDrain ? terminal.stdout.write(buffer, () => onDrain()) : terminal.stdout.write(buffer);
33637
+ const wrote = onDrain ? terminal2.stdout.write(buffer, () => onDrain()) : terminal2.stdout.write(buffer);
33246
33638
  return { bytes: Buffer.byteLength(buffer, "utf8"), backpressure: !wrote };
33247
33639
  }
33248
33640
  function logForDebugging(_message, _options = {}) {
@@ -36692,8 +37084,8 @@ function setTerminalFocused(v) {
36692
37084
  cb();
36693
37085
  }
36694
37086
  if (!v) {
36695
- for (const resolve5 of resolvers) {
36696
- resolve5();
37087
+ for (const resolve6 of resolvers) {
37088
+ resolve6();
36697
37089
  }
36698
37090
  resolvers.clear();
36699
37091
  }
@@ -42931,11 +43323,11 @@ $ npm install --save-dev react-devtools-core
42931
43323
  * and the terminal doesn't respond, the promise remains pending.
42932
43324
  */
42933
43325
  send(query) {
42934
- return new Promise((resolve5) => {
43326
+ return new Promise((resolve6) => {
42935
43327
  this.queue.push({
42936
43328
  kind: "query",
42937
43329
  match: query.match,
42938
- resolve: /* @__PURE__ */ __name((r) => resolve5(r), "resolve")
43330
+ resolve: /* @__PURE__ */ __name((r) => resolve6(r), "resolve")
42939
43331
  });
42940
43332
  this.stdout.write(query.request);
42941
43333
  });
@@ -42950,8 +43342,8 @@ $ npm install --save-dev react-devtools-core
42950
43342
  * Safe to call with no pending queries — still waits for a round-trip.
42951
43343
  */
42952
43344
  flush() {
42953
- return new Promise((resolve5) => {
42954
- this.queue.push({ kind: "sentinel", resolve: resolve5 });
43345
+ return new Promise((resolve6) => {
43346
+ this.queue.push({ kind: "sentinel", resolve: resolve6 });
42955
43347
  this.stdout.write(SENTINEL);
42956
43348
  });
42957
43349
  }
@@ -45431,8 +45823,8 @@ $ npm install --save-dev react-devtools-core
45431
45823
  }
45432
45824
  }
45433
45825
  async waitUntilExit() {
45434
- this.exitPromise ||= new Promise((resolve5, reject) => {
45435
- this.resolveExitPromise = resolve5;
45826
+ this.exitPromise ||= new Promise((resolve6, reject) => {
45827
+ this.resolveExitPromise = resolve6;
45436
45828
  this.rejectExitPromise = reject;
45437
45829
  });
45438
45830
  return this.exitPromise;
@@ -45923,10 +46315,10 @@ async function writeClipboardText(text, platform2 = process.platform, start = sp
45923
46315
  const candidates = writeClipboardCommands(platform2, env2);
45924
46316
  for (const { cmd, args } of candidates) {
45925
46317
  try {
45926
- const ok = await new Promise((resolve5) => {
46318
+ const ok = await new Promise((resolve6) => {
45927
46319
  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));
46320
+ child.once("error", () => resolve6(false));
46321
+ child.once("close", (code) => resolve6(code === 0));
45930
46322
  child.stdin?.end(text);
45931
46323
  });
45932
46324
  if (ok) {
@@ -45985,8 +46377,8 @@ async function readOsc52Clipboard(querier, timeoutMs = 500) {
45985
46377
  if (!querier) {
45986
46378
  return null;
45987
46379
  }
45988
- const timeout = new Promise((resolve5) => {
45989
- setTimeout(() => resolve5(void 0), timeoutMs);
46380
+ const timeout = new Promise((resolve6) => {
46381
+ setTimeout(() => resolve6(void 0), timeoutMs);
45990
46382
  });
45991
46383
  const query = querier.send({
45992
46384
  request: buildOsc52ClipboardQuery(),
@@ -46143,12 +46535,12 @@ async function backupFile(filePath, ops) {
46143
46535
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
46144
46536
  await ops.copyFile(filePath, `${filePath}.backup.${stamp}`);
46145
46537
  }
46146
- async function configureTerminalKeybindings(terminal, options) {
46538
+ async function configureTerminalKeybindings(terminal2, options) {
46147
46539
  const env2 = options?.env ?? process.env;
46148
46540
  const platform2 = options?.platform ?? process.platform;
46149
46541
  const homeDir = options?.homeDir ?? homedir25();
46150
46542
  const ops = { ...DEFAULT_FILE_OPS, ...options?.fileOps ?? {} };
46151
- const meta = TERMINAL_META[terminal];
46543
+ const meta = TERMINAL_META[terminal2];
46152
46544
  if (isRemoteShellSession(env2)) {
46153
46545
  return {
46154
46546
  success: false,
@@ -46343,6 +46735,7 @@ var init_overlayStore = __esm({
46343
46735
  buildOverlayState = /* @__PURE__ */ __name(() => ({
46344
46736
  agents: false,
46345
46737
  agentsInitialHistoryIndex: 0,
46738
+ agentsWorkflowId: null,
46346
46739
  approval: null,
46347
46740
  clarify: null,
46348
46741
  confirm: null,
@@ -46358,13 +46751,14 @@ var init_overlayStore = __esm({
46358
46751
  $overlayState = atom2(buildOverlayState());
46359
46752
  $isBlocked = computed2(
46360
46753
  $overlayState,
46361
- ({ agents, approval, clarify, confirm, mcpHub, modelPicker, pager, picker, secret, skillsHub, sudo }) => Boolean(agents || approval || clarify || confirm || mcpHub || modelPicker || pager || picker || secret || skillsHub || sudo)
46754
+ ({ agents, approval, clarify, confirm, mcpHub, pager, picker, secret, skillsHub, sudo }) => Boolean(agents || approval || clarify || confirm || mcpHub || pager || picker || secret || skillsHub || sudo)
46362
46755
  );
46363
46756
  patchOverlayState = /* @__PURE__ */ __name((next) => $overlayState.set(typeof next === "function" ? next($overlayState.get()) : { ...$overlayState.get(), ...next }), "patchOverlayState");
46364
46757
  resetFlowOverlays = /* @__PURE__ */ __name(() => $overlayState.set({
46365
46758
  ...buildOverlayState(),
46366
46759
  agents: $overlayState.get().agents,
46367
46760
  agentsInitialHistoryIndex: $overlayState.get().agentsInitialHistoryIndex,
46761
+ agentsWorkflowId: $overlayState.get().agentsWorkflowId,
46368
46762
  mcpHub: $overlayState.get().mcpHub,
46369
46763
  modelPicker: $overlayState.get().modelPicker,
46370
46764
  modelPickerMode: $overlayState.get().modelPickerMode,
@@ -47164,13 +47558,25 @@ var init_debug = __esm({
47164
47558
  }
47165
47559
  });
47166
47560
 
47561
+ // src/app/workflowStore.ts
47562
+ import { atom as atom3 } from "nanostores";
47563
+ var $workflowSnapshots, setWorkflowSnapshot;
47564
+ var init_workflowStore = __esm({
47565
+ "src/app/workflowStore.ts"() {
47566
+ "use strict";
47567
+ $workflowSnapshots = atom3({});
47568
+ setWorkflowSnapshot = /* @__PURE__ */ __name((snapshot) => $workflowSnapshots.set({ ...$workflowSnapshots.get(), [snapshot.id]: snapshot }), "setWorkflowSnapshot");
47569
+ }
47570
+ });
47571
+
47167
47572
  // src/app/slash/commands/openjaw.ts
47168
- var fmt, money, stub, eventLine, parseScheduleInput, showUsage, openjawCommands;
47573
+ var fmt, money, stub, eventLine, parseScheduleInput, asWorkflowSnapshot, workflowLine, workflowRows, showUsage, openjawCommands;
47169
47574
  var init_openjaw = __esm({
47170
47575
  "src/app/slash/commands/openjaw.ts"() {
47171
47576
  "use strict";
47172
47577
  init_usage();
47173
47578
  init_overlayStore();
47579
+ init_workflowStore();
47174
47580
  fmt = /* @__PURE__ */ __name((n) => (n ?? 0).toLocaleString(), "fmt");
47175
47581
  money = /* @__PURE__ */ __name((n) => `$${(n ?? 0).toFixed(4)}`, "money");
47176
47582
  stub = /* @__PURE__ */ __name((ctx, command, message) => {
@@ -47210,6 +47616,14 @@ var init_openjaw = __esm({
47210
47616
  }
47211
47617
  return null;
47212
47618
  }, "parseScheduleInput");
47619
+ asWorkflowSnapshot = /* @__PURE__ */ __name((run) => run ? run : null, "asWorkflowSnapshot");
47620
+ workflowLine = /* @__PURE__ */ __name((run) => {
47621
+ const active = run.workers.filter((worker) => worker.status === "running" || worker.status === "queued").length;
47622
+ const done = run.workers.filter((worker) => worker.status === "completed").length;
47623
+ return `${run.id} \xB7 ${run.status} \xB7 ${done}/${run.workers.length} done${active ? ` \xB7 ${active} active` : ""} \xB7 ${run.goal}`;
47624
+ }, "workflowLine");
47625
+ workflowRows = /* @__PURE__ */ __name((runs) => runs.map((run) => [run.id, `${run.status} \xB7 ${run.workerCount}/${run.plannedWorkerCount} workers
47626
+ ${run.goal}`]), "workflowRows");
47213
47627
  showUsage = /* @__PURE__ */ __name((ctx, render2) => {
47214
47628
  ctx.gateway.rpc("session.usage", { session_id: ctx.sid }).then(ctx.guarded(render2)).catch(ctx.guardedErr);
47215
47629
  }, "showUsage");
@@ -47231,6 +47645,10 @@ var init_openjaw = __esm({
47231
47645
  if (!r.messages?.length) {
47232
47646
  ctx.transcript.sys("connect: no output");
47233
47647
  }
47648
+ if (!ctx.sid && r.config_updates) {
47649
+ ctx.transcript.sys("provider connected \u2014 starting OpenJaw session\u2026");
47650
+ ctx.session.newSession();
47651
+ }
47234
47652
  })
47235
47653
  ).catch(ctx.guardedErr);
47236
47654
  }, "run")
@@ -47324,6 +47742,70 @@ var init_openjaw = __esm({
47324
47742
  ).catch(ctx.guardedErr);
47325
47743
  }, "run")
47326
47744
  },
47745
+ {
47746
+ aliases: ["wf"],
47747
+ help: "start or inspect an advisory dynamic workflow",
47748
+ name: "workflow",
47749
+ usage: "/workflow <goal> | /workflow status [id] | list | show [id] | cancel <id>",
47750
+ run: /* @__PURE__ */ __name((arg, ctx) => {
47751
+ const text = arg.trim();
47752
+ if (!text) {
47753
+ return ctx.transcript.sys("usage: /workflow <goal> | /workflow status [id] | list | show [id] | cancel <id>");
47754
+ }
47755
+ const [subRaw, ...rest] = text.split(/\s+/);
47756
+ const sub = subRaw?.toLowerCase() ?? "";
47757
+ const remainder = rest.join(" ").trim();
47758
+ if (sub === "status") {
47759
+ return ctx.gateway.rpc("workflow.status", { id: remainder, session_id: ctx.sid }).then(
47760
+ ctx.guarded((r) => {
47761
+ const snapshot = asWorkflowSnapshot(r.run);
47762
+ if (!snapshot) return ctx.transcript.sys(r.error || "no workflows yet");
47763
+ setWorkflowSnapshot(snapshot);
47764
+ patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0, agentsWorkflowId: snapshot.id });
47765
+ ctx.transcript.sys(`workflow status \xB7 ${workflowLine(snapshot)}`);
47766
+ })
47767
+ ).catch(ctx.guardedErr);
47768
+ }
47769
+ if (sub === "list" || sub === "ls") {
47770
+ return ctx.gateway.rpc("workflow.list", { limit: 30, session_id: ctx.sid }).then(
47771
+ ctx.guarded((r) => {
47772
+ const runs = r.runs ?? [];
47773
+ if (!runs.length) return ctx.transcript.sys("no workflows yet");
47774
+ ctx.transcript.panel("Workflows", [{ rows: workflowRows(runs) }]);
47775
+ })
47776
+ ).catch(ctx.guardedErr);
47777
+ }
47778
+ if (sub === "show") {
47779
+ return ctx.gateway.rpc("workflow.show", { id: remainder, session_id: ctx.sid }).then(
47780
+ ctx.guarded((r) => {
47781
+ const snapshot = asWorkflowSnapshot(r.run);
47782
+ if (!snapshot) return ctx.transcript.sys(r.error || "no workflows yet");
47783
+ setWorkflowSnapshot(snapshot);
47784
+ ctx.transcript.page(snapshot.summary || workflowLine(snapshot), `Workflow ${snapshot.id}`);
47785
+ })
47786
+ ).catch(ctx.guardedErr);
47787
+ }
47788
+ if (sub === "cancel" || sub === "stop") {
47789
+ if (!remainder) return ctx.transcript.sys("usage: /workflow cancel <runId|workerId>");
47790
+ return ctx.gateway.rpc("workflow.cancel", { id: remainder, session_id: ctx.sid }).then(
47791
+ ctx.guarded((r) => {
47792
+ const snapshot = asWorkflowSnapshot(r.run);
47793
+ if (snapshot) setWorkflowSnapshot(snapshot);
47794
+ ctx.transcript.sys(r.ok ? `workflow cancel requested: ${remainder}` : r.error || `not found: ${remainder}`);
47795
+ })
47796
+ ).catch(ctx.guardedErr);
47797
+ }
47798
+ return ctx.gateway.rpc("workflow.start", { goal: text, session_id: ctx.sid }).then(
47799
+ ctx.guarded((r) => {
47800
+ const snapshot = asWorkflowSnapshot(r.run);
47801
+ if (!snapshot) return ctx.transcript.sys(r.error || "workflow failed to start");
47802
+ setWorkflowSnapshot(snapshot);
47803
+ patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0, agentsWorkflowId: snapshot.id });
47804
+ ctx.transcript.sys(`workflow ${snapshot.id} started \xB7 ${snapshot.plannedWorkerCount} workers \xB7 concurrency ${snapshot.concurrency}`);
47805
+ })
47806
+ ).catch(ctx.guardedErr);
47807
+ }, "run")
47808
+ },
47327
47809
  {
47328
47810
  help: "schedule a recurring prompt",
47329
47811
  name: "schedule",
@@ -47456,7 +47938,7 @@ var init_openjaw = __esm({
47456
47938
  });
47457
47939
 
47458
47940
  // src/app/delegationStore.ts
47459
- import { atom as atom3 } from "nanostores";
47941
+ import { atom as atom4 } from "nanostores";
47460
47942
  var buildState, $delegationState, getDelegationState, patchDelegationState, $overlaySectionsOpen, toggleOverlaySection, applyDelegationStatus;
47461
47943
  var init_delegationStore = __esm({
47462
47944
  "src/app/delegationStore.ts"() {
@@ -47467,10 +47949,10 @@ var init_delegationStore = __esm({
47467
47949
  paused: false,
47468
47950
  updatedAt: null
47469
47951
  }), "buildState");
47470
- $delegationState = atom3(buildState());
47952
+ $delegationState = atom4(buildState());
47471
47953
  getDelegationState = /* @__PURE__ */ __name(() => $delegationState.get(), "getDelegationState");
47472
47954
  patchDelegationState = /* @__PURE__ */ __name((next) => $delegationState.set({ ...$delegationState.get(), ...next }), "patchDelegationState");
47473
- $overlaySectionsOpen = atom3({});
47955
+ $overlaySectionsOpen = atom4({});
47474
47956
  toggleOverlaySection = /* @__PURE__ */ __name((title, defaultOpen) => {
47475
47957
  const state = $overlaySectionsOpen.get();
47476
47958
  const current = title in state ? state[title] : defaultOpen;
@@ -47496,7 +47978,7 @@ var init_delegationStore = __esm({
47496
47978
  });
47497
47979
 
47498
47980
  // src/app/spawnHistoryStore.ts
47499
- import { atom as atom4 } from "nanostores";
47981
+ import { atom as atom5 } from "nanostores";
47500
47982
  function summarizeLabel(subagents) {
47501
47983
  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
47984
  return top || `${subagents.length} agent${subagents.length === 1 ? "" : "s"}`;
@@ -47539,8 +48021,8 @@ var init_spawnHistoryStore = __esm({
47539
48021
  "src/app/spawnHistoryStore.ts"() {
47540
48022
  "use strict";
47541
48023
  HISTORY_LIMIT = 10;
47542
- $spawnHistory = atom4([]);
47543
- $spawnDiff = atom4(null);
48024
+ $spawnHistory = atom5([]);
48025
+ $spawnDiff = atom5(null);
47544
48026
  getSpawnHistory = /* @__PURE__ */ __name(() => $spawnHistory.get(), "getSpawnHistory");
47545
48027
  clearDiffPair = /* @__PURE__ */ __name(() => $spawnDiff.set(null), "clearDiffPair");
47546
48028
  setDiffPair = /* @__PURE__ */ __name((pair) => $spawnDiff.set(pair), "setDiffPair");
@@ -47850,15 +48332,15 @@ var init_ops = __esm({
47850
48332
  }
47851
48333
  const [a, b] = parts;
47852
48334
  const history = getSpawnHistory();
47853
- const resolve5 = /* @__PURE__ */ __name((token) => {
48335
+ const resolve6 = /* @__PURE__ */ __name((token) => {
47854
48336
  const n = parseInt(token, 10);
47855
48337
  if (Number.isFinite(n) && n >= 1 && n <= history.length) {
47856
48338
  return history[n - 1] ?? null;
47857
48339
  }
47858
48340
  return null;
47859
48341
  }, "resolve");
47860
- const baseline = resolve5(a);
47861
- const candidate = resolve5(b);
48342
+ const baseline = resolve6(a);
48343
+ const candidate = resolve6(b);
47862
48344
  if (!baseline || !candidate) {
47863
48345
  return ctx.transcript.sys(`replay-diff: could not resolve indices \xB7 history has ${history.length} entries`);
47864
48346
  }
@@ -48731,76 +49213,20 @@ var init_session2 = __esm({
48731
49213
  }
48732
49214
  });
48733
49215
 
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
49216
  // src/app/slash/commands/setup.ts
48786
49217
  var setupCommands;
48787
49218
  var init_setup = __esm({
48788
49219
  "src/app/slash/commands/setup.ts"() {
48789
49220
  "use strict";
48790
- init_entry_exports();
48791
- init_externalCli();
48792
- init_setupHandoff();
49221
+ init_overlayStore();
48793
49222
  setupCommands = [
48794
49223
  {
48795
- help: "run full setup wizard (launches `hermes setup`)",
49224
+ help: "first-run setup help; opens provider connection picker",
48796
49225
  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")
49226
+ run: /* @__PURE__ */ __name((_arg, ctx) => {
49227
+ ctx.transcript.sys("OpenJaw setup: run /connect to set up a provider, then /model to choose a model.");
49228
+ patchOverlayState({ modelPicker: true, modelPickerMode: "connect" });
49229
+ }, "run")
48804
49230
  }
48805
49231
  ];
48806
49232
  }
@@ -48956,17 +49382,856 @@ var init_catalog = __esm({
48956
49382
  }
48957
49383
  });
48958
49384
 
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";
49385
+ // src/workflows/planner.ts
49386
+ function resolveDynamicWorkflowConfig(config) {
49387
+ const raw = config.features?.dynamic_workflows ?? {};
49388
+ return {
49389
+ enabled: raw.enabled ?? DEFAULT_DYNAMIC_WORKFLOW_CONFIG.enabled,
49390
+ plannerMode: "adaptive",
49391
+ hardMaxWorkers: clampInt(raw.hard_max_workers, DEFAULT_DYNAMIC_WORKFLOW_CONFIG.hardMaxWorkers, 1, 1e4),
49392
+ hardMaxConcurrentWorkers: clampInt(
49393
+ raw.hard_max_concurrent_workers,
49394
+ DEFAULT_DYNAMIC_WORKFLOW_CONFIG.hardMaxConcurrentWorkers,
49395
+ 1,
49396
+ 2048
49397
+ ),
49398
+ workerTimeoutMs: clampInt(raw.worker_timeout_ms, DEFAULT_DYNAMIC_WORKFLOW_CONFIG.workerTimeoutMs, 1e4, 36e5),
49399
+ persistHistory: raw.persist_history ?? DEFAULT_DYNAMIC_WORKFLOW_CONFIG.persistHistory
49400
+ };
49401
+ }
49402
+ function initialConcurrency(plannedWorkers, limits) {
49403
+ if (plannedWorkers <= 0) return 0;
49404
+ return Math.min(Math.ceil(Math.sqrt(plannedWorkers) * 2), plannedWorkers, limits.hardMaxConcurrentWorkers);
49405
+ }
49406
+ function adjustConcurrency(current, plannedWorkers, limits, event) {
49407
+ if (plannedWorkers <= 0) return 0;
49408
+ if (event === "rate_limited") return Math.max(1, Math.floor(current / 2));
49409
+ return Math.min(current + 1, plannedWorkers, limits.hardMaxConcurrentWorkers);
49410
+ }
49411
+ function planWorkflow(goal, runId, limits) {
49412
+ const taskGoals = deriveTaskGoals(goal);
49413
+ const specs = [];
49414
+ const maxWorkers = limits.hardMaxWorkers;
49415
+ for (const taskGoal of taskGoals) {
49416
+ if (specs.length >= maxWorkers) break;
49417
+ const id = `${runId}-w${specs.length + 1}`;
49418
+ specs.push({
49419
+ depth: 0,
49420
+ goal: taskGoal,
49421
+ id,
49422
+ index: specs.length,
49423
+ parentId: null,
49424
+ prompt: buildTaskPrompt(goal, taskGoal),
49425
+ role: "task"
49426
+ });
49427
+ }
49428
+ const taskSpecs = [...specs];
49429
+ const verifierEvery = taskSpecs.length >= 8 ? 4 : 3;
49430
+ for (let i = 0; i < taskSpecs.length && specs.length < maxWorkers; i += verifierEvery) {
49431
+ const batch = taskSpecs.slice(i, i + verifierEvery);
49432
+ const id = `${runId}-w${specs.length + 1}`;
49433
+ specs.push({
49434
+ depth: 1,
49435
+ dependsOn: batch.map((item) => item.id),
49436
+ goal: `Verify findings from workers ${batch.map((item) => item.index + 1).join(", ")}`,
49437
+ id,
49438
+ index: specs.length,
49439
+ parentId: batch[0]?.id ?? null,
49440
+ prompt: buildVerifierPrompt(goal, batch),
49441
+ role: "verifier"
49442
+ });
49443
+ }
49444
+ return { concurrency: initialConcurrency(specs.length, limits), specs };
49445
+ }
49446
+ function deriveTaskGoals(goal) {
49447
+ const explicit = extractExplicitTasks(goal);
49448
+ const words = goal.trim().split(/\s+/).filter(Boolean).length;
49449
+ const complexity = Math.min(12, Math.max(3, Math.ceil(words / 35)));
49450
+ const inferred = inferLanes(goal);
49451
+ const seeds = explicit.length > 1 ? explicit : inferred;
49452
+ const out = [];
49453
+ for (const item of seeds) {
49454
+ pushUnique(out, item);
49455
+ }
49456
+ while (out.length < complexity) {
49457
+ const next = DEFAULT_LANES[out.length % DEFAULT_LANES.length];
49458
+ pushUnique(out, next);
49459
+ if (out.length >= DEFAULT_LANES.length && explicit.length <= 1) break;
49460
+ }
49461
+ return out.slice(0, Math.max(1, out.length));
49462
+ }
49463
+ function extractExplicitTasks(goal) {
49464
+ return goal.split(/\r?\n/).map((line) => line.trim().replace(/^[-*+]\s+/, "").replace(/^\d+[.)]\s+/, "").trim()).filter((line) => line.length >= 12);
49465
+ }
49466
+ function inferLanes(goal) {
49467
+ const lower = goal.toLowerCase();
49468
+ const lanes = ["Map the current state and locate relevant files or evidence"];
49469
+ if (/code|repo|branch|test|build|bug|feature|implement/.test(lower)) {
49470
+ lanes.push("Review implementation risks, edge cases, and integration points");
49471
+ lanes.push("Inspect validation strategy and likely test coverage");
49472
+ }
49473
+ if (/research|latest|current|external|claude|compare|similar/.test(lower)) {
49474
+ lanes.push("Research external context and comparable workflow behavior");
49475
+ }
49476
+ if (/ui|status|overlay|navigate|progress|worker/.test(lower)) {
49477
+ lanes.push("Evaluate user experience, status visibility, and navigation requirements");
49478
+ }
49479
+ if (/security|safe|permission|mutat|write|approval/.test(lower)) {
49480
+ lanes.push("Analyze safety boundaries, permissions, and failure modes");
49481
+ }
49482
+ lanes.push("Draft concrete recommendations and acceptance criteria");
49483
+ return lanes;
49484
+ }
49485
+ function pushUnique(items, value) {
49486
+ const normalized = value.trim();
49487
+ if (!normalized) return;
49488
+ if (!items.some((item) => item.toLowerCase() === normalized.toLowerCase())) items.push(normalized);
49489
+ }
49490
+ function buildTaskPrompt(overallGoal, taskGoal) {
49491
+ return [
49492
+ `Overall workflow goal: ${overallGoal}`,
49493
+ `Your assigned read-only worker goal: ${taskGoal}`,
49494
+ "",
49495
+ "Work independently. Use only read/search/analysis tools. Do not modify files, send messages, update memory, or run shell/code execution.",
49496
+ "Return concise findings with evidence, confidence, and open questions."
49497
+ ].join("\n");
49498
+ }
49499
+ function buildVerifierPrompt(overallGoal, batch) {
49500
+ return [
49501
+ `Overall workflow goal: ${overallGoal}`,
49502
+ `Verify the likely claims and gaps for these worker lanes: ${batch.map((item) => item.goal).join("; ")}`,
49503
+ "",
49504
+ "Use read-only checks. Label findings as verified, partially verified, or unverified. Do not modify anything."
49505
+ ].join("\n");
49506
+ }
49507
+ var DEFAULT_DYNAMIC_WORKFLOW_CONFIG, clampInt, DEFAULT_LANES;
49508
+ var init_planner = __esm({
49509
+ "src/workflows/planner.ts"() {
49510
+ "use strict";
49511
+ DEFAULT_DYNAMIC_WORKFLOW_CONFIG = {
49512
+ enabled: true,
49513
+ plannerMode: "adaptive",
49514
+ hardMaxWorkers: 1024,
49515
+ hardMaxConcurrentWorkers: 128,
49516
+ workerTimeoutMs: 18e4,
49517
+ persistHistory: true
49518
+ };
49519
+ clampInt = /* @__PURE__ */ __name((value, fallback, min, max) => {
49520
+ const parsed = typeof value === "number" ? value : Number(value);
49521
+ if (!Number.isFinite(parsed)) return fallback;
49522
+ return Math.min(max, Math.max(min, Math.floor(parsed)));
49523
+ }, "clampInt");
49524
+ __name(resolveDynamicWorkflowConfig, "resolveDynamicWorkflowConfig");
49525
+ __name(initialConcurrency, "initialConcurrency");
49526
+ __name(adjustConcurrency, "adjustConcurrency");
49527
+ __name(planWorkflow, "planWorkflow");
49528
+ __name(deriveTaskGoals, "deriveTaskGoals");
49529
+ __name(extractExplicitTasks, "extractExplicitTasks");
49530
+ __name(inferLanes, "inferLanes");
49531
+ DEFAULT_LANES = [
49532
+ "Map the current state and locate relevant files or evidence",
49533
+ "Review implementation risks, edge cases, and integration points",
49534
+ "Inspect validation strategy and likely test coverage",
49535
+ "Analyze safety boundaries, permissions, and failure modes",
49536
+ "Draft concrete recommendations and acceptance criteria"
49537
+ ];
49538
+ __name(pushUnique, "pushUnique");
49539
+ __name(buildTaskPrompt, "buildTaskPrompt");
49540
+ __name(buildVerifierPrompt, "buildVerifierPrompt");
49541
+ }
49542
+ });
49543
+
49544
+ // src/workflows/persistence.ts
49545
+ import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync7, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
48963
49546
  import { homedir as homedir28 } from "node:os";
48964
- import { basename as basename3, extname as extname4, join as join42 } from "node:path";
49547
+ import { basename as basename3, join as join42, resolve as resolve4 } from "node:path";
49548
+ function ensureDir(path3) {
49549
+ if (!existsSync31(path3)) mkdirSync17(path3, { recursive: true });
49550
+ }
49551
+ function safeSegment(value) {
49552
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 80) || "default";
49553
+ }
49554
+ function readJson(path3) {
49555
+ try {
49556
+ return JSON.parse(readFileSync28(path3, "utf8"));
49557
+ } catch {
49558
+ return null;
49559
+ }
49560
+ }
49561
+ function saveWorkflowSnapshot(snapshot) {
49562
+ ensureDir(workflowDir());
49563
+ const path3 = join42(workflowDir(), `${safeSegment(snapshot.id)}.json`);
49564
+ writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
49565
+ `, "utf8");
49566
+ return path3;
49567
+ }
49568
+ function loadWorkflowSnapshot(id) {
49569
+ const path3 = join42(workflowDir(), `${safeSegment(id)}.json`);
49570
+ return readJson(path3);
49571
+ }
49572
+ function listWorkflowSnapshots(limit = 30) {
49573
+ if (!existsSync31(workflowDir())) return [];
49574
+ const entries = [];
49575
+ for (const entry of readdirSync7(workflowDir(), { withFileTypes: true })) {
49576
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
49577
+ const path3 = join42(workflowDir(), entry.name);
49578
+ const snapshot = readJson(path3);
49579
+ if (!snapshot) continue;
49580
+ entries.push({
49581
+ finishedAt: snapshot.finishedAt,
49582
+ goal: snapshot.goal,
49583
+ id: snapshot.id,
49584
+ path: path3,
49585
+ plannedWorkerCount: snapshot.plannedWorkerCount,
49586
+ startedAt: snapshot.startedAt,
49587
+ status: snapshot.status,
49588
+ workerCount: snapshot.workers.length
49589
+ });
49590
+ }
49591
+ return entries.sort((a, b) => (b.finishedAt ?? b.startedAt) - (a.finishedAt ?? a.startedAt)).slice(0, Math.max(1, limit));
49592
+ }
49593
+ function saveSpawnTreeSnapshot(input) {
49594
+ const sessionId = safeSegment(input.session_id ?? "default");
49595
+ const dir2 = join42(spawnTreeDir(), sessionId);
49596
+ ensureDir(dir2);
49597
+ const stamp = new Date((input.finished_at ?? Date.now() / 1e3) * 1e3).toISOString().replace(/[:.]/g, "-");
49598
+ const path3 = join42(dir2, `${stamp}-${Math.random().toString(36).slice(2, 8)}.json`);
49599
+ const snapshot = {
49600
+ count: input.subagents?.length ?? 0,
49601
+ finished_at: input.finished_at,
49602
+ label: input.label,
49603
+ session_id: input.session_id,
49604
+ started_at: input.started_at,
49605
+ subagents: input.subagents ?? []
49606
+ };
49607
+ writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
49608
+ `, "utf8");
49609
+ return path3;
49610
+ }
49611
+ function listSpawnTreeSnapshots(sessionId = "default", limit = 30) {
49612
+ const dir2 = join42(spawnTreeDir(), safeSegment(sessionId));
49613
+ if (!existsSync31(dir2)) return [];
49614
+ const entries = [];
49615
+ for (const entry of readdirSync7(dir2, { withFileTypes: true })) {
49616
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
49617
+ const path3 = join42(dir2, entry.name);
49618
+ const snapshot = readJson(path3);
49619
+ if (!snapshot) continue;
49620
+ entries.push({
49621
+ count: snapshot.count,
49622
+ finished_at: snapshot.finished_at,
49623
+ label: snapshot.label,
49624
+ path: path3,
49625
+ session_id: snapshot.session_id,
49626
+ started_at: snapshot.started_at
49627
+ });
49628
+ }
49629
+ return entries.sort((a, b) => (b.finished_at ?? 0) - (a.finished_at ?? 0)).slice(0, Math.max(1, limit));
49630
+ }
49631
+ function loadSpawnTreeSnapshot(path3) {
49632
+ const root = resolve4(spawnTreeDir());
49633
+ const resolved = resolve4(path3);
49634
+ if (!resolved.startsWith(root)) return null;
49635
+ const snapshot = readJson(resolved);
49636
+ return snapshot ? { ...snapshot, path: resolved } : null;
49637
+ }
49638
+ var rootDir, workflowDir, spawnTreeDir;
49639
+ var init_persistence = __esm({
49640
+ "src/workflows/persistence.ts"() {
49641
+ "use strict";
49642
+ rootDir = /* @__PURE__ */ __name(() => join42(homedir28(), ".openjaw-agent"), "rootDir");
49643
+ workflowDir = /* @__PURE__ */ __name(() => join42(rootDir(), "workflows"), "workflowDir");
49644
+ spawnTreeDir = /* @__PURE__ */ __name(() => join42(rootDir(), "spawn-trees"), "spawnTreeDir");
49645
+ __name(ensureDir, "ensureDir");
49646
+ __name(safeSegment, "safeSegment");
49647
+ __name(readJson, "readJson");
49648
+ __name(saveWorkflowSnapshot, "saveWorkflowSnapshot");
49649
+ __name(loadWorkflowSnapshot, "loadWorkflowSnapshot");
49650
+ __name(listWorkflowSnapshots, "listWorkflowSnapshots");
49651
+ __name(saveSpawnTreeSnapshot, "saveSpawnTreeSnapshot");
49652
+ __name(listSpawnTreeSnapshots, "listSpawnTreeSnapshots");
49653
+ __name(loadSpawnTreeSnapshot, "loadSpawnTreeSnapshot");
49654
+ }
49655
+ });
49656
+
49657
+ // src/workflows/readOnlyTools.ts
49658
+ function isWorkflowReadOnlyTool(name) {
49659
+ return READ_ONLY_TOOLS.has(name);
49660
+ }
49661
+ var READ_ONLY_TOOLS, ReadOnlyToolRuntime;
49662
+ var init_readOnlyTools = __esm({
49663
+ "src/workflows/readOnlyTools.ts"() {
49664
+ "use strict";
49665
+ READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
49666
+ "browser_snapshot",
49667
+ "browser_extract",
49668
+ "file_info",
49669
+ "file_list",
49670
+ "file_read",
49671
+ "glob",
49672
+ "grep",
49673
+ "image_view",
49674
+ "web_extract",
49675
+ "web_fetch",
49676
+ "web_search"
49677
+ ]);
49678
+ __name(isWorkflowReadOnlyTool, "isWorkflowReadOnlyTool");
49679
+ ReadOnlyToolRuntime = class {
49680
+ constructor(inner) {
49681
+ this.inner = inner;
49682
+ }
49683
+ inner;
49684
+ static {
49685
+ __name(this, "ReadOnlyToolRuntime");
49686
+ }
49687
+ listTools() {
49688
+ return this.inner.listTools().filter((tool) => isWorkflowReadOnlyTool(tool.name));
49689
+ }
49690
+ async execute(name, input) {
49691
+ if (!isWorkflowReadOnlyTool(name)) {
49692
+ throw new Error(`Tool ${name} is not available in advisory workflow workers`);
49693
+ }
49694
+ return await this.inner.execute(name, input);
49695
+ }
49696
+ };
49697
+ }
49698
+ });
49699
+
49700
+ // src/workflows/manager.ts
49701
+ import { randomUUID as randomUUID13 } from "node:crypto";
49702
+ function buildPlannerSystemPrompt(limits) {
49703
+ return [
49704
+ "You are the OpenJaw dynamic workflow planner.",
49705
+ "Return ONLY valid JSON. Do not include markdown fences or prose.",
49706
+ "Create a task graph for advisory read-only worker agents. Do not answer the user task.",
49707
+ "Use enough workers to represent genuinely different research lanes, viewpoints, critics, verifiers, and a final synthesizer. Do not default to a small fixed count.",
49708
+ "For panel-review prompts, create separate panelist workers for each requested philosophy and a critic/risk worker before the synthesizer.",
49709
+ `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.`,
49710
+ '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"]}]}',
49711
+ "Always include exactly one synthesizer worker that depends on the most important research, panelist, critic, and verifier workers."
49712
+ ].join("\n");
49713
+ }
49714
+ function parseModelPlan(content, goal, runId, limits) {
49715
+ const parsed = JSON.parse(extractJsonObject(content));
49716
+ const workers = Array.isArray(parsed.workers) ? parsed.workers : [];
49717
+ const specs = [];
49718
+ const usedIds = /* @__PURE__ */ new Set();
49719
+ for (const worker of workers.slice(0, limits.hardMaxWorkers)) {
49720
+ const rawGoal = String(worker.goal ?? "").trim();
49721
+ if (!rawGoal) continue;
49722
+ const id = uniqueWorkerId(runId, worker.id || rawGoal, usedIds, specs.length + 1);
49723
+ const role = normalizeRole(worker.role);
49724
+ const dependsOn = Array.isArray(worker.depends_on) ? worker.depends_on.map(String).map((value) => scopedWorkerId(runId, value)).filter((value) => usedIds.has(value)) : [];
49725
+ specs.push({
49726
+ depth: dependsOn.length > 0 || role !== "task" ? 1 : 0,
49727
+ dependsOn,
49728
+ goal: rawGoal,
49729
+ id,
49730
+ index: specs.length,
49731
+ parentId: dependsOn[0] ?? null,
49732
+ prompt: buildModelWorkerPrompt(goal, rawGoal, worker.role, worker.philosophy, parsed.language),
49733
+ role
49734
+ });
49735
+ }
49736
+ return ensureSynthesizer({ concurrency: 0, specs }, goal, runId, limits);
49737
+ }
49738
+ function ensureSynthesizer(plan, goal, runId, limits) {
49739
+ const specs = plan.specs.slice(0, limits.hardMaxWorkers);
49740
+ const existing = specs.find((spec) => spec.role === "synthesizer");
49741
+ const dependencies = specs.filter((spec) => spec.role !== "synthesizer").map((spec) => spec.id);
49742
+ if (existing) {
49743
+ existing.dependsOn = existing.dependsOn?.length ? existing.dependsOn : dependencies;
49744
+ existing.depth = Math.max(existing.depth, 1);
49745
+ existing.parentId = existing.dependsOn[0] ?? null;
49746
+ } else if (specs.length < limits.hardMaxWorkers) {
49747
+ specs.push({
49748
+ depth: 1,
49749
+ dependsOn: dependencies,
49750
+ goal: "Synthesize the panel and research outputs into the final answer",
49751
+ id: `${runId}-synthesizer`,
49752
+ index: specs.length,
49753
+ parentId: dependencies[0] ?? null,
49754
+ prompt: buildModelWorkerPrompt(goal, "Synthesize all completed worker outputs into the final answer for the user.", "synthesizer", void 0, void 0),
49755
+ role: "synthesizer"
49756
+ });
49757
+ }
49758
+ return { concurrency: Math.min(Math.ceil(Math.sqrt(specs.length) * 2), specs.length, limits.hardMaxConcurrentWorkers), specs };
49759
+ }
49760
+ function buildModelWorkerPrompt(overallGoal, workerGoal, role, philosophy, language) {
49761
+ return [
49762
+ `Overall workflow goal: ${overallGoal}`,
49763
+ `Worker role: ${role || "researcher"}`,
49764
+ philosophy ? `Investment/work philosophy: ${philosophy}` : "",
49765
+ language ? `Final language preference: ${language}` : "",
49766
+ `Assigned goal: ${workerGoal}`,
49767
+ "",
49768
+ "Use only read/search/analysis tools. Do not modify files, send messages, update memory, or run shell/code execution.",
49769
+ "If prior worker outputs are provided below, use them as context and explicitly resolve agreements, disagreements, and uncertainty.",
49770
+ "Return evidence-backed findings. A synthesizer must produce the final answer directly for the user."
49771
+ ].filter(Boolean).join("\n");
49772
+ }
49773
+ function normalizeRole(role) {
49774
+ const lower = String(role ?? "").toLowerCase();
49775
+ if (lower.includes("synth") || lower.includes("chair")) return "synthesizer";
49776
+ if (lower.includes("verify") || lower.includes("critic") || lower.includes("risk")) return "verifier";
49777
+ return "task";
49778
+ }
49779
+ function extractJsonObject(content) {
49780
+ const trimmed = content.trim().replace(/^```(?:json)?\s*/i, "").replace(/```$/i, "").trim();
49781
+ const start = trimmed.indexOf("{");
49782
+ const end = trimmed.lastIndexOf("}");
49783
+ if (start < 0 || end < start) throw new Error("planner response did not contain a JSON object");
49784
+ return trimmed.slice(start, end + 1);
49785
+ }
49786
+ function uniqueWorkerId(runId, value, used, index) {
49787
+ const base = scopedWorkerId(runId, value) || `${runId}-w${index}`;
49788
+ let candidate = base;
49789
+ let suffix = 2;
49790
+ while (used.has(candidate)) {
49791
+ candidate = `${base}-${suffix}`;
49792
+ suffix += 1;
49793
+ }
49794
+ used.add(candidate);
49795
+ return candidate;
49796
+ }
49797
+ function scopedWorkerId(runId, value) {
49798
+ const slug = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
49799
+ return slug.startsWith(`${runId}-`) ? slug : `${runId}-${slug || "worker"}`;
49800
+ }
49801
+ function firstParagraph(value) {
49802
+ return value.split(/\n\s*\n/)[0]?.trim().slice(0, 800) || value.trim().slice(0, 800);
49803
+ }
49804
+ function inferVerificationState(value) {
49805
+ const lower = value.toLowerCase();
49806
+ if (lower.includes("partially verified") || lower.includes("partial")) return "partial";
49807
+ if (lower.includes("unverified") || lower.includes("not verified")) return "unverified";
49808
+ if (lower.includes("verified")) return "verified";
49809
+ return "pending";
49810
+ }
49811
+ function dependencyOutputContext(run, spec) {
49812
+ const dependencyIds = spec.dependsOn ?? [];
49813
+ if (dependencyIds.length === 0) return "";
49814
+ const blocks = dependencyIds.map((id) => run.workers.find((worker) => worker.id === id)).filter((worker) => Boolean(worker)).map((worker) => [
49815
+ `## Worker ${worker.id}`,
49816
+ `Role: ${worker.workerRole ?? "task"}`,
49817
+ `Goal: ${worker.goal}`,
49818
+ `Status: ${worker.status}`,
49819
+ worker.summary ? `Summary: ${worker.summary}` : "",
49820
+ worker.details ? `Details:
49821
+ ${worker.details}` : ""
49822
+ ].filter(Boolean).join("\n"));
49823
+ return blocks.length ? `# Prior worker outputs
49824
+ ${blocks.join("\n\n")}` : "";
49825
+ }
49826
+ var terminal, WorkflowManager;
49827
+ var init_manager = __esm({
49828
+ "src/workflows/manager.ts"() {
49829
+ "use strict";
49830
+ init_agent_loop();
49831
+ init_providers();
49832
+ init_planner();
49833
+ init_persistence();
49834
+ init_readOnlyTools();
49835
+ terminal = /* @__PURE__ */ new Set(["cancelled", "completed", "failed"]);
49836
+ WorkflowManager = class {
49837
+ constructor(config, toolRuntime, systemPromptFn, bus, options = {}) {
49838
+ this.config = config;
49839
+ this.toolRuntime = toolRuntime;
49840
+ this.systemPromptFn = systemPromptFn;
49841
+ this.bus = bus;
49842
+ this.planner = options.planner;
49843
+ this.runner = options.runner ?? this.defaultRunner;
49844
+ }
49845
+ config;
49846
+ toolRuntime;
49847
+ systemPromptFn;
49848
+ bus;
49849
+ static {
49850
+ __name(this, "WorkflowManager");
49851
+ }
49852
+ runs = /* @__PURE__ */ new Map();
49853
+ planner;
49854
+ runner;
49855
+ async start(goal, sessionId) {
49856
+ const limits = resolveDynamicWorkflowConfig(this.config);
49857
+ if (!limits.enabled) {
49858
+ throw new Error("dynamic workflows are disabled in config");
49859
+ }
49860
+ const id = `wf-${randomUUID13().slice(0, 8)}`;
49861
+ const plan = await this.buildPlan(goal, id, limits);
49862
+ const now2 = Date.now();
49863
+ const run = {
49864
+ abortController: new AbortController(),
49865
+ activeAborts: /* @__PURE__ */ new Map(),
49866
+ concurrency: plan.concurrency,
49867
+ config: limits,
49868
+ goal,
49869
+ id,
49870
+ plannedWorkerCount: plan.specs.length,
49871
+ startedAt: now2,
49872
+ status: "running",
49873
+ workers: plan.specs.map((spec) => this.workerFromSpec(spec, id, plan.specs.length, now2))
49874
+ };
49875
+ this.runs.set(id, run);
49876
+ this.emitWorkflow("workflow.start", run, sessionId);
49877
+ for (const worker of run.workers) {
49878
+ this.emitSubagent("subagent.spawn_requested", run, worker, sessionId);
49879
+ }
49880
+ void this.executeRun(run, plan.specs, sessionId);
49881
+ return { run: this.snapshot(run), started: true };
49882
+ }
49883
+ status(id) {
49884
+ const run = id ? this.runs.get(id) : this.latestRun();
49885
+ if (run) return this.snapshot(run);
49886
+ if (id) return loadWorkflowSnapshot(id);
49887
+ const first = listWorkflowSnapshots(1)[0];
49888
+ return first ? loadWorkflowSnapshot(first.id) : null;
49889
+ }
49890
+ list(limit = 30) {
49891
+ const live = [...this.runs.values()].map((run) => ({
49892
+ finishedAt: run.finishedAt,
49893
+ goal: run.goal,
49894
+ id: run.id,
49895
+ plannedWorkerCount: run.plannedWorkerCount,
49896
+ startedAt: run.startedAt,
49897
+ status: run.status,
49898
+ workerCount: run.workers.length
49899
+ }));
49900
+ const persisted = listWorkflowSnapshots(limit);
49901
+ const seen = new Set(live.map((entry) => entry.id));
49902
+ 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));
49903
+ }
49904
+ cancel(id) {
49905
+ const run = this.runs.get(id);
49906
+ if (run) {
49907
+ this.cancelRun(run);
49908
+ return { found: true, run: this.snapshot(run) };
49909
+ }
49910
+ for (const candidate of this.runs.values()) {
49911
+ const worker = candidate.workers.find((item) => item.id === id);
49912
+ if (!worker) continue;
49913
+ candidate.activeAborts.get(worker.id)?.();
49914
+ this.updateWorker(candidate, worker.id, {
49915
+ currentStep: "cancel requested",
49916
+ status: "interrupted"
49917
+ });
49918
+ return { found: true, run: this.snapshot(candidate), workerId: worker.id };
49919
+ }
49920
+ return { found: false };
49921
+ }
49922
+ async buildPlan(goal, runId, limits) {
49923
+ if (this.planner) {
49924
+ return ensureSynthesizer(await this.planner(goal, runId, limits), goal, runId, limits);
49925
+ }
49926
+ try {
49927
+ const provider = createProvider(this.config);
49928
+ const controller = new AbortController();
49929
+ const timer = setTimeout(() => controller.abort(), Math.min(6e4, limits.workerTimeoutMs));
49930
+ try {
49931
+ const result = await provider.chat({
49932
+ systemPrompt: buildPlannerSystemPrompt(limits),
49933
+ messages: [{ role: "user", content: goal }],
49934
+ tools: [],
49935
+ signal: controller.signal
49936
+ });
49937
+ const parsed = parseModelPlan(result.text ?? "", goal, runId, limits);
49938
+ if (parsed.specs.length > 0) return parsed;
49939
+ } finally {
49940
+ clearTimeout(timer);
49941
+ }
49942
+ } catch {
49943
+ }
49944
+ return ensureSynthesizer(planWorkflow(goal, runId, limits), goal, runId, limits);
49945
+ }
49946
+ async executeRun(run, specs, sessionId) {
49947
+ const queue = [...specs];
49948
+ const active = /* @__PURE__ */ new Map();
49949
+ let stableCompletions = 0;
49950
+ try {
49951
+ while ((queue.length > 0 || active.size > 0) && !run.abortController.signal.aborted) {
49952
+ const ready = queue.filter((spec) => this.dependenciesFinished(run, spec));
49953
+ while (ready.length > 0 && active.size < run.concurrency && !run.abortController.signal.aborted) {
49954
+ const spec = ready.shift();
49955
+ queue.splice(queue.indexOf(spec), 1);
49956
+ const promise = this.executeWorker(run, spec, sessionId).then(() => {
49957
+ stableCompletions += 1;
49958
+ if (stableCompletions % Math.max(2, run.concurrency) === 0) {
49959
+ run.concurrency = adjustConcurrency(run.concurrency, run.plannedWorkerCount, run.config, "stable_completion");
49960
+ }
49961
+ }).catch((err) => {
49962
+ const message = err instanceof Error ? err.message : String(err);
49963
+ if (/rate|429|quota|throttle/i.test(message)) {
49964
+ run.concurrency = adjustConcurrency(run.concurrency, run.plannedWorkerCount, run.config, "rate_limited");
49965
+ }
49966
+ }).finally(() => active.delete(spec.id));
49967
+ active.set(spec.id, promise);
49968
+ }
49969
+ if (active.size === 0) break;
49970
+ await Promise.race(active.values());
49971
+ }
49972
+ if (run.abortController.signal.aborted) {
49973
+ for (const worker of run.workers) {
49974
+ if (worker.status === "queued" || worker.status === "running") {
49975
+ this.updateWorker(run, worker.id, { currentStep: "cancelled", status: "interrupted" });
49976
+ }
49977
+ }
49978
+ run.status = "cancelled";
49979
+ run.summary = this.summarize(run, "cancelled");
49980
+ } else if (run.workers.some((worker) => worker.status === "failed")) {
49981
+ run.status = "failed";
49982
+ run.summary = this.summarize(run, "failed");
49983
+ } else {
49984
+ run.status = "completed";
49985
+ run.summary = this.summarize(run, "completed");
49986
+ }
49987
+ } catch (err) {
49988
+ run.status = "failed";
49989
+ run.summary = `Workflow failed: ${err instanceof Error ? err.message : String(err)}`;
49990
+ } finally {
49991
+ run.finishedAt = Date.now();
49992
+ if (run.config.persistHistory) saveWorkflowSnapshot(this.snapshot(run));
49993
+ this.emitWorkflow(run.status === "failed" ? "workflow.error" : "workflow.complete", run, sessionId);
49994
+ }
49995
+ }
49996
+ async executeWorker(run, spec, sessionId) {
49997
+ if (run.abortController.signal.aborted) return;
49998
+ this.updateWorker(run, spec.id, {
49999
+ currentStep: "starting",
50000
+ startedAt: Date.now(),
50001
+ status: "running"
50002
+ });
50003
+ this.emitSubagent("subagent.start", run, this.worker(run, spec.id), sessionId);
50004
+ const controller = new AbortController();
50005
+ const timeoutMs = spec.role === "synthesizer" ? run.config.workerTimeoutMs * 3 : run.config.workerTimeoutMs;
50006
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
50007
+ run.activeAborts.set(spec.id, () => controller.abort());
50008
+ const signal = AbortSignal.any([controller.signal, run.abortController.signal]);
50009
+ try {
50010
+ const result = await this.runner({
50011
+ config: this.config,
50012
+ run: this.snapshot(run),
50013
+ signal,
50014
+ spec,
50015
+ systemPromptFn: this.systemPromptFn,
50016
+ toolRuntime: new ReadOnlyToolRuntime(this.toolRuntime),
50017
+ update: /* @__PURE__ */ __name((patch) => {
50018
+ this.updateWorker(run, spec.id, patch);
50019
+ this.emitSubagent("subagent.progress", run, this.worker(run, spec.id), sessionId);
50020
+ }, "update")
50021
+ });
50022
+ this.updateWorker(run, spec.id, {
50023
+ currentStep: "complete",
50024
+ details: result,
50025
+ durationSeconds: (Date.now() - (this.worker(run, spec.id).startedAt ?? Date.now())) / 1e3,
50026
+ status: signal.aborted ? "interrupted" : "completed",
50027
+ summary: firstParagraph(result),
50028
+ verificationState: spec.role === "verifier" ? inferVerificationState(result) : "not_applicable"
50029
+ });
50030
+ } catch (err) {
50031
+ const message = err instanceof Error ? err.message : String(err);
50032
+ this.updateWorker(run, spec.id, {
50033
+ currentStep: signal.aborted ? "interrupted" : "failed",
50034
+ details: message,
50035
+ durationSeconds: (Date.now() - (this.worker(run, spec.id).startedAt ?? Date.now())) / 1e3,
50036
+ status: signal.aborted ? "interrupted" : "failed",
50037
+ summary: message,
50038
+ verificationState: "unverified"
50039
+ });
50040
+ if (!signal.aborted) throw err;
50041
+ } finally {
50042
+ clearTimeout(timeout);
50043
+ run.activeAborts.delete(spec.id);
50044
+ this.emitSubagent("subagent.complete", run, this.worker(run, spec.id), sessionId);
50045
+ if (run.config.persistHistory) saveWorkflowSnapshot(this.snapshot(run));
50046
+ }
50047
+ }
50048
+ defaultRunner = /* @__PURE__ */ __name(async (ctx) => {
50049
+ const workerConfig = {
50050
+ ...ctx.config,
50051
+ features: { ...ctx.config.features, skill_auto_suggest: false }
50052
+ };
50053
+ const loop = new AgentLoop(workerConfig, ctx.toolRuntime);
50054
+ const sections = await ctx.systemPromptFn();
50055
+ const systemPrompt = [
50056
+ ...sections,
50057
+ "# Advisory Workflow Worker Rules",
50058
+ "You are a read-only workflow worker. Never modify files, execute shell/code, send messages, update memory, or perform browser actions that change state.",
50059
+ "Use available read/search tools only. Return concise evidence-backed findings."
50060
+ ].filter(Boolean).join("\n\n");
50061
+ ctx.signal.addEventListener("abort", () => loop.abort(), { once: true });
50062
+ let answer = "";
50063
+ let streamedText = "";
50064
+ let outputTail = [];
50065
+ let toolCount = 0;
50066
+ const userPrompt = [ctx.spec.prompt, dependencyOutputContext(ctx.run, ctx.spec)].filter(Boolean).join("\n\n");
50067
+ for await (const chunk of loop.run(userPrompt, systemPrompt)) {
50068
+ if (ctx.signal.aborted) {
50069
+ loop.abort();
50070
+ break;
50071
+ }
50072
+ if (chunk.type === "thinking" && chunk.content.trim()) {
50073
+ streamedText += chunk.content;
50074
+ ctx.update({ currentStep: chunk.content.trim().slice(0, 180), thinking: [chunk.content.trim().slice(0, 500)] });
50075
+ }
50076
+ if (chunk.type === "tool_call") {
50077
+ toolCount += 1;
50078
+ ctx.update({ currentStep: `using ${chunk.toolName ?? "tool"}`, toolCount, tools: [`${chunk.toolName ?? "tool"}(...)`] });
50079
+ }
50080
+ if (chunk.type === "tool_result") {
50081
+ outputTail = [...outputTail, { isError: /error/i.test(chunk.content), preview: chunk.content.slice(0, 240), tool: chunk.toolName ?? "tool" }].slice(-8);
50082
+ ctx.update({ outputTail });
50083
+ }
50084
+ if (chunk.type === "answer" && chunk.content.trim()) answer = chunk.content.trim();
50085
+ }
50086
+ const result = answer || streamedText.trim();
50087
+ if (ctx.signal.aborted) {
50088
+ if (result) {
50089
+ return `${result}
50090
+
50091
+ [Partial output preserved after worker interruption.]`;
50092
+ }
50093
+ throw new Error("worker interrupted");
50094
+ }
50095
+ return result || "Worker completed with no final answer.";
50096
+ }, "defaultRunner");
50097
+ workerFromSpec(spec, workflowId, taskCount, now2) {
50098
+ return {
50099
+ currentStep: "queued",
50100
+ depth: spec.depth,
50101
+ goal: spec.goal,
50102
+ id: spec.id,
50103
+ index: spec.index,
50104
+ notes: [],
50105
+ parentId: spec.parentId,
50106
+ startedAt: now2,
50107
+ status: "queued",
50108
+ taskCount,
50109
+ thinking: [],
50110
+ toolCount: 0,
50111
+ tools: [],
50112
+ verificationState: spec.role === "verifier" ? "pending" : "not_applicable",
50113
+ workflowId,
50114
+ workerRole: spec.role
50115
+ };
50116
+ }
50117
+ dependenciesFinished(run, spec) {
50118
+ return (spec.dependsOn ?? []).every((id) => {
50119
+ const status = run.workers.find((worker) => worker.id === id)?.status;
50120
+ return status === "completed" || status === "failed" || status === "interrupted";
50121
+ });
50122
+ }
50123
+ updateWorker(run, id, patch) {
50124
+ run.workers = run.workers.map((worker) => worker.id === id ? { ...worker, ...patch } : worker);
50125
+ }
50126
+ worker(run, id) {
50127
+ return run.workers.find((worker) => worker.id === id) ?? run.workers[0];
50128
+ }
50129
+ snapshot(run) {
50130
+ return {
50131
+ concurrency: run.concurrency,
50132
+ finishedAt: run.finishedAt,
50133
+ goal: run.goal,
50134
+ id: run.id,
50135
+ plannedWorkerCount: run.plannedWorkerCount,
50136
+ startedAt: run.startedAt,
50137
+ status: run.status,
50138
+ summary: run.summary,
50139
+ workers: run.workers.map((worker) => ({ ...worker }))
50140
+ };
50141
+ }
50142
+ latestRun() {
50143
+ const runs = [...this.runs.values()].sort((a, b) => b.startedAt - a.startedAt);
50144
+ return runs[0] ?? null;
50145
+ }
50146
+ cancelRun(run) {
50147
+ if (terminal.has(run.status)) return;
50148
+ run.abortController.abort();
50149
+ for (const abort of run.activeAborts.values()) abort();
50150
+ run.status = "cancelled";
50151
+ }
50152
+ summarize(run, status) {
50153
+ const synthesizer = run.workers.find((worker) => worker.workerRole === "synthesizer" && worker.details && worker.details !== "worker interrupted");
50154
+ if ((status === "completed" || status === "cancelled" || status === "failed") && synthesizer?.details) {
50155
+ return [synthesizer.details, "No files were modified by advisory workflow workers."].join("\n\n");
50156
+ }
50157
+ const counts = run.workers.reduce((acc, worker) => {
50158
+ acc[worker.status] = (acc[worker.status] ?? 0) + 1;
50159
+ return acc;
50160
+ }, {});
50161
+ const highlights = run.workers.filter((worker) => worker.summary).slice(0, 8).map((worker) => `- ${worker.goal}: ${worker.summary}`).join("\n");
50162
+ return [
50163
+ `Workflow ${status}: ${run.goal}`,
50164
+ `Workers: ${run.workers.length} planned \xB7 ${counts.completed ?? 0} completed \xB7 ${counts.failed ?? 0} failed \xB7 ${counts.interrupted ?? 0} interrupted.`,
50165
+ "No files were modified by advisory workflow workers.",
50166
+ highlights ? `
50167
+ Key worker summaries:
50168
+ ${highlights}` : ""
50169
+ ].filter(Boolean).join("\n");
50170
+ }
50171
+ emitWorkflow(type, run, sessionId) {
50172
+ this.bus.emitEvent({
50173
+ payload: this.snapshot(run),
50174
+ session_id: sessionId ?? void 0,
50175
+ type
50176
+ });
50177
+ }
50178
+ emitSubagent(type, run, worker, sessionId) {
50179
+ this.bus.emitEvent({
50180
+ payload: {
50181
+ cost_usd: worker.costUsd,
50182
+ current_step: worker.currentStep,
50183
+ depth: worker.depth,
50184
+ details: worker.details,
50185
+ duration_seconds: worker.durationSeconds,
50186
+ files_read: worker.filesRead,
50187
+ files_written: worker.filesWritten,
50188
+ goal: worker.goal,
50189
+ input_tokens: worker.inputTokens,
50190
+ output_tail: worker.outputTail?.map((entry) => ({ is_error: entry.isError, preview: entry.preview, tool: entry.tool })),
50191
+ output_tokens: worker.outputTokens,
50192
+ parent_id: worker.parentId,
50193
+ status: worker.status,
50194
+ subagent_id: worker.id,
50195
+ summary: worker.summary,
50196
+ task_count: worker.taskCount,
50197
+ task_index: worker.index,
50198
+ text: worker.currentStep ?? worker.summary,
50199
+ tool_count: worker.toolCount,
50200
+ verification_state: worker.verificationState,
50201
+ workflow_id: run.id,
50202
+ worker_role: worker.workerRole
50203
+ },
50204
+ session_id: sessionId ?? void 0,
50205
+ type
50206
+ });
50207
+ }
50208
+ };
50209
+ __name(buildPlannerSystemPrompt, "buildPlannerSystemPrompt");
50210
+ __name(parseModelPlan, "parseModelPlan");
50211
+ __name(ensureSynthesizer, "ensureSynthesizer");
50212
+ __name(buildModelWorkerPrompt, "buildModelWorkerPrompt");
50213
+ __name(normalizeRole, "normalizeRole");
50214
+ __name(extractJsonObject, "extractJsonObject");
50215
+ __name(uniqueWorkerId, "uniqueWorkerId");
50216
+ __name(scopedWorkerId, "scopedWorkerId");
50217
+ __name(firstParagraph, "firstParagraph");
50218
+ __name(inferVerificationState, "inferVerificationState");
50219
+ __name(dependencyOutputContext, "dependencyOutputContext");
50220
+ }
50221
+ });
50222
+
50223
+ // src/rpcHandlers.ts
50224
+ import { spawn as spawn6 } from "node:child_process";
50225
+ import { randomUUID as randomUUID14 } from "node:crypto";
50226
+ import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync29, rmSync, statSync as statSync4, writeFileSync as writeFileSync20 } from "node:fs";
50227
+ import { homedir as homedir29 } from "node:os";
50228
+ import { basename as basename4, extname as extname4, join as join43 } from "node:path";
48965
50229
  function registerRpcHandlers(options) {
48966
50230
  const { agentConfig, agentLoop, bridgeEmitter, bridgeManager, bus, mcpManager, systemPromptFn, toolRegistry, voiceManager } = options;
48967
50231
  let currentRun = null;
48968
50232
  const pendingResponders = /* @__PURE__ */ new Map();
48969
50233
  const promptCollector = createPromptCollector(bus, () => agentLoop.sessionId);
50234
+ const workflowManager = new WorkflowManager(agentConfig, toolRegistry, systemPromptFn, bus);
48970
50235
  const OAUTH_FLOW_TTL_MS = 15 * 60 * 1e3;
48971
50236
  const oauthFlows = /* @__PURE__ */ new Map();
48972
50237
  const cleanupOAuthFlow = /* @__PURE__ */ __name((flowId, abort) => {
@@ -48985,7 +50250,7 @@ function registerRpcHandlers(options) {
48985
50250
  const user = bridgeUser(rawEvent, source);
48986
50251
  const text = formatBridgeText(rawEvent, source, user);
48987
50252
  bus.emitEvent({
48988
- payload: { source, task_id: randomUUID13(), text, user },
50253
+ payload: { source, task_id: randomUUID14(), text, user },
48989
50254
  session_id: agentLoop.sessionId,
48990
50255
  type: "bridge.message"
48991
50256
  });
@@ -48994,7 +50259,7 @@ function registerRpcHandlers(options) {
48994
50259
  bus.registerRpc("setup.status", () => {
48995
50260
  const hasConfigKey = Boolean(agentConfig.llm.api_key && agentConfig.llm.api_key !== "proxy-token");
48996
50261
  const hasEnvKey = Boolean(
48997
- process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GITHUB_TOKEN
50262
+ 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
50263
  );
48999
50264
  const hasStoredCredential = PROVIDERS2.some((p) => {
49000
50265
  try {
@@ -49342,12 +50607,12 @@ ${helpMessage}` : field.label;
49342
50607
  const id = String(params.session_id ?? agentLoop.sessionId) || agentLoop.sessionId;
49343
50608
  const data = loadSession(id);
49344
50609
  const messages = data?.messages ?? agentLoop.history;
49345
- const exportDir = join42(homedir28(), ".openjaw-agent", "exports");
49346
- if (!existsSync31(exportDir)) {
49347
- mkdirSync17(exportDir, { recursive: true });
50610
+ const exportDir = join43(homedir29(), ".openjaw-agent", "exports");
50611
+ if (!existsSync32(exportDir)) {
50612
+ mkdirSync18(exportDir, { recursive: true });
49348
50613
  }
49349
50614
  const safeId = id.replace(/[^a-zA-Z0-9_.-]/g, "_") || agentLoop.sessionId;
49350
- const file2 = join42(exportDir, `session-${safeId}.md`);
50615
+ const file2 = join43(exportDir, `session-${safeId}.md`);
49351
50616
  const lines = [
49352
50617
  `# OpenJaw Agent Session ${id}`,
49353
50618
  `Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -49360,7 +50625,7 @@ ${helpMessage}` : field.label;
49360
50625
  for (const message of messages) {
49361
50626
  lines.push(...sessionMessageToMarkdown(message));
49362
50627
  }
49363
- writeFileSync19(file2, lines.join("\n"), "utf-8");
50628
+ writeFileSync20(file2, lines.join("\n"), "utf-8");
49364
50629
  return { file: file2, message_count: messages.length };
49365
50630
  });
49366
50631
  bus.registerRpc("session.interrupt", () => {
@@ -49377,8 +50642,8 @@ ${helpMessage}` : field.label;
49377
50642
  return { deleted: "" };
49378
50643
  }
49379
50644
  try {
49380
- const file2 = join42(homedir28(), ".openjaw-agent", "sessions", `${id}.json`);
49381
- if (existsSync31(file2)) {
50645
+ const file2 = join43(homedir29(), ".openjaw-agent", "sessions", `${id}.json`);
50646
+ if (existsSync32(file2)) {
49382
50647
  rmSync(file2);
49383
50648
  }
49384
50649
  return { deleted: id };
@@ -49451,11 +50716,11 @@ ${helpMessage}` : field.label;
49451
50716
  bus.registerRpc("shell.exec", async (params) => {
49452
50717
  const command = String(params.command ?? "");
49453
50718
  if (!command) return { code: -1, stderr: "empty command" };
49454
- return await new Promise((resolve5) => {
50719
+ return await new Promise((resolve6) => {
49455
50720
  const isWin = process.platform === "win32";
49456
50721
  const shell = isWin ? "powershell.exe" : "sh";
49457
50722
  const args = isWin ? ["-NoProfile", "-Command", command] : ["-c", command];
49458
- const child = spawn7(shell, args, { timeout: 3e4 });
50723
+ const child = spawn6(shell, args, { timeout: 3e4 });
49459
50724
  let stdout = "";
49460
50725
  let stderr = "";
49461
50726
  let truncated = false;
@@ -49478,10 +50743,10 @@ ${helpMessage}` : field.label;
49478
50743
  if (truncated) {
49479
50744
  stderr += "\n[output truncated to 1MB]";
49480
50745
  }
49481
- resolve5({ code: code ?? -1, stderr, stdout });
50746
+ resolve6({ code: code ?? -1, stderr, stdout });
49482
50747
  });
49483
50748
  child.on("error", (err) => {
49484
- resolve5({ code: -1, stderr: err.message });
50749
+ resolve6({ code: -1, stderr: err.message });
49485
50750
  });
49486
50751
  });
49487
50752
  });
@@ -49603,7 +50868,7 @@ ${helpMessage}` : field.label;
49603
50868
  return agentConfig.llm.copilot_enterprise_url;
49604
50869
  })();
49605
50870
  const flow = await startCopilotDeviceFlow(clientId, enterpriseUrl);
49606
- const flowId = randomUUID13();
50871
+ const flowId = randomUUID14();
49607
50872
  const controller = new AbortController();
49608
50873
  const timer = setTimeout(() => {
49609
50874
  cleanupOAuthFlow(flowId, true);
@@ -49767,7 +51032,7 @@ ${helpMessage}` : field.label;
49767
51032
  Object.assign(agentConfig.llm, result.configUpdates);
49768
51033
  bus.emitEvent({ payload: sessionInfoSnapshot(agentLoop, toolRegistry), session_id: agentLoop.sessionId, type: "session.info" });
49769
51034
  }
49770
- return { messages };
51035
+ return { config_updates: result.configUpdates, messages };
49771
51036
  });
49772
51037
  bus.registerRpc("provider.disconnect", (params) => {
49773
51038
  const provider = String(params.provider ?? "").trim();
@@ -49827,6 +51092,32 @@ ${helpMessage}` : field.label;
49827
51092
  tasks: forks.map((task) => ({ id: task.id, prompt: task.prompt, status: task.status }))
49828
51093
  };
49829
51094
  });
51095
+ bus.registerRpc("workflow.start", async (params) => {
51096
+ const goal = String(params.goal ?? params.prompt ?? "").trim();
51097
+ if (!goal) return { error: "usage: /workflow <goal>", ok: false };
51098
+ const result = await workflowManager.start(goal, agentLoop.sessionId);
51099
+ return { ok: true, run: result.run };
51100
+ });
51101
+ bus.registerRpc("workflow.status", (params) => {
51102
+ const id = String(params.id ?? params.run_id ?? "").trim();
51103
+ const run = workflowManager.status(id || void 0);
51104
+ return run ? { ok: true, run } : { error: id ? `workflow not found: ${id}` : "no workflows yet", ok: false };
51105
+ });
51106
+ bus.registerRpc("workflow.list", (params) => {
51107
+ const limit = Number(params.limit ?? 30);
51108
+ return { ok: true, runs: workflowManager.list(Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 30) };
51109
+ });
51110
+ bus.registerRpc("workflow.show", (params) => {
51111
+ const id = String(params.id ?? params.run_id ?? "").trim();
51112
+ const run = workflowManager.status(id || void 0);
51113
+ return run ? { ok: true, run, summary: run.summary ?? "" } : { error: id ? `workflow not found: ${id}` : "no workflows yet", ok: false };
51114
+ });
51115
+ bus.registerRpc("workflow.cancel", (params) => {
51116
+ const id = String(params.id ?? params.run_id ?? params.worker_id ?? "").trim();
51117
+ if (!id) return { error: "usage: /workflow cancel <runId|workerId>", ok: false };
51118
+ const result = workflowManager.cancel(id);
51119
+ return result.found ? { ok: true, ...result } : { error: `workflow or worker not found: ${id}`, ok: false };
51120
+ });
49830
51121
  bus.registerRpc("schedule.add", (params) => {
49831
51122
  const prompt = String(params.prompt ?? "").trim();
49832
51123
  const raw = String(params.schedule ?? "").trim();
@@ -50097,13 +51388,13 @@ ${helpMessage}` : field.label;
50097
51388
  const firstSpace = raw.search(/\s/);
50098
51389
  const path3 = firstSpace > 0 ? raw.slice(0, firstSpace) : raw;
50099
51390
  const remainder = firstSpace > 0 ? raw.slice(firstSpace).trim() : "";
50100
- if (!existsSync31(path3)) {
51391
+ if (!existsSync32(path3)) {
50101
51392
  throw new Error(`image.attach: file not found: ${path3}`);
50102
51393
  }
50103
51394
  let buffer;
50104
51395
  let fileSize = 0;
50105
51396
  try {
50106
- buffer = readFileSync28(path3);
51397
+ buffer = readFileSync29(path3);
50107
51398
  fileSize = statSync4(path3).size;
50108
51399
  } catch (err) {
50109
51400
  throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
@@ -50113,12 +51404,12 @@ ${helpMessage}` : field.label;
50113
51404
  pendingImage = {
50114
51405
  base64: buffer.toString("base64"),
50115
51406
  mimeType,
50116
- name: basename3(path3)
51407
+ name: basename4(path3)
50117
51408
  };
50118
51409
  const tokenEstimate = Math.max(1, Math.ceil(buffer.byteLength / 750));
50119
51410
  return {
50120
51411
  height: 0,
50121
- name: basename3(path3),
51412
+ name: basename4(path3),
50122
51413
  remainder,
50123
51414
  token_estimate: tokenEstimate,
50124
51415
  width: 0
@@ -50149,13 +51440,13 @@ ${helpMessage}` : field.label;
50149
51440
  }
50150
51441
  });
50151
51442
  bus.registerRpc("reload.env", () => {
50152
- const envPath = join42(homedir28(), ".openjaw-agent", ".env");
50153
- if (!existsSync31(envPath)) {
51443
+ const envPath = join43(homedir29(), ".openjaw-agent", ".env");
51444
+ if (!existsSync32(envPath)) {
50154
51445
  return { updated: 0 };
50155
51446
  }
50156
51447
  let updated = 0;
50157
51448
  try {
50158
- const raw = readFileSync28(envPath, "utf-8");
51449
+ const raw = readFileSync29(envPath, "utf-8");
50159
51450
  for (const line of raw.split(/\r?\n/)) {
50160
51451
  const trimmed = line.trim();
50161
51452
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -50195,8 +51486,25 @@ ${helpMessage}` : field.label;
50195
51486
  error: "checkpoints are not enabled in this build",
50196
51487
  success: false
50197
51488
  }));
50198
- bus.registerRpc("spawn_tree.list", () => ({ entries: [] }));
50199
- bus.registerRpc("spawn_tree.load", () => ({ subagents: [] }));
51489
+ bus.registerRpc("spawn_tree.save", (params) => ({
51490
+ path: saveSpawnTreeSnapshot({
51491
+ finished_at: typeof params.finished_at === "number" ? params.finished_at : Date.now() / 1e3,
51492
+ label: typeof params.label === "string" ? params.label : void 0,
51493
+ session_id: typeof params.session_id === "string" ? params.session_id : agentLoop.sessionId ?? "default",
51494
+ started_at: typeof params.started_at === "number" || params.started_at === null ? params.started_at : null,
51495
+ subagents: Array.isArray(params.subagents) ? params.subagents : []
51496
+ })
51497
+ }));
51498
+ bus.registerRpc("spawn_tree.list", (params) => ({
51499
+ entries: listSpawnTreeSnapshots(
51500
+ typeof params.session_id === "string" ? params.session_id : agentLoop.sessionId ?? "default",
51501
+ typeof params.limit === "number" ? params.limit : 30
51502
+ )
51503
+ }));
51504
+ bus.registerRpc("spawn_tree.load", (params) => {
51505
+ const path3 = String(params.path ?? "").trim();
51506
+ return path3 ? loadSpawnTreeSnapshot(path3) ?? { subagents: [] } : { subagents: [] };
51507
+ });
50200
51508
  bus.registerRpc("skills.reload", async () => {
50201
51509
  try {
50202
51510
  clearSkillsCache();
@@ -50280,9 +51588,19 @@ ${helpMessage}` : field.label;
50280
51588
  }
50281
51589
  return { output: name ? `unknown command: /${name}` : "(no command)", type: "exec" };
50282
51590
  });
50283
- bus.registerRpc("delegation.status", () => ({ delegated: [], paused: false }));
51591
+ bus.registerRpc("delegation.status", () => ({
51592
+ active: [],
51593
+ max_concurrent_children: agentConfig.features?.dynamic_workflows?.hard_max_concurrent_workers ?? 128,
51594
+ max_spawn_depth: agentConfig.features?.dynamic_workflows?.hard_max_workers ?? 1024,
51595
+ paused: false
51596
+ }));
50284
51597
  bus.registerRpc("delegation.pause", (params) => ({ paused: Boolean(params.paused) }));
50285
- bus.registerRpc("subagent.interrupt", () => ({ ok: true }));
51598
+ bus.registerRpc("subagent.interrupt", (params) => {
51599
+ const id = String(params.subagent_id ?? "").trim();
51600
+ if (!id) return { found: false };
51601
+ const result = workflowManager.cancel(id);
51602
+ return { found: result.found, subagent_id: id };
51603
+ });
50286
51604
  syncMcpTools(mcpManager, toolRegistry);
50287
51605
  bus.emitEvent({
50288
51606
  payload: sessionInfoSnapshot(agentLoop, toolRegistry),
@@ -50330,6 +51648,8 @@ var init_rpcHandlers = __esm({
50330
51648
  init_registry2();
50331
51649
  init_usage();
50332
51650
  init_usageSnapshot();
51651
+ init_manager();
51652
+ init_persistence();
50333
51653
  init_registry3();
50334
51654
  PROVIDERS2 = ["anthropic", "openai", "github-copilot"];
50335
51655
  PROVIDER_LABELS = {
@@ -50515,8 +51835,8 @@ var init_rpcHandlers = __esm({
50515
51835
  ${raw}`;
50516
51836
  return `${header}: ${raw}`;
50517
51837
  }, "formatBridgeText");
50518
- runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve5) => {
50519
- const child = spawn7(command, args, { timeout, windowsHide: true });
51838
+ runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve6) => {
51839
+ const child = spawn6(command, args, { timeout, windowsHide: true });
50520
51840
  let stdout = "";
50521
51841
  let stderr = "";
50522
51842
  let truncated = false;
@@ -50539,9 +51859,9 @@ ${raw}`;
50539
51859
  if (truncated) {
50540
51860
  stderr += "\n[output truncated to 1MB]";
50541
51861
  }
50542
- resolve5({ code: code ?? -1, stderr, stdout });
51862
+ resolve6({ code: code ?? -1, stderr, stdout });
50543
51863
  });
50544
- child.on("error", (err) => resolve5({ code: -1, stderr: err.message, stdout }));
51864
+ child.on("error", (err) => resolve6({ code: -1, stderr: err.message, stdout }));
50545
51865
  }), "runProcess");
50546
51866
  contentToText = /* @__PURE__ */ __name((content) => {
50547
51867
  if (typeof content === "string") return content;
@@ -50699,14 +52019,14 @@ var init_memoryMonitor = __esm({
50699
52019
  });
50700
52020
 
50701
52021
  // src/lib/openExternalUrl.ts
50702
- import { spawn as spawn8 } from "node:child_process";
52022
+ import { spawn as spawn7 } from "node:child_process";
50703
52023
  import { platform } from "node:os";
50704
52024
  function openExternalUrl(rawUrl, dependencies = {}) {
50705
52025
  const url = parseSafeUrl(rawUrl);
50706
52026
  if (!url) {
50707
52027
  return false;
50708
52028
  }
50709
- const spawnFn = dependencies.spawn ?? spawn8;
52029
+ const spawnFn = dependencies.spawn ?? spawn7;
50710
52030
  const platformId = dependencies.platform?.() ?? platform();
50711
52031
  const command = openCommand(platformId);
50712
52032
  if (!command) {
@@ -51496,7 +52816,7 @@ async function terminalParityHints(env2 = process.env, options) {
51496
52816
  hints.push({
51497
52817
  key: "remote",
51498
52818
  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"
52819
+ 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
52820
  });
51501
52821
  }
51502
52822
  return hints;
@@ -51601,13 +52921,13 @@ var init_setup2 = __esm({
51601
52921
  SETUP_REQUIRED_TITLE = "Setup Required";
51602
52922
  buildSetupRequiredSections = /* @__PURE__ */ __name(() => [
51603
52923
  {
51604
- text: "Hermes needs a model provider before the TUI can start a session."
52924
+ 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
52925
  },
51606
52926
  {
51607
52927
  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"]
52928
+ ["/connect", "set up provider credentials in-place"],
52929
+ ["/model", "choose or switch model after connecting"],
52930
+ ["Ctrl+C", "exit and restart OpenJaw after setup if needed"]
51611
52931
  ],
51612
52932
  title: "Actions"
51613
52933
  }
@@ -51910,7 +53230,7 @@ var init_reasoning2 = __esm({
51910
53230
  });
51911
53231
 
51912
53232
  // src/app/turnStore.ts
51913
- import { atom as atom5 } from "nanostores";
53233
+ import { atom as atom6 } from "nanostores";
51914
53234
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
51915
53235
  var buildTurnState, $turnState, getTurnState, subscribeTurn, useTurnSelector, patchTurnState, toggleTodoCollapsed, archiveDoneTodos, archiveTodosAtTurnEnd, resetTurnState;
51916
53236
  var init_turnStore = __esm({
@@ -51934,7 +53254,7 @@ var init_turnStore = __esm({
51934
53254
  tools: [],
51935
53255
  turnTrail: []
51936
53256
  }), "buildTurnState");
51937
- $turnState = atom5(buildTurnState());
53257
+ $turnState = atom6(buildTurnState());
51938
53258
  getTurnState = /* @__PURE__ */ __name(() => $turnState.get(), "getTurnState");
51939
53259
  subscribeTurn = /* @__PURE__ */ __name((cb) => $turnState.listen(() => cb()), "subscribeTurn");
51940
53260
  useTurnSelector = /* @__PURE__ */ __name((selector) => useSyncExternalStore4(
@@ -52534,7 +53854,9 @@ ${stripped}
52534
53854
  ...base,
52535
53855
  apiCalls: p.api_calls ?? base.apiCalls,
52536
53856
  costUsd: p.cost_usd ?? base.costUsd,
53857
+ currentStep: p.current_step ?? base.currentStep,
52537
53858
  depth: p.depth ?? base.depth,
53859
+ details: p.details ?? base.details,
52538
53860
  filesRead: p.files_read ?? base.filesRead,
52539
53861
  filesWritten: p.files_written ?? base.filesWritten,
52540
53862
  goal: p.goal || base.goal,
@@ -52548,6 +53870,9 @@ ${stripped}
52548
53870
  taskCount: p.task_count ?? base.taskCount,
52549
53871
  toolCount: p.tool_count ?? base.toolCount,
52550
53872
  toolsets: p.toolsets ?? base.toolsets,
53873
+ verificationState: p.verification_state ?? base.verificationState,
53874
+ workflowId: p.workflow_id ?? base.workflowId,
53875
+ workerRole: p.worker_role ?? base.workerRole,
52551
53876
  ...patch(base)
52552
53877
  };
52553
53878
  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 +53959,7 @@ function createGatewayEventHandler(ctx) {
52634
53959
  setTimeout(async () => {
52635
53960
  let sid = getUiState().sid;
52636
53961
  for (let i = 0; !sid && i < 40; i += 1) {
52637
- await new Promise((resolve5) => setTimeout(resolve5, 100));
53962
+ await new Promise((resolve6) => setTimeout(resolve6, 100));
52638
53963
  sid = getUiState().sid;
52639
53964
  }
52640
53965
  if (!sid) {
@@ -52981,6 +54306,26 @@ function createGatewayEventHandler(ctx) {
52981
54306
  { createIfMissing: false }
52982
54307
  );
52983
54308
  return;
54309
+ case "workflow.start":
54310
+ case "workflow.progress":
54311
+ setWorkflowSnapshot(ev.payload);
54312
+ return;
54313
+ case "workflow.complete": {
54314
+ setWorkflowSnapshot(ev.payload);
54315
+ const text = String(ev.payload?.summary ?? "").trim();
54316
+ if (text) {
54317
+ appendMessage({ role: "assistant", text });
54318
+ }
54319
+ return;
54320
+ }
54321
+ case "workflow.error": {
54322
+ setWorkflowSnapshot(ev.payload);
54323
+ const text = String(ev.payload?.summary ?? "").trim();
54324
+ if (text) {
54325
+ sys(text);
54326
+ }
54327
+ return;
54328
+ }
52984
54329
  case "message.delta":
52985
54330
  turnController.recordMessageDelta(ev.payload ?? {});
52986
54331
  return;
@@ -53015,7 +54360,7 @@ function createGatewayEventHandler(ctx) {
53015
54360
  }
53016
54361
  };
53017
54362
  }
53018
- var NO_PROVIDER_RE, statusFromBusy, applySkin, dropBgTask, pushUnique, pushThinking, pushNote, pushTool;
54363
+ var NO_PROVIDER_RE, statusFromBusy, applySkin, dropBgTask, pushUnique2, pushThinking, pushNote, pushTool;
53019
54364
  var init_createGatewayEventHandler = __esm({
53020
54365
  "src/app/createGatewayEventHandler.ts"() {
53021
54366
  "use strict";
@@ -53030,6 +54375,7 @@ var init_createGatewayEventHandler = __esm({
53030
54375
  init_overlayStore();
53031
54376
  init_turnController();
53032
54377
  init_uiStore();
54378
+ init_workflowStore();
53033
54379
  NO_PROVIDER_RE = /\bNo (?:LLM|inference) provider configured\b/i;
53034
54380
  statusFromBusy = /* @__PURE__ */ __name(() => getUiState().busy ? "running\u2026" : "ready", "statusFromBusy");
53035
54381
  applySkin = /* @__PURE__ */ __name((s) => patchUiState({
@@ -53047,10 +54393,10 @@ var init_createGatewayEventHandler = __esm({
53047
54393
  next.delete(taskId);
53048
54394
  return { ...state, bgTasks: next };
53049
54395
  }), "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);
54396
+ pushUnique2 = /* @__PURE__ */ __name((max) => (xs, x) => xs.at(-1) === x ? xs : [...xs, x].slice(-max), "pushUnique");
54397
+ pushThinking = pushUnique2(6);
54398
+ pushNote = pushUnique2(6);
54399
+ pushTool = pushUnique2(8);
53054
54400
  __name(createGatewayEventHandler, "createGatewayEventHandler");
53055
54401
  }
53056
54402
  });
@@ -53194,12 +54540,12 @@ var init_createSlashHandler = __esm({
53194
54540
  });
53195
54541
 
53196
54542
  // src/app/inputSelectionStore.ts
53197
- import { atom as atom6 } from "nanostores";
54543
+ import { atom as atom7 } from "nanostores";
53198
54544
  var $inputSelection, setInputSelection, getInputSelection;
53199
54545
  var init_inputSelectionStore = __esm({
53200
54546
  "src/app/inputSelectionStore.ts"() {
53201
54547
  "use strict";
53202
- $inputSelection = atom6(null);
54548
+ $inputSelection = atom7(null);
53203
54549
  setInputSelection = /* @__PURE__ */ __name((next) => $inputSelection.set(next), "setInputSelection");
53204
54550
  getInputSelection = /* @__PURE__ */ __name(() => $inputSelection.get(), "getInputSelection");
53205
54551
  }
@@ -53360,21 +54706,21 @@ var init_useCompletion = __esm({
53360
54706
  });
53361
54707
 
53362
54708
  // 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";
54709
+ import { appendFileSync as appendFileSync4, existsSync as existsSync33, mkdirSync as mkdirSync19, readFileSync as readFileSync30 } from "node:fs";
54710
+ import { homedir as homedir30 } from "node:os";
54711
+ import { join as join44 } from "node:path";
53366
54712
  function load() {
53367
54713
  if (cache3) {
53368
54714
  return cache3;
53369
54715
  }
53370
54716
  try {
53371
- if (!existsSync32(file)) {
54717
+ if (!existsSync33(file)) {
53372
54718
  cache3 = [];
53373
54719
  return cache3;
53374
54720
  }
53375
54721
  const entries = [];
53376
54722
  let current = [];
53377
- for (const line of readFileSync29(file, "utf8").split("\n")) {
54723
+ for (const line of readFileSync30(file, "utf8").split("\n")) {
53378
54724
  if (line.startsWith("+")) {
53379
54725
  current.push(line.slice(1));
53380
54726
  } else if (current.length) {
@@ -53405,8 +54751,8 @@ function append(line) {
53405
54751
  items.splice(0, items.length - MAX);
53406
54752
  }
53407
54753
  try {
53408
- if (!existsSync32(dir)) {
53409
- mkdirSync18(dir, { recursive: true });
54754
+ if (!existsSync33(dir)) {
54755
+ mkdirSync19(dir, { recursive: true });
53410
54756
  }
53411
54757
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
53412
54758
  const encoded = trimmed.split("\n").map((l) => `+${l}`).join("\n");
@@ -53422,8 +54768,8 @@ var init_history = __esm({
53422
54768
  "src/lib/history.ts"() {
53423
54769
  "use strict";
53424
54770
  MAX = 1e3;
53425
- dir = process.env.OPENJAW_HOME ?? join43(homedir29(), ".openjaw-agent");
53426
- file = join43(dir, ".openjaw-agent_history");
54771
+ dir = process.env.OPENJAW_HOME ?? join44(homedir30(), ".openjaw-agent");
54772
+ file = join44(dir, ".openjaw-agent_history");
53427
54773
  cache3 = null;
53428
54774
  __name(load, "load");
53429
54775
  __name(append, "append");
@@ -53517,7 +54863,7 @@ var init_useQueue = __esm({
53517
54863
 
53518
54864
  // src/lib/editor.ts
53519
54865
  import { accessSync, constants } from "node:fs";
53520
- import { delimiter, join as join44 } from "node:path";
54866
+ import { delimiter, join as join45 } from "node:path";
53521
54867
  var FALLBACKS, isExecutable, resolveEditor;
53522
54868
  var init_editor = __esm({
53523
54869
  "src/lib/editor.ts"() {
@@ -53540,7 +54886,7 @@ var init_editor = __esm({
53540
54886
  return ["notepad.exe"];
53541
54887
  }
53542
54888
  const dirs = (env2.PATH ?? "").split(delimiter).filter(Boolean);
53543
- const found = FALLBACKS.flatMap((name) => dirs.map((d) => join44(d, name))).find(isExecutable);
54889
+ const found = FALLBACKS.flatMap((name) => dirs.map((d) => join45(d, name))).find(isExecutable);
53544
54890
  return [found ?? "vi"];
53545
54891
  }, "resolveEditor");
53546
54892
  }
@@ -53548,9 +54894,9 @@ var init_editor = __esm({
53548
54894
 
53549
54895
  // src/app/useComposerState.ts
53550
54896
  import { spawnSync } from "node:child_process";
53551
- import { mkdtempSync, readFileSync as readFileSync30, rmSync as rmSync2, writeFileSync as writeFileSync20 } from "node:fs";
54897
+ import { mkdtempSync, readFileSync as readFileSync31, rmSync as rmSync2, writeFileSync as writeFileSync21 } from "node:fs";
53552
54898
  import { tmpdir as tmpdir12 } from "node:os";
53553
- import { join as join45 } from "node:path";
54899
+ import { join as join46 } from "node:path";
53554
54900
  import { useStore } from "@nanostores/react";
53555
54901
  import { useCallback as useCallback7, useMemo as useMemo6, useState as useState12 } from "react";
53556
54902
  function insertAtCursor(value, cursor, text) {
@@ -53707,10 +55053,10 @@ function useComposerState({
53707
55053
  [handleResolvedPaste, onClipboardPaste, querier]
53708
55054
  );
53709
55055
  const openEditor = useCallback7(async () => {
53710
- const dir2 = mkdtempSync(join45(tmpdir12(), "hermes-"));
53711
- const file2 = join45(dir2, "prompt.md");
55056
+ const dir2 = mkdtempSync(join46(tmpdir12(), "hermes-"));
55057
+ const file2 = join46(dir2, "prompt.md");
53712
55058
  const [cmd, ...args] = resolveEditor();
53713
- writeFileSync20(file2, [...inputBuf, input].join("\n"));
55059
+ writeFileSync21(file2, [...inputBuf, input].join("\n"));
53714
55060
  let exitCode = null;
53715
55061
  await withInkSuspended(async () => {
53716
55062
  exitCode = spawnSync(cmd, [...args, file2], { stdio: "inherit" }).status;
@@ -53719,7 +55065,7 @@ function useComposerState({
53719
55065
  if (exitCode !== 0) {
53720
55066
  return;
53721
55067
  }
53722
- const text = readFileSync30(file2, "utf8").trimEnd();
55068
+ const text = readFileSync31(file2, "utf8").trimEnd();
53723
55069
  if (!text) {
53724
55070
  return;
53725
55071
  }
@@ -54137,11 +55483,11 @@ function applyVoiceRecordResponse(response, starting, voice, sys) {
54137
55483
  }
54138
55484
  }
54139
55485
  function useInputHandlers(ctx) {
54140
- const { actions, composer, gateway, terminal, voice, wheelStep } = ctx;
55486
+ const { actions, composer, gateway, terminal: terminal2, voice, wheelStep } = ctx;
54141
55487
  const { actions: cActions, refs: cRefs, state: cState } = composer;
54142
55488
  const overlay = useStore2($overlayState);
54143
55489
  const isBlocked = useStore2($isBlocked);
54144
- const pagerPageSize = Math.max(5, (terminal.stdout?.rows ?? 24) - 6);
55490
+ const pagerPageSize = Math.max(5, (terminal2.stdout?.rows ?? 24) - 6);
54145
55491
  const scrollIdleTimer = useRef10(null);
54146
55492
  const wheelAccelRef = useRef10(initWheelAccelForHost());
54147
55493
  const precisionWheelRef = useRef10(initPrecisionWheel());
@@ -54156,13 +55502,13 @@ function useInputHandlers(ctx) {
54156
55502
  turnController.relaxStreaming();
54157
55503
  }, TYPING_IDLE_MS);
54158
55504
  }
54159
- terminal.scrollWithSelection(delta);
55505
+ terminal2.scrollWithSelection(delta);
54160
55506
  }, "scrollTranscript");
54161
55507
  const copySelection = /* @__PURE__ */ __name(() => {
54162
- terminal.selection.copySelection();
55508
+ terminal2.selection.copySelection();
54163
55509
  }, "copySelection");
54164
55510
  const clearSelection2 = /* @__PURE__ */ __name(() => {
54165
- terminal.selection.clearSelection();
55511
+ terminal2.selection.clearSelection();
54166
55512
  }, "clearSelection");
54167
55513
  const cancelOverlayFromCtrlC = /* @__PURE__ */ __name(() => {
54168
55514
  if (overlay.clarify) {
@@ -54361,7 +55707,7 @@ function useInputHandlers(ctx) {
54361
55707
  return scrollTranscript(1);
54362
55708
  }
54363
55709
  if (key.pageUp || key.pageDown) {
54364
- const viewport = terminal.scrollRef.current?.getViewportHeight() ?? Math.max(6, (terminal.stdout?.rows ?? 24) - 8);
55710
+ const viewport = terminal2.scrollRef.current?.getViewportHeight() ?? Math.max(6, (terminal2.stdout?.rows ?? 24) - 8);
54365
55711
  const step = Math.max(4, Math.floor(viewport / 2));
54366
55712
  return scrollTranscript(key.pageUp ? -step : step);
54367
55713
  }
@@ -54371,7 +55717,7 @@ function useInputHandlers(ctx) {
54371
55717
  if (key.escape && cState.queueEditIdx !== null) {
54372
55718
  return cActions.clearIn();
54373
55719
  }
54374
- if (key.escape && terminal.hasSelection) {
55720
+ if (key.escape && terminal2.hasSelection) {
54375
55721
  return clearSelection2();
54376
55722
  }
54377
55723
  if (key.escape && live.focusedPane === "transcript") {
@@ -54397,7 +55743,7 @@ function useInputHandlers(ctx) {
54397
55743
  }
54398
55744
  }
54399
55745
  if (isCopyShortcut(key, ch)) {
54400
- if (terminal.hasSelection) {
55746
+ if (terminal2.hasSelection) {
54401
55747
  return copySelection();
54402
55748
  }
54403
55749
  const inputSel = getInputSelection();
@@ -54432,7 +55778,7 @@ function useInputHandlers(ctx) {
54432
55778
  }
54433
55779
  if (isAction(key, ch, "l")) {
54434
55780
  clearSelection2();
54435
- forceRedraw(terminal.stdout ?? process.stdout);
55781
+ forceRedraw(terminal2.stdout ?? process.stdout);
54436
55782
  return;
54437
55783
  }
54438
55784
  if (isVoiceToggleKey(key, ch, voice.recordKey)) {
@@ -54582,7 +55928,7 @@ var init_useLongRunToolCharms = __esm({
54582
55928
  });
54583
55929
 
54584
55930
  // src/app/useSessionLifecycle.ts
54585
- import { writeFileSync as writeFileSync21 } from "node:fs";
55931
+ import { writeFileSync as writeFileSync22 } from "node:fs";
54586
55932
  import { useCallback as useCallback8 } from "react";
54587
55933
  function useSessionLifecycle(opts) {
54588
55934
  const {
@@ -54765,7 +56111,7 @@ var init_useSessionLifecycle = __esm({
54765
56111
  return;
54766
56112
  }
54767
56113
  try {
54768
- writeFileSync21(file2, JSON.stringify({ session_id: sessionId }), { mode: 384 });
56114
+ writeFileSync22(file2, JSON.stringify({ session_id: sessionId }), { mode: 384 });
54769
56115
  } catch {
54770
56116
  }
54771
56117
  }, "writeActiveSessionFile");
@@ -55622,8 +56968,13 @@ function useMainApp(gw) {
55622
56968
  );
55623
56969
  const onModelSelect = useCallback10((value) => {
55624
56970
  patchOverlayState({ modelPicker: false });
56971
+ if (value === "__openjaw_connect_complete__") {
56972
+ sys("provider connected \u2014 starting OpenJaw session\u2026");
56973
+ session.newSession();
56974
+ return;
56975
+ }
55625
56976
  slashRef.current(`/model ${value}`);
55626
- }, []);
56977
+ }, [session, sys]);
55627
56978
  const hasReasoning = useTurnSelector((state) => Boolean(state.reasoning.trim()));
55628
56979
  const anyPanelVisible = SECTION_NAMES.some(
55629
56980
  (s) => sectionMode(s, ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== "hidden"
@@ -55795,9 +57146,9 @@ __export(perfPane_exports, {
55795
57146
  PerfPane: () => PerfPane,
55796
57147
  logFrameEvent: () => logFrameEvent
55797
57148
  });
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";
57149
+ import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync20 } from "node:fs";
57150
+ import { homedir as homedir31 } from "node:os";
57151
+ import { dirname as dirname7, join as join47 } from "node:path";
55801
57152
  import { Profiler } from "react";
55802
57153
  import { jsx as jsx17 } from "react/jsx-runtime";
55803
57154
  function PerfPane({ children, id }) {
@@ -55813,13 +57164,13 @@ var init_perfPane = __esm({
55813
57164
  init_entry_exports();
55814
57165
  ENABLED = /^(?:1|true|yes|on)$/i.test((process.env.OPENJAW_DEV_PERF ?? "").trim());
55815
57166
  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");
57167
+ LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() || join47(homedir31(), ".openjaw-agent", "perf.log");
55817
57168
  logReady = false;
55818
57169
  writeRow = /* @__PURE__ */ __name((row) => {
55819
57170
  if (!logReady) {
55820
57171
  logReady = true;
55821
57172
  try {
55822
- mkdirSync19(dirname7(LOG_PATH2), { recursive: true });
57173
+ mkdirSync20(dirname7(LOG_PATH2), { recursive: true });
55823
57174
  } catch {
55824
57175
  }
55825
57176
  }
@@ -56072,6 +57423,10 @@ function Detail({ id, node, t }) {
56072
57423
  ] }),
56073
57424
  /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", marginTop: 1, children: [
56074
57425
  /* @__PURE__ */ jsx18(Field, { name: "depth", t, value: `${item.depth} \xB7 ${item.status}` }),
57426
+ item.workflowId ? /* @__PURE__ */ jsx18(Field, { name: "workflow", t, value: item.workflowId }) : null,
57427
+ item.workerRole ? /* @__PURE__ */ jsx18(Field, { name: "role", t, value: item.workerRole }) : null,
57428
+ item.verificationState ? /* @__PURE__ */ jsx18(Field, { name: "verification", t, value: item.verificationState }) : null,
57429
+ item.currentStep ? /* @__PURE__ */ jsx18(Field, { name: "current", t, value: item.currentStep }) : null,
56075
57430
  item.model ? /* @__PURE__ */ jsx18(Field, { name: "model", t, value: item.model }) : null,
56076
57431
  item.toolsets?.length ? /* @__PURE__ */ jsx18(Field, { name: "toolsets", t, value: item.toolsets.join(", ") }) : null,
56077
57432
  /* @__PURE__ */ jsx18(Field, { name: "tools", t, value: `${item.toolCount ?? 0} (subtree ${agg.totalTools})` }),
@@ -56146,7 +57501,8 @@ function Detail({ id, node, t }) {
56146
57501
  " ",
56147
57502
  line
56148
57503
  ] }, 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
57504
+ item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Summary", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.summary }) }) : null,
57505
+ 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
57506
  ] });
56151
57507
  }
56152
57508
  function ListRow({
@@ -56246,12 +57602,23 @@ function DiffView({
56246
57602
  ] })
56247
57603
  ] });
56248
57604
  }
56249
- function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56250
- const liveSubagents = useTurnSelector((state) => state.subagents);
57605
+ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t, workflowId = null }) {
57606
+ const allLiveSubagents = useTurnSelector((state) => state.subagents);
56251
57607
  const delegation = useStore4($delegationState);
56252
57608
  const history = useStore4($spawnHistory);
56253
57609
  const diffPair = useStore4($spawnDiff);
57610
+ const workflowSnapshots = useStore4($workflowSnapshots);
56254
57611
  const { stdout } = useStdout();
57612
+ const liveSubagents = workflowId ? allLiveSubagents.filter((item) => item.workflowId === workflowId) : allLiveSubagents;
57613
+ const workflowSnapshot = workflowId ? workflowSnapshots[workflowId] ?? null : null;
57614
+ const workflowReplaySnapshot = workflowSnapshot && liveSubagents.length === 0 ? {
57615
+ finishedAt: workflowSnapshot.finishedAt ?? Date.now(),
57616
+ id: workflowSnapshot.id,
57617
+ label: workflowSnapshot.goal,
57618
+ sessionId: workflowSnapshot.id,
57619
+ startedAt: workflowSnapshot.startedAt,
57620
+ subagents: workflowSnapshot.workers
57621
+ } : null;
56255
57622
  const [historyIndex, setHistoryIndex] = useState14(
56256
57623
  () => Math.max(0, Math.min(history.length, Math.floor(initialHistoryIndex)))
56257
57624
  );
@@ -56263,9 +57630,9 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56263
57630
  const [mode, setMode] = useState14("list");
56264
57631
  const detailScrollRef = useRef14(null);
56265
57632
  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;
57633
+ const activeSnapshot = !workflowId && historyIndex > 0 ? history[historyIndex - 1] : null;
57634
+ const justFinishedSnapshot = !workflowId && historyIndex === 0 && liveSubagents.length === 0 ? history[0] ?? null : null;
57635
+ const effectiveSnapshot = workflowReplaySnapshot ?? activeSnapshot ?? justFinishedSnapshot;
56269
57636
  const replayMode = effectiveSnapshot != null;
56270
57637
  const subagents = replayMode ? effectiveSnapshot.subagents : liveSubagents;
56271
57638
  const tree = useMemo8(() => buildSubagentTree(subagents), [subagents]);
@@ -56434,7 +57801,7 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56434
57801
  const capsLabel = delegation.maxSpawnDepth ? `caps d${delegation.maxSpawnDepth}/${delegation.maxConcurrentChildren ?? "?"}` : "";
56435
57802
  const title = replayMode && effectiveSnapshot ? `${historyIndex > 0 ? `Replay ${historyIndex}/${history.length}` : "Last turn"} \xB7 finished ${new Date(
56436
57803
  effectiveSnapshot.finishedAt
56437
- ).toLocaleTimeString()}` : `Spawn tree${delegation.paused ? " \xB7 \u23F8 paused" : ""}`;
57804
+ ).toLocaleTimeString()}` : workflowId ? `Workflow ${workflowId}${delegation.paused ? " \xB7 \u23F8 paused" : ""}` : `Spawn tree${delegation.paused ? " \xB7 \u23F8 paused" : ""}`;
56438
57805
  const metaLine = [formatSummary(totals), spark, capsLabel, mix2 ? `\xB7 ${mix2}` : ""].filter(Boolean).join(" ");
56439
57806
  const controlsHint = replayMode ? " \xB7 controls locked" : ` \xB7 x kill \xB7 X subtree \xB7 p ${delegation.paused ? "resume" : "pause"}`;
56440
57807
  if (diffPair) {
@@ -56448,7 +57815,7 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
56448
57815
  metaLine
56449
57816
  ] }) : null
56450
57817
  ] }) }),
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: [
57818
+ 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
57819
  /* @__PURE__ */ jsx18(GanttStrip, { cols, cursor, flatNodes: rows, maxRows: 6, now: now2, t }),
56453
57820
  /* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 0, flexShrink: 0, overflow: "hidden", children: rows.slice(listWindowStart, listWindowStart + rowsH).map((node, i) => /* @__PURE__ */ jsx18(
56454
57821
  ListRow,
@@ -56494,6 +57861,7 @@ var init_agentsOverlay = __esm({
56494
57861
  init_overlayStore();
56495
57862
  init_spawnHistoryStore();
56496
57863
  init_turnStore();
57864
+ init_workflowStore();
56497
57865
  init_rpc();
56498
57866
  init_subagentTree();
56499
57867
  init_text();
@@ -58422,7 +59790,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58422
59790
  refreshProviders();
58423
59791
  }
58424
59792
  if (mode === "connect") {
58425
- onCancel();
59793
+ onSelect("__openjaw_connect_complete__");
58426
59794
  return;
58427
59795
  }
58428
59796
  setStage("model");
@@ -58518,7 +59886,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58518
59886
  setKeyInput("");
58519
59887
  setKeySaving(false);
58520
59888
  if (mode === "connect") {
58521
- onCancel();
59889
+ onSelect("__openjaw_connect_complete__");
58522
59890
  return;
58523
59891
  }
58524
59892
  setStage("model");
@@ -58557,7 +59925,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58557
59925
  if (r?.disconnected) {
58558
59926
  setProviders(
58559
59927
  (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
59928
+ (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
59929
  )
58562
59930
  );
58563
59931
  }
@@ -58605,7 +59973,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58605
59973
  return;
58606
59974
  }
58607
59975
  if (mode === "connect") {
58608
- onCancel();
59976
+ onSelect("__openjaw_connect_complete__");
58609
59977
  return;
58610
59978
  }
58611
59979
  setStage("model");
@@ -58693,7 +60061,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
58693
60061
  "Configure ",
58694
60062
  provider.name
58695
60063
  ] }),
58696
- /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to ~/.hermes/.env)" }),
60064
+ /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to OpenJaw credentials)" }),
58697
60065
  /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: " " }),
58698
60066
  /* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
58699
60067
  provider.key_env,
@@ -59889,14 +61257,14 @@ __export(fpsStore_exports, {
59889
61257
  $fpsState: () => $fpsState,
59890
61258
  trackFrame: () => trackFrame
59891
61259
  });
59892
- import { atom as atom7 } from "nanostores";
61260
+ import { atom as atom8 } from "nanostores";
59893
61261
  var WINDOW_SIZE, $fpsState, timestamps, totalFrames, trackFrame;
59894
61262
  var init_fpsStore = __esm({
59895
61263
  "src/lib/fpsStore.ts"() {
59896
61264
  "use strict";
59897
61265
  init_env();
59898
61266
  WINDOW_SIZE = 30;
59899
- $fpsState = atom7({ fps: 0, lastDurationMs: 0, totalFrames: 0 });
61267
+ $fpsState = atom8({ fps: 0, lastDurationMs: 0, totalFrames: 0 });
59900
61268
  timestamps = [];
59901
61269
  totalFrames = 0;
59902
61270
  trackFrame = SHOW_FPS ? (durationMs) => {
@@ -61060,7 +62428,7 @@ var init_mathUnicode = __esm({
61060
62428
 
61061
62429
  // src/lib/syntax.ts
61062
62430
  function highlightLine(line, lang, t) {
61063
- const spec = resolve4(lang);
62431
+ const spec = resolve5(lang);
61064
62432
  if (!spec) {
61065
62433
  return [["", line]];
61066
62434
  }
@@ -61092,7 +62460,7 @@ function highlightLine(line, lang, t) {
61092
62460
  }
61093
62461
  return tokens;
61094
62462
  }
61095
- var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve4, isHighlightable, TOKEN_RE;
62463
+ var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve5, isHighlightable, TOKEN_RE;
61096
62464
  var init_syntax = __esm({
61097
62465
  "src/lib/syntax.ts"() {
61098
62466
  "use strict";
@@ -61146,8 +62514,8 @@ var init_syntax = __esm({
61146
62514
  yml: "yaml",
61147
62515
  zsh: "sh"
61148
62516
  };
61149
- resolve4 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
61150
- isHighlightable = /* @__PURE__ */ __name((lang) => resolve4(lang) !== null, "isHighlightable");
62517
+ resolve5 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
62518
+ isHighlightable = /* @__PURE__ */ __name((lang) => resolve5(lang) !== null, "isHighlightable");
61151
62519
  TOKEN_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|\b\d+(?:\.\d+)?\b|[A-Za-z_$][\w$]*/g;
61152
62520
  __name(highlightLine, "highlightLine");
61153
62521
  }
@@ -63271,8 +64639,9 @@ var init_appLayout = __esm({
63271
64639
  {
63272
64640
  gw,
63273
64641
  initialHistoryIndex: overlay.agentsInitialHistoryIndex,
63274
- onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0 }),
63275
- t: ui.theme
64642
+ onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0, agentsWorkflowId: null }),
64643
+ t: ui.theme,
64644
+ workflowId: overlay.agentsWorkflowId
63276
64645
  }
63277
64646
  );
63278
64647
  }, "AgentsOverlayPane"));