@charzhu/openjaw-agent 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -19,22 +19,90 @@ var __export = (target, all) => {
19
19
  __defProp(target, name, { get: all[name], enumerable: true });
20
20
  };
21
21
 
22
- // src/packageRoot.ts
22
+ // src/desktop/launcher.ts
23
+ var launcher_exports = {};
24
+ __export(launcher_exports, {
25
+ launchDesktop: () => launchDesktop
26
+ });
27
+ import { spawn } from "node:child_process";
23
28
  import { existsSync } from "node:fs";
24
- import { dirname, join } from "node:path";
29
+ import { createRequire } from "node:module";
30
+ import { dirname, resolve } from "node:path";
25
31
  import { fileURLToPath } from "node:url";
26
- function findPackageRoot() {
32
+ async function launchDesktop(forwardedArgs) {
33
+ let electronBinary;
34
+ try {
35
+ const require2 = createRequire(import.meta.url);
36
+ electronBinary = require2("electron");
37
+ } catch (err) {
38
+ process.stderr.write(ELECTRON_MISSING_HINT);
39
+ process.exit(1);
40
+ }
27
41
  const here = dirname(fileURLToPath(import.meta.url));
42
+ const candidates = [
43
+ resolve(here, "electronMain.js"),
44
+ // source layout / direct dist/desktop run
45
+ resolve(here, "desktop", "electronMain.js")
46
+ // dist/main.js → dist/desktop/electronMain.js
47
+ ];
48
+ const electronEntry = candidates.find((p) => existsSync(p)) ?? candidates[0];
49
+ const child = spawn(electronBinary, [electronEntry, ...forwardedArgs], {
50
+ stdio: "inherit",
51
+ env: process.env,
52
+ windowsHide: false
53
+ });
54
+ await new Promise((resolve7) => {
55
+ child.on("exit", (code) => {
56
+ process.exit(code ?? 0);
57
+ resolve7();
58
+ });
59
+ child.on("error", (err) => {
60
+ process.stderr.write(`openjaw-agent app: failed to spawn electron: ${err.message}
61
+ `);
62
+ process.exit(1);
63
+ resolve7();
64
+ });
65
+ });
66
+ }
67
+ var ELECTRON_MISSING_HINT;
68
+ var init_launcher = __esm({
69
+ "src/desktop/launcher.ts"() {
70
+ "use strict";
71
+ ELECTRON_MISSING_HINT = [
72
+ "",
73
+ "The desktop UI requires the optional `electron` peer dependency, which",
74
+ "is not currently installed.",
75
+ "",
76
+ "To install it globally with openjaw-agent:",
77
+ " npm install -g @charzhu/openjaw-agent --include=optional",
78
+ "",
79
+ "Or, if you are running from a local clone:",
80
+ " cd path/to/openjaw-agent && npm install electron",
81
+ "",
82
+ "CLI usage (no desktop UI) is unaffected: just run `openjaw-agent` without",
83
+ "the `app` subcommand.",
84
+ ""
85
+ ].join("\n");
86
+ __name(launchDesktop, "launchDesktop");
87
+ }
88
+ });
89
+
90
+ // src/packageRoot.ts
91
+ import { existsSync as existsSync2 } from "node:fs";
92
+ import { dirname as dirname2, join } from "node:path";
93
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
94
+ function findPackageRoot() {
95
+ const here = dirname2(fileURLToPath2(import.meta.url));
28
96
  let dir2 = here;
29
97
  for (let i = 0; i < 6; i++) {
30
- if (existsSync(join(dir2, "package.json")) && existsSync(join(dir2, "prompts"))) {
98
+ if (existsSync2(join(dir2, "package.json")) && existsSync2(join(dir2, "prompts"))) {
31
99
  return dir2;
32
100
  }
33
- const parent = dirname(dir2);
101
+ const parent = dirname2(dir2);
34
102
  if (parent === dir2) break;
35
103
  dir2 = parent;
36
104
  }
37
- return dirname(here);
105
+ return dirname2(here);
38
106
  }
39
107
  function packageRoot() {
40
108
  if (cached === null) {
@@ -73,8 +141,8 @@ __export(config_exports, {
73
141
  saveAgentConfig: () => saveAgentConfig,
74
142
  updateBridgeConfig: () => updateBridgeConfig
75
143
  });
76
- import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "node:fs";
77
- import { join as join2, dirname as dirname2 } from "node:path";
144
+ import { readFileSync, writeFileSync, existsSync as existsSync3, mkdirSync } from "node:fs";
145
+ import { join as join2, dirname as dirname3 } from "node:path";
78
146
  import { homedir } from "node:os";
79
147
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
80
148
  function getConfigDir() {
@@ -88,10 +156,10 @@ function loadAgentConfig() {
88
156
  const userConfigPath = getConfigPath();
89
157
  const bundledConfigPath = packageBundledConfigPath();
90
158
  let configPath = null;
91
- if (existsSync2(userConfigPath)) {
159
+ if (existsSync3(userConfigPath)) {
92
160
  configPath = userConfigPath;
93
- } else if (existsSync2(bundledConfigPath)) {
94
- if (!existsSync2(configDir2)) {
161
+ } else if (existsSync3(bundledConfigPath)) {
162
+ if (!existsSync3(configDir2)) {
95
163
  mkdirSync(configDir2, { recursive: true });
96
164
  }
97
165
  const bundledContent = readFileSync(bundledConfigPath, "utf8");
@@ -101,7 +169,7 @@ function loadAgentConfig() {
101
169
  console.log(` Edit it to change provider/model settings.
102
170
  `);
103
171
  } else {
104
- if (!existsSync2(configDir2)) {
172
+ if (!existsSync3(configDir2)) {
105
173
  mkdirSync(configDir2, { recursive: true });
106
174
  }
107
175
  writeFileSync(userConfigPath, stringifyYaml(DEFAULT_CONFIG), "utf8");
@@ -131,7 +199,8 @@ function loadAgentConfig() {
131
199
  copilot_oauth_client_id: parsedLlm?.copilot_oauth_client_id ?? DEFAULT_CONFIG.llm.copilot_oauth_client_id,
132
200
  context_compression: parsedLlm?.context_compression,
133
201
  compression_threshold: parsedLlm?.compression_threshold,
134
- compression_model: parsedLlm?.compression_model
202
+ compression_model: parsedLlm?.compression_model,
203
+ max_tool_rounds: parsedLlm?.max_tool_rounds
135
204
  },
136
205
  telegram: parsed?.telegram ?? void 0,
137
206
  feishu: parsed?.feishu ?? void 0,
@@ -171,7 +240,7 @@ function saveAgentConfig(config) {
171
240
  }
172
241
  function loadRawConfigYaml() {
173
242
  const path3 = getConfigPath();
174
- if (!existsSync2(path3)) {
243
+ if (!existsSync3(path3)) {
175
244
  return { path: path3, yamlObj: void 0 };
176
245
  }
177
246
  const raw = readFileSync(path3, "utf8");
@@ -188,13 +257,13 @@ function updateBridgeConfig(name, values) {
188
257
  next2[name] = values;
189
258
  }
190
259
  const tmpPath = `${path3}.tmp`;
191
- const dir2 = dirname2(path3);
192
- if (!existsSync2(dir2)) {
260
+ const dir2 = dirname3(path3);
261
+ if (!existsSync3(dir2)) {
193
262
  mkdirSync(dir2, { recursive: true });
194
263
  }
195
264
  writeFileSync(tmpPath, stringifyYaml(next2), "utf8");
196
- const { renameSync } = await import("node:fs");
197
- renameSync(tmpPath, path3);
265
+ const { renameSync: renameSync2 } = await import("node:fs");
266
+ renameSync2(tmpPath, path3);
198
267
  }, "task");
199
268
  const next = configWriteChain.then(task, task);
200
269
  configWriteChain = next.then(
@@ -249,7 +318,7 @@ __export(image_resize_exports, {
249
318
  resizeImageForAgent: () => resizeImageForAgent
250
319
  });
251
320
  import { execSync } from "node:child_process";
252
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
321
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
253
322
  import { join as join3 } from "node:path";
254
323
  import { tmpdir } from "node:os";
255
324
  import { randomUUID } from "node:crypto";
@@ -300,7 +369,7 @@ Write-Output "$newW x $newH"
300
369
  `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`,
301
370
  { timeout: 15e3, encoding: "utf-8" }
302
371
  ).trim();
303
- if (!existsSync3(outputPath)) {
372
+ if (!existsSync4(outputPath)) {
304
373
  throw new Error(`Resize produced no output (PowerShell said: ${result})`);
305
374
  }
306
375
  const resizedBuffer = readFileSync2(outputPath);
@@ -310,17 +379,17 @@ Write-Output "$newW x $newH"
310
379
  return { base64: resizedBase64, mimeType: actualMimeType };
311
380
  } finally {
312
381
  try {
313
- if (existsSync3(inputPath)) unlinkSync(inputPath);
382
+ if (existsSync4(inputPath)) unlinkSync(inputPath);
314
383
  } catch {
315
384
  }
316
385
  try {
317
- if (existsSync3(outputPath)) unlinkSync(outputPath);
386
+ if (existsSync4(outputPath)) unlinkSync(outputPath);
318
387
  } catch {
319
388
  }
320
389
  }
321
390
  }
322
391
  async function resizeImageFileForAgent(filePath) {
323
- if (!existsSync3(filePath)) {
392
+ if (!existsSync4(filePath)) {
324
393
  throw new Error(`Image file not found: ${filePath}`);
325
394
  }
326
395
  const buffer = readFileSync2(filePath);
@@ -868,7 +937,7 @@ Start-Sleep -Milliseconds 50
868
937
  }
869
938
  }
870
939
  async function wait(duration) {
871
- await new Promise((resolve6) => setTimeout(resolve6, duration * 1e3));
940
+ await new Promise((resolve7) => setTimeout(resolve7, duration * 1e3));
872
941
  return { output: `Waited ${duration} seconds` };
873
942
  }
874
943
  function getDisplayDimensions() {
@@ -1976,12 +2045,12 @@ var init_copilot_token = __esm({
1976
2045
  });
1977
2046
 
1978
2047
  // src/provider-auth.ts
1979
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
2048
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1980
2049
  import { join as join5 } from "node:path";
1981
2050
  import { homedir as homedir2 } from "node:os";
1982
2051
  function getAuthDir() {
1983
2052
  const dir2 = join5(homedir2(), ".openjaw-agent");
1984
- if (!existsSync4(dir2)) mkdirSync3(dir2, { recursive: true });
2053
+ if (!existsSync5(dir2)) mkdirSync3(dir2, { recursive: true });
1985
2054
  return dir2;
1986
2055
  }
1987
2056
  function getProviderAuthPath() {
@@ -1992,7 +2061,7 @@ function emptyFile() {
1992
2061
  }
1993
2062
  function loadProviderCredentials() {
1994
2063
  const path3 = getProviderAuthPath();
1995
- if (!existsSync4(path3)) return emptyFile();
2064
+ if (!existsSync5(path3)) return emptyFile();
1996
2065
  const parsed = JSON.parse(readFileSync3(path3, "utf8"));
1997
2066
  return {
1998
2067
  version: 1,
@@ -2205,8 +2274,8 @@ function safeJsonParse(value) {
2205
2274
  }
2206
2275
  }
2207
2276
  async function loadWebSocket() {
2208
- const { createRequire } = await import("node:module");
2209
- return createRequire(import.meta.url)("ws");
2277
+ const { createRequire: createRequire3 } = await import("node:module");
2278
+ return createRequire3(import.meta.url)("ws");
2210
2279
  }
2211
2280
  function responsesWebSocketUrl(baseUrl) {
2212
2281
  const url = new URL(`${baseUrl.replace(/\/+$/, "")}/responses`);
@@ -2563,7 +2632,7 @@ var init_copilot = __esm({
2563
2632
  handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
2564
2633
  });
2565
2634
  const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
2566
- return new Promise((resolve6, reject) => {
2635
+ return new Promise((resolve7, reject) => {
2567
2636
  const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
2568
2637
  let settled = false;
2569
2638
  const timeout = setTimeout(() => {
@@ -2577,7 +2646,7 @@ var init_copilot = __esm({
2577
2646
  settled = true;
2578
2647
  clearTimeout(timeout);
2579
2648
  ws.close();
2580
- resolve6(value);
2649
+ resolve7(value);
2581
2650
  }, "finish");
2582
2651
  const fail = /* @__PURE__ */ __name((error) => {
2583
2652
  if (settled) return;
@@ -2641,7 +2710,8 @@ var init_copilot = __esm({
2641
2710
  messages.push({
2642
2711
  role: "assistant",
2643
2712
  content: msg.content,
2644
- ...toolCalls?.length ? { tool_calls: toolCalls } : {}
2713
+ ...toolCalls?.length ? { tool_calls: toolCalls } : {},
2714
+ ...msg.reasoningOpaque ? { reasoning_opaque: msg.reasoningOpaque } : {}
2645
2715
  });
2646
2716
  } else {
2647
2717
  for (const result of msg.results) {
@@ -2671,11 +2741,12 @@ var init_copilot = __esm({
2671
2741
  messages: this.buildChatMessages(options),
2672
2742
  tools: options.tools.length > 0 ? options.tools.map(toChatTool) : void 0,
2673
2743
  tool_choice: options.tools.length > 0 ? "auto" : void 0,
2674
- temperature: this.config.temperature
2744
+ temperature: this.config.temperature,
2745
+ stream: true
2675
2746
  };
2676
2747
  const res = await fetch(`${await this.baseUrl(options.signal)}/chat/completions`, {
2677
2748
  method: "POST",
2678
- headers: await this.headers(options),
2749
+ headers: { ...await this.headers(options), Accept: "text/event-stream" },
2679
2750
  body: JSON.stringify(requestBody),
2680
2751
  signal: options.signal
2681
2752
  });
@@ -2683,19 +2754,85 @@ var init_copilot = __esm({
2683
2754
  const detail = await res.text();
2684
2755
  throw new Error(`GitHub Copilot chat error: ${res.status} ${detail}`);
2685
2756
  }
2686
- const data = await res.json();
2687
- const choice = data.choices?.[0];
2688
- if (!choice?.message) throw new Error("No response from GitHub Copilot");
2689
- const toolCalls = (choice.message.tool_calls ?? []).map((tc) => ({
2690
- id: tc.id,
2691
- name: tc.function.name,
2692
- input: safeJsonParse(tc.function.arguments)
2693
- }));
2757
+ if (!res.body) throw new Error("GitHub Copilot chat: no response body for stream");
2758
+ let text = null;
2759
+ let finishReason;
2760
+ let reasoningOpaque;
2761
+ let promptTokens;
2762
+ let completionTokens;
2763
+ const toolSlots = [];
2764
+ const lastSlotByIndex = /* @__PURE__ */ new Map();
2765
+ const reader = res.body.getReader();
2766
+ const decoder = new TextDecoder();
2767
+ let buf = "";
2768
+ for (; ; ) {
2769
+ const { done, value } = await reader.read();
2770
+ if (done) break;
2771
+ buf += decoder.decode(value, { stream: true });
2772
+ let nl;
2773
+ while ((nl = buf.indexOf("\n")) !== -1) {
2774
+ const rawLine = buf.slice(0, nl).trim();
2775
+ buf = buf.slice(nl + 1);
2776
+ if (!rawLine.startsWith("data:")) continue;
2777
+ const payload = rawLine.slice(5).trim();
2778
+ if (payload === "[DONE]") continue;
2779
+ let evt;
2780
+ try {
2781
+ evt = JSON.parse(payload);
2782
+ } catch {
2783
+ continue;
2784
+ }
2785
+ const choice = evt.choices?.[0];
2786
+ if (choice) {
2787
+ const delta = choice.delta;
2788
+ if (delta) {
2789
+ if (typeof delta.content === "string") text = (text ?? "") + delta.content;
2790
+ if (typeof delta.reasoning_opaque === "string") {
2791
+ reasoningOpaque = (reasoningOpaque ?? "") + delta.reasoning_opaque;
2792
+ }
2793
+ const deltaToolCalls = delta.tool_calls;
2794
+ for (const tc of deltaToolCalls ?? []) {
2795
+ const idx = typeof tc.index === "number" ? tc.index : 0;
2796
+ const fn = tc.function;
2797
+ const startsNewCall = typeof tc.id === "string" || typeof fn?.name === "string";
2798
+ let slot;
2799
+ if (startsNewCall) {
2800
+ slot = { args: "" };
2801
+ if (typeof tc.id === "string") slot.id = tc.id;
2802
+ if (fn?.name) slot.name = fn.name;
2803
+ toolSlots.push(slot);
2804
+ lastSlotByIndex.set(idx, slot);
2805
+ } else {
2806
+ slot = lastSlotByIndex.get(idx) ?? { args: "" };
2807
+ if (!lastSlotByIndex.has(idx)) {
2808
+ toolSlots.push(slot);
2809
+ lastSlotByIndex.set(idx, slot);
2810
+ }
2811
+ }
2812
+ if (typeof fn?.arguments === "string") slot.args += fn.arguments;
2813
+ }
2814
+ }
2815
+ if (typeof choice.finish_reason === "string") finishReason = choice.finish_reason;
2816
+ if (typeof choice.reasoning_opaque === "string") {
2817
+ reasoningOpaque = choice.reasoning_opaque;
2818
+ }
2819
+ const msg = choice.message;
2820
+ if (typeof msg?.reasoning_opaque === "string") reasoningOpaque = msg.reasoning_opaque;
2821
+ }
2822
+ const usage2 = evt.usage;
2823
+ if (usage2) {
2824
+ promptTokens = usage2.prompt_tokens;
2825
+ completionTokens = usage2.completion_tokens;
2826
+ }
2827
+ }
2828
+ }
2829
+ const toolCalls = toolSlots.filter((slot) => slot.id && slot.name).map((slot) => ({ id: slot.id, name: slot.name, input: safeJsonParse(slot.args) }));
2694
2830
  return {
2695
- text: choice.message.content,
2831
+ text,
2696
2832
  toolCalls,
2697
- stopReason: toolCalls.length > 0 ? "tool_use" : choice.finish_reason === "length" ? "max_tokens" : "end",
2698
- usage: this.usage(data.usage?.prompt_tokens, data.usage?.completion_tokens)
2833
+ stopReason: toolCalls.length > 0 ? "tool_use" : finishReason === "length" ? "max_tokens" : "end",
2834
+ usage: this.usage(promptTokens, completionTokens),
2835
+ reasoningOpaque
2699
2836
  };
2700
2837
  }
2701
2838
  buildResponsesInput(options) {
@@ -2945,13 +3082,13 @@ var init_providers = __esm({
2945
3082
  });
2946
3083
 
2947
3084
  // src/session.ts
2948
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync } from "node:fs";
3085
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync, renameSync, unlinkSync as unlinkSync3 } from "node:fs";
2949
3086
  import { join as join6 } from "node:path";
2950
3087
  import { homedir as homedir3 } from "node:os";
2951
3088
  import { randomUUID as randomUUID2 } from "node:crypto";
2952
3089
  function getSessionsDir() {
2953
3090
  const dir2 = join6(homedir3(), ".openjaw-agent", "sessions");
2954
- if (!existsSync5(dir2)) mkdirSync4(dir2, { recursive: true });
3091
+ if (!existsSync6(dir2)) mkdirSync4(dir2, { recursive: true });
2955
3092
  return dir2;
2956
3093
  }
2957
3094
  function sessionPath(id) {
@@ -2972,11 +3109,23 @@ function createSession(provider, model) {
2972
3109
  function saveSession(session) {
2973
3110
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2974
3111
  session.turnCount = session.messages.filter((m) => m.role === "user").length;
2975
- writeFileSync5(sessionPath(session.id), JSON.stringify(session, null, 2), "utf8");
3112
+ const finalPath = sessionPath(session.id);
3113
+ const tmpPath = `${finalPath}.tmp`;
3114
+ const payload = JSON.stringify(session, null, 2);
3115
+ try {
3116
+ writeFileSync5(tmpPath, payload, "utf8");
3117
+ renameSync(tmpPath, finalPath);
3118
+ } catch (err) {
3119
+ try {
3120
+ unlinkSync3(tmpPath);
3121
+ } catch {
3122
+ }
3123
+ throw err;
3124
+ }
2976
3125
  }
2977
3126
  function loadSession(id) {
2978
3127
  const path3 = sessionPath(id);
2979
- if (!existsSync5(path3)) return null;
3128
+ if (!existsSync6(path3)) return null;
2980
3129
  try {
2981
3130
  return JSON.parse(readFileSync4(path3, "utf8"));
2982
3131
  } catch {
@@ -3361,7 +3510,7 @@ var init_cache_monitor = __esm({
3361
3510
  });
3362
3511
 
3363
3512
  // src/telemetry.ts
3364
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "node:fs";
3513
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "node:fs";
3365
3514
  import { join as join7 } from "node:path";
3366
3515
  import { homedir as homedir4 } from "node:os";
3367
3516
  function now() {
@@ -3384,7 +3533,7 @@ var init_telemetry = __esm({
3384
3533
  constructor(sessionId) {
3385
3534
  this.sessionId = sessionId;
3386
3535
  try {
3387
- if (!existsSync6(TELEMETRY_DIR)) {
3536
+ if (!existsSync7(TELEMETRY_DIR)) {
3388
3537
  mkdirSync5(TELEMETRY_DIR, { recursive: true });
3389
3538
  }
3390
3539
  } catch {
@@ -3415,7 +3564,7 @@ var init_telemetry = __esm({
3415
3564
  getRecentEvents(limit = 20) {
3416
3565
  try {
3417
3566
  const todayFile = join7(TELEMETRY_DIR, `${today()}.jsonl`);
3418
- if (!existsSync6(todayFile)) return [];
3567
+ if (!existsSync7(todayFile)) return [];
3419
3568
  const lines = readFileSync5(todayFile, "utf-8").trim().split("\n");
3420
3569
  return lines.slice(-limit).map((l) => JSON.parse(l));
3421
3570
  } catch {
@@ -3963,13 +4112,13 @@ function selectToolsForRequest(params) {
3963
4112
  addByName(name);
3964
4113
  }
3965
4114
  const relevantCategories = categoriesForMessage(userMessage);
3966
- for (const tool of allTools) {
3967
- if (selected.size >= maxTools) break;
4115
+ const relevantTools = allTools.map((tool, index) => ({ index, score: toolRelevanceScore(tool.name, userMessage), tool })).filter(({ tool }) => {
3968
4116
  const cat = categoryForTool(tool.name);
3969
- if (cat === "mcp") continue;
3970
- if (relevantCategories.has(cat)) {
3971
- selected.set(tool.name, tool);
3972
- }
4117
+ return cat !== "mcp" && relevantCategories.has(cat) && !selected.has(tool.name);
4118
+ }).sort((a, b) => b.score - a.score || a.index - b.index);
4119
+ for (const { tool } of relevantTools) {
4120
+ if (selected.size >= maxTools) break;
4121
+ selected.set(tool.name, tool);
3973
4122
  }
3974
4123
  if (selected.size < maxTools && relevantCategories.size === 0) {
3975
4124
  for (const tool of allTools) {
@@ -4000,6 +4149,20 @@ function categoriesForMessage(message) {
4000
4149
  }
4001
4150
  return categories;
4002
4151
  }
4152
+ function preloadRelevantCategoriesForRequest(loader, message) {
4153
+ if (typeof loader.loadCategory !== "function") return [];
4154
+ const loaded = [];
4155
+ for (const category of categoriesForMessage(message)) {
4156
+ if (category === "mcp" || category === "meta" || category === "skill") continue;
4157
+ const added = loader.loadCategory(category);
4158
+ if (added > 0) loaded.push(category);
4159
+ }
4160
+ return loaded;
4161
+ }
4162
+ function toolOutputLooksFailed(output) {
4163
+ if (!output) return false;
4164
+ return /^\s*\{?"?(error|success)"?\s*:\s*("|false)/i.test(output) || /\b(error|not found|enoent|failed)\b/i.test(output.slice(0, 300));
4165
+ }
4003
4166
  function normalizeCategory(value) {
4004
4167
  const normalized = value.toLowerCase();
4005
4168
  if (["browser", "email", "teams", "office", "wechat", "memory", "files", "system"].includes(normalized)) {
@@ -4020,6 +4183,20 @@ function categoryForTool(toolName) {
4020
4183
  if (toolName.startsWith("system_") || toolName.startsWith("clipboard_") || ["code_execute", "web_fetch", "web_search", "web_extract", "notify", "sleep", "ask_user", "config"].includes(toolName)) return "system";
4021
4184
  return "mcp";
4022
4185
  }
4186
+ function toolRelevanceScore(toolName, message) {
4187
+ const lower = message.toLowerCase();
4188
+ const name = toolName.toLowerCase();
4189
+ if (/https?:\/\//i.test(message) && ["web_extract", "web_fetch", "web_search"].includes(name)) return 150;
4190
+ 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;
4191
+ 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;
4192
+ if (/https?:\/\//i.test(message) && ["browser_navigate", "browser_extract", "browser_snapshot"].includes(name)) return 135;
4193
+ 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;
4194
+ if (/\b(powerpoint|pptx?|presentation|slides?|deck)\b/.test(lower) && name.startsWith("powerpoint_")) return 85;
4195
+ 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;
4196
+ 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;
4197
+ if (name === "openjaw_load_tools" || name === "invoke_skill") return 90;
4198
+ return 0;
4199
+ }
4023
4200
  var DEFAULT_OPENAI_MAX_TOOLS, MCP_AUTO_GROW_HARD_CAP, BUILTIN_HEADROOM, FOUNDATION_TOOL_NAMES, PROFILE_CATEGORIES, CATEGORY_KEYWORDS;
4024
4201
  var init_tool_exposure = __esm({
4025
4202
  "src/tool-exposure.ts"() {
@@ -4043,10 +4220,10 @@ var init_tool_exposure = __esm({
4043
4220
  CATEGORY_KEYWORDS = [
4044
4221
  { category: "email", patterns: [/\b(email|mail|outlook|inbox|calendar|schedule|meeting|invite|today|tomorrow)\b/i] },
4045
4222
  { category: "teams", patterns: [/\b(teams|chat|channel|message|dm|meeting|standup|today|mention)\b/i] },
4046
- { category: "browser", patterns: [/\b(browser|page|website|web|navigate|click|screenshot|snapshot|console|image|search online)\b/i] },
4047
- { category: "files", patterns: [/\b(file|folder|directory|read|write|edit|grep|glob|find in repo|codebase)\b/i] },
4223
+ { category: "browser", patterns: [/\b(browser|page|website|web|navigate|click|screenshot|snapshot|console|image|search online)\b/i, /https?:\/\//i] },
4224
+ { 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] },
4048
4225
  { 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] },
4049
- { category: "office", patterns: [/\b(word|excel|powerpoint|spreadsheet|document|presentation|slide)\b/i] },
4226
+ { category: "office", patterns: [/\b(word|excel|powerpoint|pptx?|spreadsheet|document|presentation|slide|deck)\b/i] },
4050
4227
  { category: "wechat", patterns: [/\b(wechat|weixin)\b/i] },
4051
4228
  { category: "memory", patterns: [/\b(memory|remember|recall|todo|preference)\b/i] }
4052
4229
  ];
@@ -4058,8 +4235,245 @@ var init_tool_exposure = __esm({
4058
4235
  __name(rememberLoadedToolExposure, "rememberLoadedToolExposure");
4059
4236
  __name(selectToolsForRequest, "selectToolsForRequest");
4060
4237
  __name(categoriesForMessage, "categoriesForMessage");
4238
+ __name(preloadRelevantCategoriesForRequest, "preloadRelevantCategoriesForRequest");
4239
+ __name(toolOutputLooksFailed, "toolOutputLooksFailed");
4061
4240
  __name(normalizeCategory, "normalizeCategory");
4062
4241
  __name(categoryForTool, "categoryForTool");
4242
+ __name(toolRelevanceScore, "toolRelevanceScore");
4243
+ }
4244
+ });
4245
+
4246
+ // src/turn-control.ts
4247
+ var DEFAULT_MAX_TOOL_ROUNDS, IterationBudget;
4248
+ var init_turn_control = __esm({
4249
+ "src/turn-control.ts"() {
4250
+ "use strict";
4251
+ DEFAULT_MAX_TOOL_ROUNDS = 100;
4252
+ IterationBudget = class {
4253
+ static {
4254
+ __name(this, "IterationBudget");
4255
+ }
4256
+ used = 0;
4257
+ graceUsed = false;
4258
+ max;
4259
+ constructor(max = DEFAULT_MAX_TOOL_ROUNDS) {
4260
+ this.max = Math.max(1, Math.floor(max));
4261
+ }
4262
+ get remaining() {
4263
+ return Math.max(0, this.max - this.used);
4264
+ }
4265
+ get consumed() {
4266
+ return this.used;
4267
+ }
4268
+ /** True while there is budget (or the one-time grace round) left to run. */
4269
+ canContinue() {
4270
+ return this.remaining > 0 || !this.graceUsed;
4271
+ }
4272
+ /** True only when the budget is spent and we are on the grace round. */
4273
+ isGraceRound() {
4274
+ return this.remaining === 0 && !this.graceUsed;
4275
+ }
4276
+ /** Consume one round. Returns false if nothing (not even grace) is left. */
4277
+ consume() {
4278
+ if (this.remaining > 0) {
4279
+ this.used += 1;
4280
+ return true;
4281
+ }
4282
+ if (!this.graceUsed) {
4283
+ this.graceUsed = true;
4284
+ return true;
4285
+ }
4286
+ return false;
4287
+ }
4288
+ /** Give a round back (e.g. a round that made no model-visible progress). */
4289
+ refund() {
4290
+ if (this.used > 0) this.used -= 1;
4291
+ }
4292
+ };
4293
+ }
4294
+ });
4295
+
4296
+ // src/tool-guardrails.ts
4297
+ function isNoProgressTracked(toolName) {
4298
+ if (MUTATING_TOOLS.has(toolName)) return false;
4299
+ if (toolName.startsWith("powerpoint_") || toolName.startsWith("word_") || toolName.startsWith("excel_")) {
4300
+ return false;
4301
+ }
4302
+ return true;
4303
+ }
4304
+ function signatureOf(toolName, args) {
4305
+ return `${toolName}\0${canonicalJson(args ?? {})}`;
4306
+ }
4307
+ function canonicalJson(value) {
4308
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
4309
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
4310
+ const obj = value;
4311
+ const keys = Object.keys(obj).sort();
4312
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
4313
+ }
4314
+ function hashOutput(output) {
4315
+ let h = 2166136261;
4316
+ const s = output ?? "";
4317
+ for (let i = 0; i < s.length; i++) {
4318
+ h ^= s.charCodeAt(i);
4319
+ h = Math.imul(h, 16777619);
4320
+ }
4321
+ return (h >>> 0).toString(16);
4322
+ }
4323
+ function appendGuardrailGuidance(result, decision) {
4324
+ if (decision.action !== "warn" && decision.action !== "halt" || !decision.message) return result;
4325
+ const label = decision.action === "halt" ? "Tool loop hard stop" : "Tool loop warning";
4326
+ return `${result || ""}
4327
+
4328
+ [${label}: ${decision.code}; count=${decision.count}; ${decision.message}]`;
4329
+ }
4330
+ function blockedToolResult(decision) {
4331
+ return JSON.stringify({ error: decision.message, guardrail: { code: decision.code, action: decision.action } });
4332
+ }
4333
+ var DEFAULT_GUARDRAIL_THRESHOLDS, MUTATING_TOOLS, ToolLoopGuardrails;
4334
+ var init_tool_guardrails = __esm({
4335
+ "src/tool-guardrails.ts"() {
4336
+ "use strict";
4337
+ DEFAULT_GUARDRAIL_THRESHOLDS = {
4338
+ hardStopEnabled: true,
4339
+ exactFailureWarnAfter: 2,
4340
+ exactFailureBlockAfter: 5,
4341
+ sameToolFailureWarnAfter: 3,
4342
+ sameToolFailureHaltAfter: 8,
4343
+ noProgressWarnAfter: 2,
4344
+ noProgressBlockAfter: 5
4345
+ };
4346
+ MUTATING_TOOLS = /* @__PURE__ */ new Set([
4347
+ "file_write",
4348
+ "file_edit",
4349
+ "file_delete",
4350
+ "notify",
4351
+ "ask_user",
4352
+ "memory_append",
4353
+ "memory_save"
4354
+ ]);
4355
+ __name(isNoProgressTracked, "isNoProgressTracked");
4356
+ __name(signatureOf, "signatureOf");
4357
+ __name(canonicalJson, "canonicalJson");
4358
+ __name(hashOutput, "hashOutput");
4359
+ ToolLoopGuardrails = class {
4360
+ static {
4361
+ __name(this, "ToolLoopGuardrails");
4362
+ }
4363
+ t;
4364
+ exactFailure = /* @__PURE__ */ new Map();
4365
+ sameToolFailure = /* @__PURE__ */ new Map();
4366
+ noProgress = /* @__PURE__ */ new Map();
4367
+ haltDecision = null;
4368
+ constructor(thresholds = {}) {
4369
+ this.t = { ...DEFAULT_GUARDRAIL_THRESHOLDS, ...thresholds };
4370
+ }
4371
+ /** Set once a block/halt has fired; the loop reads this to stop the turn. */
4372
+ get halted() {
4373
+ return this.haltDecision;
4374
+ }
4375
+ /**
4376
+ * Consult before (re-)executing a tool call. Returns `block` if this exact
4377
+ * call has already failed/no-progressed past the hard-stop threshold, so the
4378
+ * loop can skip execution and tell the model to change strategy.
4379
+ */
4380
+ before(toolName, args) {
4381
+ if (!this.t.hardStopEnabled) return { action: "allow", toolName };
4382
+ const sig = signatureOf(toolName, args);
4383
+ const exact = this.exactFailure.get(sig) ?? 0;
4384
+ if (exact >= this.t.exactFailureBlockAfter) {
4385
+ return this.recordHalt({
4386
+ action: "block",
4387
+ code: "repeated_exact_failure_block",
4388
+ message: `Blocked ${toolName}: the same tool call failed ${exact} times with identical arguments. Stop retrying it unchanged; change strategy or explain the blocker.`,
4389
+ toolName,
4390
+ count: exact
4391
+ });
4392
+ }
4393
+ if (isNoProgressTracked(toolName)) {
4394
+ const rec = this.noProgress.get(sig);
4395
+ if (rec && rec.count >= this.t.noProgressBlockAfter) {
4396
+ return this.recordHalt({
4397
+ action: "block",
4398
+ code: "idempotent_no_progress_block",
4399
+ 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.`,
4400
+ toolName,
4401
+ count: rec.count
4402
+ });
4403
+ }
4404
+ }
4405
+ return { action: "allow", toolName };
4406
+ }
4407
+ /**
4408
+ * Record the outcome after a tool call. Returns a `warn` to append to the
4409
+ * tool result, or a `halt` to stop the turn. `failed` is derived by the
4410
+ * caller from the output shape (reuse tool-exposure's toolOutputLooksFailed).
4411
+ */
4412
+ after(toolName, args, output, failed) {
4413
+ const sig = signatureOf(toolName, args);
4414
+ if (failed) {
4415
+ const exact = (this.exactFailure.get(sig) ?? 0) + 1;
4416
+ this.exactFailure.set(sig, exact);
4417
+ this.noProgress.delete(sig);
4418
+ const same = (this.sameToolFailure.get(toolName) ?? 0) + 1;
4419
+ this.sameToolFailure.set(toolName, same);
4420
+ if (this.t.hardStopEnabled && same >= this.t.sameToolFailureHaltAfter) {
4421
+ return this.recordHalt({
4422
+ action: "halt",
4423
+ code: "same_tool_failure_halt",
4424
+ message: `Stopped ${toolName}: it failed ${same} times this turn. Stop retrying the same failing tool path and choose a different approach.`,
4425
+ toolName,
4426
+ count: same
4427
+ });
4428
+ }
4429
+ if (exact >= this.t.exactFailureWarnAfter) {
4430
+ return {
4431
+ action: "warn",
4432
+ code: "repeated_exact_failure_warning",
4433
+ 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.`,
4434
+ toolName,
4435
+ count: exact
4436
+ };
4437
+ }
4438
+ if (same >= this.t.sameToolFailureWarnAfter) {
4439
+ return {
4440
+ action: "warn",
4441
+ code: "same_tool_failure_warning",
4442
+ 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.`,
4443
+ toolName,
4444
+ count: same
4445
+ };
4446
+ }
4447
+ return { action: "allow", toolName, count: exact };
4448
+ }
4449
+ this.exactFailure.delete(sig);
4450
+ this.sameToolFailure.delete(toolName);
4451
+ if (!isNoProgressTracked(toolName)) {
4452
+ this.noProgress.delete(sig);
4453
+ return { action: "allow", toolName };
4454
+ }
4455
+ const hash3 = hashOutput(output);
4456
+ const prev = this.noProgress.get(sig);
4457
+ const count = prev && prev.hash === hash3 ? prev.count + 1 : 1;
4458
+ this.noProgress.set(sig, { hash: hash3, count });
4459
+ if (count >= this.t.noProgressWarnAfter) {
4460
+ return {
4461
+ action: "warn",
4462
+ code: "idempotent_no_progress_warning",
4463
+ message: `${toolName} returned the same result ${count} times. Use the result already provided or change the approach instead of repeating it unchanged.`,
4464
+ toolName,
4465
+ count
4466
+ };
4467
+ }
4468
+ return { action: "allow", toolName, count };
4469
+ }
4470
+ recordHalt(decision) {
4471
+ this.haltDecision = decision;
4472
+ return decision;
4473
+ }
4474
+ };
4475
+ __name(appendGuardrailGuidance, "appendGuardrailGuidance");
4476
+ __name(blockedToolResult, "blockedToolResult");
4063
4477
  }
4064
4478
  });
4065
4479
 
@@ -4075,8 +4489,8 @@ function isOAuthAbortError(err) {
4075
4489
  function oauthDomain(enterpriseUrl) {
4076
4490
  return enterpriseUrl ? normalizeCopilotEnterpriseDomain(enterpriseUrl) : "github.com";
4077
4491
  }
4078
- async function startCopilotDeviceFlow(clientId, enterpriseUrl) {
4079
- if (!clientId.trim()) {
4492
+ async function startCopilotDeviceFlow(clientId2, enterpriseUrl) {
4493
+ if (!clientId2.trim()) {
4080
4494
  throw new Error("GitHub OAuth client ID is required for Copilot login.");
4081
4495
  }
4082
4496
  const normalizedEnterpriseUrl = enterpriseUrl ? normalizeCopilotEnterpriseDomain(enterpriseUrl) : void 0;
@@ -4089,7 +4503,7 @@ async function startCopilotDeviceFlow(clientId, enterpriseUrl) {
4089
4503
  "User-Agent": "openjaw-agent/0.1.0"
4090
4504
  },
4091
4505
  body: JSON.stringify({
4092
- client_id: clientId,
4506
+ client_id: clientId2,
4093
4507
  scope: "read:user"
4094
4508
  })
4095
4509
  });
@@ -4105,7 +4519,7 @@ async function startCopilotDeviceFlow(clientId, enterpriseUrl) {
4105
4519
  verificationUri: body.verification_uri,
4106
4520
  userCode: body.user_code,
4107
4521
  deviceCode: body.device_code,
4108
- clientId,
4522
+ clientId: clientId2,
4109
4523
  intervalSeconds: body.interval ?? 5,
4110
4524
  enterpriseUrl: normalizedEnterpriseUrl
4111
4525
  };
@@ -4409,8 +4823,8 @@ For local proxy mode without API keys, use /connect maestro.`
4409
4823
  }
4410
4824
  };
4411
4825
  }
4412
- const clientId = resolveCopilotClientId();
4413
- if (!clientId) {
4826
+ const clientId2 = resolveCopilotClientId();
4827
+ if (!clientId2) {
4414
4828
  emit({
4415
4829
  type: "error",
4416
4830
  content: [
@@ -4426,7 +4840,7 @@ For local proxy mode without API keys, use /connect maestro.`
4426
4840
  }
4427
4841
  const label = enterpriseUrl ? `GitHub Enterprise (${enterpriseUrl})` : "GitHub.com";
4428
4842
  try {
4429
- const flow = await startCopilotDeviceFlow(clientId, enterpriseUrl);
4843
+ const flow = await startCopilotDeviceFlow(clientId2, enterpriseUrl);
4430
4844
  emit({
4431
4845
  type: "system",
4432
4846
  content: [
@@ -4612,6 +5026,8 @@ var init_agent_loop = __esm({
4612
5026
  init_telemetry();
4613
5027
  init_context_compressor();
4614
5028
  init_tool_exposure();
5029
+ init_turn_control();
5030
+ init_tool_guardrails();
4615
5031
  init_provider_auth();
4616
5032
  init_connect();
4617
5033
  AgentLoop = class {
@@ -4949,6 +5365,66 @@ var init_agent_loop = __esm({
4949
5365
  this.conversationHistory = next.messages;
4950
5366
  return { session_id: next.id, title: next.summary };
4951
5367
  }
5368
+ /**
5369
+ * Create a brand-new empty session and switch this AgentLoop over to it.
5370
+ * Unlike branchSession, the new session starts with an empty history —
5371
+ * this is the "+" button / `/clear`-equivalent for users who want a
5372
+ * fresh conversation without affecting their current one.
5373
+ *
5374
+ * Persists the current session first (so any unsaved counters / summary
5375
+ * land on disk) before swapping. Returns null if a turn is currently
5376
+ * in flight (matching swapToSession's defense-in-depth posture); the
5377
+ * caller is responsible for interrupting first.
5378
+ */
5379
+ createAndSwitchTo() {
5380
+ if (this.isRunning) return null;
5381
+ saveSession(this.session);
5382
+ const next = createSession(this.config.llm.provider, this.config.llm.model);
5383
+ saveSession(next);
5384
+ this.session = next;
5385
+ this.conversationHistory = next.messages;
5386
+ this._toolExposureState = createToolExposureState();
5387
+ this._compactedOnResume = false;
5388
+ this._toolRoundsInRun = 0;
5389
+ return next;
5390
+ }
5391
+ /**
5392
+ * Resume an existing on-disk session as the active session for this loop.
5393
+ * Persists the current session first, then loads the target's messages
5394
+ * into `conversationHistory` and rebinds `this.session` so subsequent
5395
+ * `prompt.submit` turns run against the resumed conversation.
5396
+ *
5397
+ * Returns the loaded `SessionData` on success, or `null` if:
5398
+ * - a turn is currently in flight (callers must abort or wait first);
5399
+ * - the target session id doesn't exist on disk.
5400
+ *
5401
+ * Provider/model are NOT switched. The session's recorded provider/model
5402
+ * fields are updated to whatever the loop is currently configured for —
5403
+ * matching the constructor's resume behaviour, which loads the messages
5404
+ * but keeps the AgentLoop's `config.llm.provider/model` as-is. A user
5405
+ * who wants the resumed session's original model must call `switchModel`
5406
+ * separately (or run via the `/model` slash command).
5407
+ *
5408
+ * Required by both A3 defer-on-switch and any future A2 concurrent-
5409
+ * streaming refactor (the desktop UI's `session.resume` RPC depends on
5410
+ * this method actually rebinding history — see the plan, section
5411
+ * "Multi-session story").
5412
+ */
5413
+ swapToSession(targetId) {
5414
+ if (this.isRunning) return null;
5415
+ const target = loadSession(targetId);
5416
+ if (!target) return null;
5417
+ saveSession(this.session);
5418
+ target.provider = this.config.llm.provider;
5419
+ target.model = this.config.llm.model;
5420
+ this.session = target;
5421
+ this.conversationHistory = target.messages;
5422
+ this._toolExposureState = createToolExposureState();
5423
+ this._compactedOnResume = false;
5424
+ this._toolRoundsInRun = 0;
5425
+ saveSession(this.session);
5426
+ return target;
5427
+ }
4952
5428
  /** Read-only access to the underlying session metadata. */
4953
5429
  getSessionMeta() {
4954
5430
  return {
@@ -5132,15 +5608,25 @@ ${summary}
5132
5608
  const MAX_SAME_ACTIONS = 3;
5133
5609
  let previousCacheReadTokens = 0;
5134
5610
  let maxTokensContinuations = 0;
5611
+ const budget = new IterationBudget(this.config.llm.max_tool_rounds ?? DEFAULT_MAX_TOOL_ROUNDS);
5612
+ const guardrails = new ToolLoopGuardrails();
5135
5613
  for (let step = 0; ; step++) {
5136
5614
  if (signal.aborted) {
5137
5615
  yield { type: "answer", content: "[Interrupted by user]" };
5138
5616
  return;
5139
5617
  }
5618
+ const forceFinalSummary = budget.isGraceRound();
5619
+ if (!budget.consume()) {
5620
+ const reason = "max_iterations";
5621
+ yield { type: "answer", content: `[Reached the maximum of ${budget.max} tool rounds. Stopping.]`, exitReason: reason };
5622
+ return;
5623
+ }
5140
5624
  let responseText = null;
5141
5625
  const responseToolCalls = [];
5142
5626
  let responseStopReason = "end";
5627
+ let responseReasoningOpaque;
5143
5628
  let responseUsage;
5629
+ preloadRelevantCategoriesForRequest(this.toolRegistry, userMessage);
5144
5630
  const allTools = this.toolRegistry.listTools();
5145
5631
  const exposure = selectToolsForRequest({
5146
5632
  config: this.config,
@@ -5148,7 +5634,13 @@ ${summary}
5148
5634
  userMessage,
5149
5635
  state: this._toolExposureState
5150
5636
  });
5151
- const tools = exposure.tools;
5637
+ const tools = forceFinalSummary ? [] : exposure.tools;
5638
+ if (forceFinalSummary) {
5639
+ messages.push({
5640
+ role: "user",
5641
+ 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.]"
5642
+ });
5643
+ }
5152
5644
  const chatOptions = {
5153
5645
  systemPrompt,
5154
5646
  messages,
@@ -5231,6 +5723,7 @@ ${summary}
5231
5723
  responseToolCalls.push(...response.toolCalls);
5232
5724
  responseStopReason = response.stopReason;
5233
5725
  responseUsage = response.usage;
5726
+ responseReasoningOpaque = response.reasoningOpaque;
5234
5727
  }
5235
5728
  } catch (apiError) {
5236
5729
  const errMsg = apiError instanceof Error ? apiError.message : String(apiError);
@@ -5357,10 +5850,11 @@ ${summary}
5357
5850
  this.conversationHistory.push({ role: "assistant", content: responseText });
5358
5851
  this.session.messages = this.conversationHistory;
5359
5852
  saveSession(this.session);
5853
+ const exitReason = forceFinalSummary ? "max_iterations" : "completed";
5360
5854
  if (!this.provider.chatStream) {
5361
- yield { type: "answer", content: responseText ?? "" };
5855
+ yield { type: "answer", content: responseText ?? "", exitReason };
5362
5856
  } else {
5363
- yield { type: "answer", content: "" };
5857
+ yield { type: "answer", content: "", exitReason };
5364
5858
  }
5365
5859
  if (this._toolRoundsInRun >= 1 || responseText && responseText.length > 200) {
5366
5860
  void this.postTurnMemorySave(systemPrompt, messages, tools, signal);
@@ -5392,7 +5886,8 @@ ${summary}
5392
5886
  messages.push({
5393
5887
  role: "assistant",
5394
5888
  content: responseText,
5395
- toolCalls: validToolCalls
5889
+ toolCalls: validToolCalls,
5890
+ reasoningOpaque: responseReasoningOpaque
5396
5891
  });
5397
5892
  const computerCalls = validToolCalls.filter((tc) => tc.name === "computer");
5398
5893
  const otherCalls = validToolCalls.filter((tc) => tc.name !== "computer");
@@ -5400,6 +5895,10 @@ ${summary}
5400
5895
  if (signal.aborted) {
5401
5896
  return { id: tc.id, name: tc.name, output: "[Interrupted]", imageData: void 0 };
5402
5897
  }
5898
+ const pre = guardrails.before(tc.name, tc.input);
5899
+ if (pre.action === "block") {
5900
+ return { id: tc.id, name: tc.name, output: blockedToolResult(pre), imageData: void 0 };
5901
+ }
5403
5902
  let output;
5404
5903
  let imageData2;
5405
5904
  try {
@@ -5456,7 +5955,12 @@ ${summary}
5456
5955
  }
5457
5956
  } catch {
5458
5957
  }
5459
- const outputStr = typeof output === "string" ? output : JSON.stringify(output ?? { error: "No output" });
5958
+ let outputStr = typeof output === "string" ? output : JSON.stringify(output ?? { error: "No output" });
5959
+ const failed = toolOutputLooksFailed(outputStr);
5960
+ const verdict = guardrails.after(tc.name, tc.input, outputStr, failed);
5961
+ if (verdict.action === "warn" || verdict.action === "halt") {
5962
+ outputStr = appendGuardrailGuidance(outputStr, verdict);
5963
+ }
5460
5964
  return { id: tc.id, name: tc.name, output: outputStr, imageData: imageData2 };
5461
5965
  }, "executeOne");
5462
5966
  const results = [];
@@ -5484,18 +5988,18 @@ ${summary}
5484
5988
  content: parsed.question,
5485
5989
  choices: parsed.choices ?? void 0
5486
5990
  };
5487
- const userResponse = await new Promise((resolve6) => {
5991
+ const userResponse = await new Promise((resolve7) => {
5488
5992
  if (this._pendingAskUserResponse !== null) {
5489
5993
  const buffered = this._pendingAskUserResponse;
5490
5994
  this._pendingAskUserResponse = null;
5491
- resolve6(buffered);
5995
+ resolve7(buffered);
5492
5996
  return;
5493
5997
  }
5494
- this._askUserResolver = resolve6;
5998
+ this._askUserResolver = resolve7;
5495
5999
  setTimeout(() => {
5496
- if (this._askUserResolver === resolve6) {
6000
+ if (this._askUserResolver === resolve7) {
5497
6001
  this._askUserResolver = null;
5498
- resolve6("[No response from user \u2014 timed out after 5 minutes]");
6002
+ resolve7("[No response from user \u2014 timed out after 5 minutes]");
5499
6003
  }
5500
6004
  }, 5 * 60 * 1e3);
5501
6005
  });
@@ -5516,6 +6020,15 @@ ${summary}
5516
6020
  this.conversationHistory = [...messages];
5517
6021
  this.session.messages = this.conversationHistory;
5518
6022
  saveSession(this.session);
6023
+ if (guardrails.halted) {
6024
+ const reason = "guardrail_halt";
6025
+ yield {
6026
+ type: "answer",
6027
+ content: `[Stopped: ${guardrails.halted.message ?? "tool loop detected"}]`,
6028
+ exitReason: reason
6029
+ };
6030
+ return;
6031
+ }
5519
6032
  if (this._toolRoundsInRun > 0 && this._toolRoundsInRun % 5 === 0) {
5520
6033
  messages.push({
5521
6034
  role: "user",
@@ -5590,14 +6103,14 @@ __export(settings_exports, {
5590
6103
  });
5591
6104
  import { z } from "zod";
5592
6105
  import { readFile, writeFile, mkdir } from "node:fs/promises";
5593
- import { existsSync as existsSync7 } from "node:fs";
6106
+ import { existsSync as existsSync8 } from "node:fs";
5594
6107
  import { join as join9 } from "node:path";
5595
6108
  import { homedir as homedir6 } from "node:os";
5596
6109
  import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
5597
6110
  async function ensureDirectories() {
5598
6111
  const dirs = [OPENJAW_DIR, SESSIONS_DIR, MEMORY_DIR];
5599
6112
  for (const dir2 of dirs) {
5600
- if (!existsSync7(dir2)) {
6113
+ if (!existsSync8(dir2)) {
5601
6114
  await mkdir(dir2, { recursive: true });
5602
6115
  }
5603
6116
  }
@@ -5608,7 +6121,7 @@ async function loadConfig() {
5608
6121
  }
5609
6122
  await ensureDirectories();
5610
6123
  let rawConfig = {};
5611
- if (existsSync7(CONFIG_PATH)) {
6124
+ if (existsSync8(CONFIG_PATH)) {
5612
6125
  const content = await readFile(CONFIG_PATH, "utf-8");
5613
6126
  rawConfig = parseYaml2(content);
5614
6127
  }
@@ -5658,7 +6171,13 @@ var init_settings = __esm({
5658
6171
  MicrosoftConfigSchema = z.object({
5659
6172
  tenant_id: z.string().optional(),
5660
6173
  client_id: z.string().optional(),
5661
- client_secret: z.string().optional()
6174
+ client_secret: z.string().optional(),
6175
+ // Graph token acquisition strategy:
6176
+ // 'auto' (default) — try the WAM broker first (reliable, browser-free on
6177
+ // Windows), fall back to CDP browser-token extraction.
6178
+ // 'wam' — broker only (no CDP fallback).
6179
+ // 'cdp' — legacy browser-token extraction only (disable WAM).
6180
+ auth_mode: z.enum(["auto", "wam", "cdp"]).optional()
5662
6181
  });
5663
6182
  BrowserConfigSchema = z.object({
5664
6183
  executable: z.string().optional(),
@@ -6158,7 +6677,7 @@ var init_browser = __esm({
6158
6677
  await Page.domContentEventFired();
6159
6678
  } else if (options.waitFor === "networkidle") {
6160
6679
  await Page.loadEventFired();
6161
- await new Promise((resolve6) => setTimeout(resolve6, 1e3));
6680
+ await new Promise((resolve7) => setTimeout(resolve7, 1e3));
6162
6681
  }
6163
6682
  const result = await Runtime.evaluate({
6164
6683
  expression: "document.title"
@@ -6929,7 +7448,7 @@ var init_browser = __esm({
6929
7448
  if (exists) {
6930
7449
  return true;
6931
7450
  }
6932
- await new Promise((resolve6) => setTimeout(resolve6, 200));
7451
+ await new Promise((resolve7) => setTimeout(resolve7, 200));
6933
7452
  }
6934
7453
  return false;
6935
7454
  }
@@ -7157,10 +7676,10 @@ function createBrowseTools(config, sharedBrowser) {
7157
7676
  }
7158
7677
  },
7159
7678
  execute: /* @__PURE__ */ __name(async (input) => {
7160
- const { join: join48 } = await import("node:path");
7679
+ const { join: join49 } = await import("node:path");
7161
7680
  const { tmpdir: tmpdir13 } = await import("node:os");
7162
7681
  const { randomUUID: randomUUID15 } = await import("node:crypto");
7163
- const screenshotPath = join48(tmpdir13(), `openjaw-browser-${randomUUID15().slice(0, 8)}.png`);
7682
+ const screenshotPath = join49(tmpdir13(), `openjaw-browser-${randomUUID15().slice(0, 8)}.png`);
7164
7683
  const screenshot = await browser.screenshot({ fullPage: false, path: screenshotPath });
7165
7684
  const snapshot = await browser.snapshot({ full: false });
7166
7685
  return {
@@ -7855,7 +8374,7 @@ var init_outlook_desktop = __esm({
7855
8374
  }
7856
8375
  }
7857
8376
  sleep(ms) {
7858
- return new Promise((resolve6) => setTimeout(resolve6, ms));
8377
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
7859
8378
  }
7860
8379
  };
7861
8380
  }
@@ -8453,14 +8972,14 @@ var init_outlook_web = __esm({
8453
8972
  await this.browser.typeChars(text);
8454
8973
  }
8455
8974
  sleep(ms) {
8456
- return new Promise((resolve6) => setTimeout(resolve6, ms));
8975
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
8457
8976
  }
8458
8977
  };
8459
8978
  }
8460
8979
  });
8461
8980
 
8462
8981
  // ../openjaw-mcp/dist/auth/token-pool.js
8463
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
8982
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
8464
8983
  import { join as join10 } from "node:path";
8465
8984
  import { homedir as homedir7 } from "node:os";
8466
8985
  function decodeJwtPayload(jwt) {
@@ -8657,7 +9176,7 @@ var init_token_pool = __esm({
8657
9176
  * Load pool from disk, including legacy token files.
8658
9177
  */
8659
9178
  load() {
8660
- if (existsSync8(POOL_FILE)) {
9179
+ if (existsSync9(POOL_FILE)) {
8661
9180
  try {
8662
9181
  const raw = readFileSync6(POOL_FILE, "utf-8");
8663
9182
  const data = JSON.parse(raw);
@@ -8684,7 +9203,7 @@ var init_token_pool = __esm({
8684
9203
  */
8685
9204
  loadLegacyFiles() {
8686
9205
  const legacyDir = join10(POOL_DIR, "tokens");
8687
- if (!existsSync8(legacyDir))
9206
+ if (!existsSync9(legacyDir))
8688
9207
  return;
8689
9208
  try {
8690
9209
  const { readdirSync: readdirSync8 } = __require("node:fs");
@@ -8868,7 +9387,7 @@ var init_cdp_token_extractor = __esm({
8868
9387
  }
8869
9388
  logger_default.info("CDP: reloading tab for token refresh", { url: targetTab.url, audience });
8870
9389
  await this.reloadTab(targetTab);
8871
- await new Promise((resolve6) => setTimeout(resolve6, PAGE_RELOAD_WAIT_MS));
9390
+ await new Promise((resolve7) => setTimeout(resolve7, PAGE_RELOAD_WAIT_MS));
8872
9391
  if (!targetTab.webSocketDebuggerUrl)
8873
9392
  return [];
8874
9393
  const freshPages = await this.listPages();
@@ -8985,19 +9504,19 @@ var init_cdp_token_extractor = __esm({
8985
9504
  * Evaluate a JS expression in a tab and return the string result.
8986
9505
  */
8987
9506
  async evaluateInTab(wsUrl, expression) {
8988
- return new Promise((resolve6) => {
9507
+ return new Promise((resolve7) => {
8989
9508
  let ws;
8990
9509
  try {
8991
9510
  ws = new WebSocket(wsUrl);
8992
9511
  } catch (err) {
8993
9512
  logger_default.warn("CDP: WebSocket constructor failed", { wsUrl: wsUrl.substring(0, 60), error: String(err) });
8994
- resolve6(null);
9513
+ resolve7(null);
8995
9514
  return;
8996
9515
  }
8997
9516
  const timer = setTimeout(() => {
8998
9517
  logger_default.warn("CDP: evaluateInTab timeout", { wsUrl: wsUrl.substring(0, 60) });
8999
9518
  ws.close();
9000
- resolve6(null);
9519
+ resolve7(null);
9001
9520
  }, CDP_TIMEOUT_MS);
9002
9521
  ws.on("open", () => {
9003
9522
  ws.send(JSON.stringify({
@@ -9011,18 +9530,18 @@ var init_cdp_token_extractor = __esm({
9011
9530
  if (resp.id === 1) {
9012
9531
  clearTimeout(timer);
9013
9532
  ws.close();
9014
- resolve6(resp.result?.result?.value ?? null);
9533
+ resolve7(resp.result?.result?.value ?? null);
9015
9534
  }
9016
9535
  });
9017
9536
  ws.on("error", (err) => {
9018
9537
  logger_default.warn("CDP: evaluateInTab WS error", { error: String(err), wsUrl: wsUrl.substring(0, 60) });
9019
9538
  clearTimeout(timer);
9020
- resolve6(null);
9539
+ resolve7(null);
9021
9540
  });
9022
9541
  });
9023
9542
  }
9024
9543
  async extractFromTab(wsUrl) {
9025
- return new Promise((resolve6, reject) => {
9544
+ return new Promise((resolve7, reject) => {
9026
9545
  const ws = new WebSocket(wsUrl);
9027
9546
  const timer = setTimeout(() => {
9028
9547
  ws.close();
@@ -9042,14 +9561,14 @@ var init_cdp_token_extractor = __esm({
9042
9561
  ws.close();
9043
9562
  const value = resp.result?.result?.value;
9044
9563
  if (!value) {
9045
- resolve6([]);
9564
+ resolve7([]);
9046
9565
  return;
9047
9566
  }
9048
9567
  try {
9049
9568
  const tokens = JSON.parse(value);
9050
- resolve6(tokens);
9569
+ resolve7(tokens);
9051
9570
  } catch {
9052
- resolve6([]);
9571
+ resolve7([]);
9053
9572
  }
9054
9573
  }
9055
9574
  });
@@ -9065,11 +9584,11 @@ var init_cdp_token_extractor = __esm({
9065
9584
  async reloadTab(page) {
9066
9585
  if (!page.webSocketDebuggerUrl)
9067
9586
  return;
9068
- return new Promise((resolve6, reject) => {
9587
+ return new Promise((resolve7, reject) => {
9069
9588
  const ws = new WebSocket(page.webSocketDebuggerUrl);
9070
9589
  const timer = setTimeout(() => {
9071
9590
  ws.close();
9072
- resolve6();
9591
+ resolve7();
9073
9592
  }, 1e4);
9074
9593
  ws.on("open", () => {
9075
9594
  ws.send(JSON.stringify({
@@ -9083,7 +9602,7 @@ var init_cdp_token_extractor = __esm({
9083
9602
  if (resp.id === 1) {
9084
9603
  clearTimeout(timer);
9085
9604
  ws.close();
9086
- resolve6();
9605
+ resolve7();
9087
9606
  }
9088
9607
  });
9089
9608
  ws.on("error", (err) => {
@@ -9123,13 +9642,172 @@ var init_cdp_token_extractor = __esm({
9123
9642
  }
9124
9643
  });
9125
9644
 
9645
+ // ../openjaw-mcp/dist/auth/wam-token-provider.js
9646
+ var wam_token_provider_exports = {};
9647
+ __export(wam_token_provider_exports, {
9648
+ WAM_GRAPH_SCOPES: () => WAM_GRAPH_SCOPES,
9649
+ acquireGraphToken: () => acquireGraphToken,
9650
+ acquireTokenForAudience: () => acquireTokenForAudience,
9651
+ configureWam: () => configureWam,
9652
+ isWamAvailable: () => isWamAvailable,
9653
+ setWamWindowHandleProvider: () => setWamWindowHandleProvider
9654
+ });
9655
+ import { InteractionRequiredAuthError } from "@azure/msal-node";
9656
+ function configureWam(opts) {
9657
+ if (opts.clientId)
9658
+ clientId = opts.clientId;
9659
+ if (opts.tenant)
9660
+ tenant = opts.tenant;
9661
+ if (typeof opts.enabled === "boolean")
9662
+ wamEnabled = opts.enabled;
9663
+ }
9664
+ function setWamWindowHandleProvider(fn) {
9665
+ windowHandleProvider = fn;
9666
+ }
9667
+ async function getBrokerPlugin() {
9668
+ if (brokerLoadAttempted)
9669
+ return nativeBrokerPlugin;
9670
+ brokerLoadAttempted = true;
9671
+ if (process.platform !== "win32") {
9672
+ logger_default.debug("WAM: not win32, broker unavailable");
9673
+ return null;
9674
+ }
9675
+ try {
9676
+ const { NativeBrokerPlugin } = await import("@azure/msal-node-extensions");
9677
+ const plugin = new NativeBrokerPlugin();
9678
+ nativeBrokerPlugin = plugin;
9679
+ } catch (err) {
9680
+ logger_default.warn("WAM: failed to load NativeBrokerPlugin", { error: err instanceof Error ? err.message : String(err) });
9681
+ nativeBrokerPlugin = null;
9682
+ }
9683
+ return nativeBrokerPlugin;
9684
+ }
9685
+ async function getApp() {
9686
+ const plugin = await getBrokerPlugin();
9687
+ if (!plugin || !plugin.isBrokerAvailable)
9688
+ return null;
9689
+ if (!msalApp) {
9690
+ const { PublicClientApplication } = await import("@azure/msal-node");
9691
+ msalApp = new PublicClientApplication({
9692
+ auth: { clientId, authority: `https://login.microsoftonline.com/${tenant}` },
9693
+ broker: { nativeBrokerPlugin: plugin }
9694
+ });
9695
+ }
9696
+ return msalApp;
9697
+ }
9698
+ function withBrokerLock(fn) {
9699
+ const result = _brokerQueue.then(fn, fn);
9700
+ _brokerQueue = result.then(() => void 0, () => void 0);
9701
+ return result;
9702
+ }
9703
+ function isInteractionRequired(err) {
9704
+ if (err instanceof InteractionRequiredAuthError)
9705
+ return true;
9706
+ const msg = err instanceof Error ? err.message : String(err);
9707
+ return /interaction[_\-\s]?required|no[_\-\s]?account/i.test(msg);
9708
+ }
9709
+ async function acquireUnlocked(app, scopes) {
9710
+ const accounts = await app.getAllAccounts();
9711
+ const account = selectedAccount ?? accounts.find((a) => a.tenantId === tenant) ?? (accounts.length === 1 ? accounts[0] : void 0);
9712
+ if (account) {
9713
+ try {
9714
+ return await app.acquireTokenSilent({ scopes, account });
9715
+ } catch (err) {
9716
+ if (!isInteractionRequired(err)) {
9717
+ logger_default.warn("WAM: silent acquire failed (non-interactive)", {
9718
+ error: err instanceof Error ? err.message.split("\n")[0] : String(err)
9719
+ });
9720
+ throw err;
9721
+ }
9722
+ logger_default.info("WAM: silent failed, interaction required");
9723
+ }
9724
+ }
9725
+ const windowHandle = windowHandleProvider?.() ?? void 0;
9726
+ const result = await app.acquireTokenInteractive({
9727
+ scopes,
9728
+ windowHandle,
9729
+ openBrowser: noop,
9730
+ prompt: "select_account",
9731
+ loginHint: account?.username
9732
+ });
9733
+ if (result.account)
9734
+ selectedAccount = result.account;
9735
+ return result;
9736
+ }
9737
+ async function acquireGraphToken(scopes = WAM_GRAPH_SCOPES) {
9738
+ return acquireTokenForScopes(scopes);
9739
+ }
9740
+ async function acquireTokenForAudience(audience, _perms = []) {
9741
+ const base = audience.endsWith("/") ? audience.slice(0, -1) : audience;
9742
+ return acquireTokenForScopes([`${base}/.default`]);
9743
+ }
9744
+ async function acquireTokenForScopes(scopes) {
9745
+ if (!wamEnabled || brokerPoisoned)
9746
+ return null;
9747
+ try {
9748
+ const app = await getApp();
9749
+ if (!app)
9750
+ return null;
9751
+ const result = await withBrokerLock(() => acquireUnlocked(app, scopes));
9752
+ return result?.accessToken ?? null;
9753
+ } catch (err) {
9754
+ const msg = err instanceof Error ? err.message : String(err);
9755
+ if (/AccountUnusable|broker.*unavailable/i.test(msg)) {
9756
+ brokerPoisoned = true;
9757
+ logger_default.warn("WAM: broker poisoned, disabling for this session", { error: msg.split("\n")[0] });
9758
+ } else {
9759
+ logger_default.warn("WAM: acquire failed, will fall back", { error: msg.split("\n")[0] });
9760
+ }
9761
+ return null;
9762
+ }
9763
+ }
9764
+ async function isWamAvailable() {
9765
+ if (!wamEnabled || brokerPoisoned)
9766
+ return false;
9767
+ const plugin = await getBrokerPlugin();
9768
+ return !!plugin && plugin.isBrokerAvailable;
9769
+ }
9770
+ var CORP_TENANT, DEFAULT_CLIENT_ID, WAM_GRAPH_SCOPES, clientId, tenant, windowHandleProvider, wamEnabled, brokerLoadAttempted, nativeBrokerPlugin, msalApp, brokerPoisoned, selectedAccount, _brokerQueue, noop;
9771
+ var init_wam_token_provider = __esm({
9772
+ "../openjaw-mcp/dist/auth/wam-token-provider.js"() {
9773
+ "use strict";
9774
+ init_logger();
9775
+ CORP_TENANT = "72f988bf-86f1-41af-91ab-2d7cd011db47";
9776
+ DEFAULT_CLIENT_ID = "99fa64eb-feda-4f94-aecd-30637ca7bf2d";
9777
+ WAM_GRAPH_SCOPES = ["https://graph.microsoft.com/.default"];
9778
+ clientId = DEFAULT_CLIENT_ID;
9779
+ tenant = CORP_TENANT;
9780
+ windowHandleProvider = null;
9781
+ wamEnabled = true;
9782
+ __name(configureWam, "configureWam");
9783
+ __name(setWamWindowHandleProvider, "setWamWindowHandleProvider");
9784
+ brokerLoadAttempted = false;
9785
+ nativeBrokerPlugin = null;
9786
+ msalApp = null;
9787
+ brokerPoisoned = false;
9788
+ selectedAccount = null;
9789
+ __name(getBrokerPlugin, "getBrokerPlugin");
9790
+ __name(getApp, "getApp");
9791
+ _brokerQueue = Promise.resolve();
9792
+ __name(withBrokerLock, "withBrokerLock");
9793
+ noop = /* @__PURE__ */ __name(async () => {
9794
+ }, "noop");
9795
+ __name(isInteractionRequired, "isInteractionRequired");
9796
+ __name(acquireUnlocked, "acquireUnlocked");
9797
+ __name(acquireGraphToken, "acquireGraphToken");
9798
+ __name(acquireTokenForAudience, "acquireTokenForAudience");
9799
+ __name(acquireTokenForScopes, "acquireTokenForScopes");
9800
+ __name(isWamAvailable, "isWamAvailable");
9801
+ }
9802
+ });
9803
+
9126
9804
  // ../openjaw-mcp/dist/auth/graph-token-provider.js
9127
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
9128
- import { join as join11, dirname as dirname3 } from "node:path";
9805
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
9806
+ import { join as join11, dirname as dirname4 } from "node:path";
9129
9807
  import { homedir as homedir8 } from "node:os";
9130
9808
  import { exec as exec2 } from "node:child_process";
9131
9809
  import { promisify as promisify2 } from "node:util";
9132
- import { fileURLToPath as fileURLToPath2 } from "node:url";
9810
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
9133
9811
  function getSharedPool() {
9134
9812
  if (!sharedPool) {
9135
9813
  sharedPool = new TokenPool();
@@ -9149,9 +9827,10 @@ var init_graph_token_provider = __esm({
9149
9827
  init_logger();
9150
9828
  init_token_pool();
9151
9829
  init_cdp_token_extractor();
9830
+ init_wam_token_provider();
9152
9831
  execAsync2 = promisify2(exec2);
9153
- __filename = fileURLToPath2(import.meta.url);
9154
- __dirname = dirname3(__filename);
9832
+ __filename = fileURLToPath3(import.meta.url);
9833
+ __dirname = dirname4(__filename);
9155
9834
  TOKEN_PROFILES = {
9156
9835
  "graph-chat": {
9157
9836
  scopes: ["Chat.Read"],
@@ -9225,6 +9904,23 @@ var init_graph_token_provider = __esm({
9225
9904
  this.scheduleProactiveRefresh(pooled.expiresAt);
9226
9905
  return pooled.secret;
9227
9906
  }
9907
+ {
9908
+ const wamJwt = await acquireTokenForAudience(this.preferredAudience, this.requiredScopes);
9909
+ if (wamJwt) {
9910
+ const added = this.pool.addTokenFromJwt(wamJwt, "wam");
9911
+ const token = this.pool.getToken(this.requiredScopes, this.preferredAudience);
9912
+ if (token) {
9913
+ this.saveLegacyToken(token.secret);
9914
+ this.scheduleProactiveRefresh(token.expiresAt);
9915
+ logger_default.info("Token acquired via WAM broker", { tokenName: this.tokenName });
9916
+ return token.secret;
9917
+ }
9918
+ if (added) {
9919
+ this.scheduleProactiveRefresh(added.expiresAt);
9920
+ return added.secret;
9921
+ }
9922
+ }
9923
+ }
9228
9924
  logger_default.info("Tier 1 (pool) empty \u2014 trying Tier 2 (CDP extraction)...", {
9229
9925
  tokenName: this.tokenName,
9230
9926
  audience: this.preferredAudience
@@ -9482,7 +10178,7 @@ var init_graph_token_provider = __esm({
9482
10178
  * Tier 4: Legacy Python script fallback.
9483
10179
  */
9484
10180
  async extractViaLegacyScript() {
9485
- if (!existsSync9(this.extractScript)) {
10181
+ if (!existsSync10(this.extractScript)) {
9486
10182
  return false;
9487
10183
  }
9488
10184
  try {
@@ -9498,7 +10194,7 @@ var init_graph_token_provider = __esm({
9498
10194
  */
9499
10195
  migrateLegacyToken(tokenName) {
9500
10196
  const legacyPath = join11(homedir8(), ".graph-token", "tokens", `${tokenName}.json`);
9501
- if (!existsSync9(legacyPath))
10197
+ if (!existsSync10(legacyPath))
9502
10198
  return;
9503
10199
  try {
9504
10200
  const data = JSON.parse(readFileSync7(legacyPath, "utf-8"));
@@ -9757,9 +10453,9 @@ var init_outlook_graph = __esm({
9757
10453
  const wellKnown = WELL_KNOWN_FOLDERS[lower];
9758
10454
  if (wellKnown)
9759
10455
  return wellKnown;
9760
- const cached7 = this.folderIdCache.get(lower);
9761
- if (cached7)
9762
- return cached7;
10456
+ const cached8 = this.folderIdCache.get(lower);
10457
+ if (cached8)
10458
+ return cached8;
9763
10459
  try {
9764
10460
  const result = await this.graphGet(`/me/mailFolders?$filter=displayName eq '${folderName.replace(/'/g, "''")}'&$top=1`);
9765
10461
  if (result.value.length > 0) {
@@ -10017,7 +10713,7 @@ var init_outlook_graph = __esm({
10017
10713
  import { DatabaseSync } from "node:sqlite";
10018
10714
  import { join as join12 } from "node:path";
10019
10715
  import { homedir as homedir9 } from "node:os";
10020
- import { mkdirSync as mkdirSync8, existsSync as existsSync10 } from "node:fs";
10716
+ import { mkdirSync as mkdirSync8, existsSync as existsSync11 } from "node:fs";
10021
10717
  function hasFts5() {
10022
10718
  return _hasFts5;
10023
10719
  }
@@ -10033,7 +10729,7 @@ function detectFts5(database) {
10033
10729
  }
10034
10730
  function getMemoryDb() {
10035
10731
  if (!db) {
10036
- if (!existsSync10(DB_DIR)) {
10732
+ if (!existsSync11(DB_DIR)) {
10037
10733
  mkdirSync8(DB_DIR, { recursive: true });
10038
10734
  }
10039
10735
  db = new DatabaseSync(DB_PATH);
@@ -10117,9 +10813,9 @@ var init_db = __esm({
10117
10813
  import { createHash as createHash2 } from "node:crypto";
10118
10814
  function encodeAtom(word, dim2 = HRR_DIM) {
10119
10815
  const cacheKey = `${word}:${dim2}`;
10120
- const cached7 = atomCache.get(cacheKey);
10121
- if (cached7)
10122
- return cached7;
10816
+ const cached8 = atomCache.get(cacheKey);
10817
+ if (cached8)
10818
+ return cached8;
10123
10819
  const phases = new Float64Array(dim2);
10124
10820
  const bytesNeeded = dim2 * 4;
10125
10821
  const chunks = [];
@@ -10394,7 +11090,7 @@ __export(store_exports, {
10394
11090
  MemoryStore: () => MemoryStore
10395
11091
  });
10396
11092
  import { readFile as readFile2, readdir } from "node:fs/promises";
10397
- import { existsSync as existsSync11 } from "node:fs";
11093
+ import { existsSync as existsSync12 } from "node:fs";
10398
11094
  import { join as join13 } from "node:path";
10399
11095
  var MemoryStore;
10400
11096
  var init_store = __esm({
@@ -10452,7 +11148,7 @@ var init_store = __esm({
10452
11148
  const db2 = this.ensureDb();
10453
11149
  let migrated = 0;
10454
11150
  const insert = db2.prepare("INSERT INTO memories (content, source, created_at) VALUES (?, ?, ?)");
10455
- if (existsSync11(this.memoryFile)) {
11151
+ if (existsSync12(this.memoryFile)) {
10456
11152
  const content = await readFile2(this.memoryFile, "utf-8");
10457
11153
  const entries = this.parseMarkdownToEntries(content, "MEMORY.md");
10458
11154
  for (const entry of entries) {
@@ -10460,7 +11156,7 @@ var init_store = __esm({
10460
11156
  migrated++;
10461
11157
  }
10462
11158
  }
10463
- if (existsSync11(this.memoryDir)) {
11159
+ if (existsSync12(this.memoryDir)) {
10464
11160
  const files = await readdir(this.memoryDir);
10465
11161
  const mdFiles = files.filter((f) => f.endsWith(".md")).sort().reverse();
10466
11162
  for (const file2 of mdFiles) {
@@ -10744,13 +11440,13 @@ function createMemoryTools(config) {
10744
11440
  const todos = input.todos;
10745
11441
  try {
10746
11442
  const { appendFile: appendFile2, mkdir: mkdir5 } = await import("node:fs/promises");
10747
- const { existsSync: existsSync34 } = await import("node:fs");
10748
- const { join: join48 } = await import("node:path");
10749
- const { homedir: homedir32 } = await import("node:os");
10750
- const memoryDir = join48(homedir32(), ".openjaw", "memory");
10751
- if (!existsSync34(memoryDir))
11443
+ const { existsSync: existsSync36 } = await import("node:fs");
11444
+ const { join: join49 } = await import("node:path");
11445
+ const { homedir: homedir33 } = await import("node:os");
11446
+ const memoryDir = join49(homedir33(), ".openjaw", "memory");
11447
+ if (!existsSync36(memoryDir))
10752
11448
  await mkdir5(memoryDir, { recursive: true });
10753
- const todoPath = join48(memoryDir, "TODOS.md");
11449
+ const todoPath = join49(memoryDir, "TODOS.md");
10754
11450
  const { writeFile: writeFile5 } = await import("node:fs/promises");
10755
11451
  await writeFile5(todoPath, `# Session Todos
10756
11452
 
@@ -12071,7 +12767,7 @@ var init_teams_desktop = __esm({
12071
12767
  }
12072
12768
  }
12073
12769
  sleep(ms) {
12074
- return new Promise((resolve6) => setTimeout(resolve6, ms));
12770
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
12075
12771
  }
12076
12772
  /**
12077
12773
  * Get the current Teams window state
@@ -12938,7 +13634,7 @@ var init_teams_web = __esm({
12938
13634
  }
12939
13635
  }
12940
13636
  sleep(ms) {
12941
- return new Promise((resolve6) => setTimeout(resolve6, ms));
13637
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
12942
13638
  }
12943
13639
  };
12944
13640
  }
@@ -13335,9 +14031,9 @@ ${loopContent}` : loopContent;
13335
14031
  */
13336
14032
  async findChannelByName(searchTerm) {
13337
14033
  const searchLower = searchTerm.toLowerCase();
13338
- const cached7 = this.channelIdCache.get(searchLower);
13339
- if (cached7) {
13340
- return { ...cached7, displayName: searchTerm };
14034
+ const cached8 = this.channelIdCache.get(searchLower);
14035
+ if (cached8) {
14036
+ return { ...cached8, displayName: searchTerm };
13341
14037
  }
13342
14038
  try {
13343
14039
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
@@ -13517,9 +14213,9 @@ ${loopContent}` : loopContent;
13517
14213
  * Results are cached per chat ID.
13518
14214
  */
13519
14215
  async getChatMembers(chatOrChannelId) {
13520
- const cached7 = this.chatMembersCache.get(chatOrChannelId);
13521
- if (cached7)
13522
- return cached7;
14216
+ const cached8 = this.chatMembersCache.get(chatOrChannelId);
14217
+ if (cached8)
14218
+ return cached8;
13523
14219
  try {
13524
14220
  let endpoint;
13525
14221
  if (this.contextType === "channel" && this.currentChannelTeamId && chatOrChannelId === this.currentChannelId) {
@@ -13757,9 +14453,9 @@ ${loopContent}` : loopContent;
13757
14453
  */
13758
14454
  async resolveChannelIds(teamName, channelName) {
13759
14455
  const cacheKey = `${teamName}/${channelName}`;
13760
- const cached7 = this.channelIdCache.get(cacheKey);
13761
- if (cached7)
13762
- return cached7;
14456
+ const cached8 = this.channelIdCache.get(cacheKey);
14457
+ if (cached8)
14458
+ return cached8;
13763
14459
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
13764
14460
  const team = teamsData.value?.find((t) => t.displayName.toLowerCase() === teamName.toLowerCase());
13765
14461
  if (!team) {
@@ -14336,7 +15032,7 @@ var init_teams_chat_monitor = __esm({
14336
15032
  return this.sentMessages.has(normalized);
14337
15033
  }
14338
15034
  sleep(ms) {
14339
- return new Promise((resolve6) => setTimeout(resolve6, ms));
15035
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
14340
15036
  }
14341
15037
  /**
14342
15038
  * Attempt to reconnect the browser after detecting a disconnection.
@@ -15123,7 +15819,7 @@ ${lines.join("\n")}`;
15123
15819
  globalThis.__teamsSeenMessages.set(chatName, seenIds);
15124
15820
  }
15125
15821
  if (syncMode) {
15126
- return new Promise((resolve6) => {
15822
+ return new Promise((resolve7) => {
15127
15823
  const timer2 = setInterval(async () => {
15128
15824
  try {
15129
15825
  const current = await channel.readCurrentChatMessages();
@@ -15132,7 +15828,7 @@ ${lines.join("\n")}`;
15132
15828
  if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
15133
15829
  clearInterval(timer2);
15134
15830
  seenIds.add(msgId);
15135
- resolve6({
15831
+ resolve7({
15136
15832
  success: true,
15137
15833
  channel: channelType,
15138
15834
  sync: true,
@@ -15209,7 +15905,7 @@ ${lines.join("\n")}`;
15209
15905
  globalThis.__teamsSeenMessages.set(chatName, seenIds);
15210
15906
  }
15211
15907
  if (syncMode) {
15212
- return new Promise((resolve6) => {
15908
+ return new Promise((resolve7) => {
15213
15909
  const timer2 = setInterval(async () => {
15214
15910
  try {
15215
15911
  const current = await channel.readCurrentChatMessages();
@@ -15218,7 +15914,7 @@ ${lines.join("\n")}`;
15218
15914
  if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
15219
15915
  clearInterval(timer2);
15220
15916
  seenIds.add(msgId);
15221
- resolve6({
15917
+ resolve7({
15222
15918
  success: true,
15223
15919
  channel: channelType,
15224
15920
  sync: true,
@@ -15576,7 +16272,7 @@ ${lines.join("\n")}`;
15576
16272
  return allTools;
15577
16273
  }
15578
16274
  function sleep2(ms) {
15579
- return new Promise((resolve6) => setTimeout(resolve6, ms));
16275
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
15580
16276
  }
15581
16277
  var MAX_STORED_ENTRIES, ENTRY_TTL_MS;
15582
16278
  var init_chat = __esm({
@@ -15601,8 +16297,8 @@ var init_chat = __esm({
15601
16297
 
15602
16298
  // ../openjaw-mcp/dist/tools/files.js
15603
16299
  import { readFile as readFile3, writeFile as writeFile2, readdir as readdir2, stat, mkdir as mkdir2, unlink } from "node:fs/promises";
15604
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
15605
- import { join as join15, dirname as dirname4, basename, extname } from "node:path";
16300
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
16301
+ import { join as join15, dirname as dirname5, basename, extname } from "node:path";
15606
16302
  import { execSync as execSync3 } from "node:child_process";
15607
16303
  function createFileTools(_config) {
15608
16304
  return [
@@ -15639,7 +16335,7 @@ function createFileTools(_config) {
15639
16335
  execute: /* @__PURE__ */ __name(async (input) => {
15640
16336
  const path3 = input.path;
15641
16337
  const encoding = input.encoding ?? "utf-8";
15642
- if (!existsSync12(path3)) {
16338
+ if (!existsSync13(path3)) {
15643
16339
  return { error: `File not found: ${path3}` };
15644
16340
  }
15645
16341
  const BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -15750,12 +16446,12 @@ function createFileTools(_config) {
15750
16446
  const append2 = input.append ?? false;
15751
16447
  const createDirs = input.create_dirs ?? true;
15752
16448
  if (createDirs) {
15753
- const dir2 = dirname4(path3);
15754
- if (!existsSync12(dir2)) {
16449
+ const dir2 = dirname5(path3);
16450
+ if (!existsSync13(dir2)) {
15755
16451
  await mkdir2(dir2, { recursive: true });
15756
16452
  }
15757
16453
  }
15758
- if (append2 && existsSync12(path3)) {
16454
+ if (append2 && existsSync13(path3)) {
15759
16455
  const existing = await readFile3(path3, "utf-8");
15760
16456
  await writeFile2(path3, existing + content, "utf-8");
15761
16457
  } else {
@@ -15802,7 +16498,7 @@ function createFileTools(_config) {
15802
16498
  const recursive = input.recursive ?? false;
15803
16499
  const includeHidden = input.include_hidden ?? false;
15804
16500
  const pattern = input.pattern;
15805
- if (!existsSync12(dirPath)) {
16501
+ if (!existsSync13(dirPath)) {
15806
16502
  return { error: `Directory not found: ${dirPath}` };
15807
16503
  }
15808
16504
  const results = [];
@@ -15850,7 +16546,7 @@ function createFileTools(_config) {
15850
16546
  requiresConfirmation: true,
15851
16547
  execute: /* @__PURE__ */ __name(async (input) => {
15852
16548
  const path3 = input.path;
15853
- if (!existsSync12(path3)) {
16549
+ if (!existsSync13(path3)) {
15854
16550
  return { error: `File not found: ${path3}` };
15855
16551
  }
15856
16552
  await unlink(path3);
@@ -15872,7 +16568,7 @@ function createFileTools(_config) {
15872
16568
  },
15873
16569
  execute: /* @__PURE__ */ __name(async (input) => {
15874
16570
  const path3 = input.path;
15875
- if (!existsSync12(path3)) {
16571
+ if (!existsSync13(path3)) {
15876
16572
  return { error: `Path not found: ${path3}` };
15877
16573
  }
15878
16574
  const stats = await stat(path3);
@@ -15902,7 +16598,7 @@ function createFileTools(_config) {
15902
16598
  },
15903
16599
  execute: /* @__PURE__ */ __name(async (input) => {
15904
16600
  const filePath = input.path;
15905
- if (!existsSync12(filePath)) {
16601
+ if (!existsSync13(filePath)) {
15906
16602
  return { error: `Image not found: ${filePath}` };
15907
16603
  }
15908
16604
  const ext = extname(filePath).toLowerCase();
@@ -15948,7 +16644,7 @@ function createFileTools(_config) {
15948
16644
  const path3 = input.path;
15949
16645
  const oldStr = input.old_str;
15950
16646
  const newStr = input.new_str;
15951
- if (!existsSync12(path3)) {
16647
+ if (!existsSync13(path3)) {
15952
16648
  return { error: `File not found: ${path3}` };
15953
16649
  }
15954
16650
  const fileStats = await stat(path3);
@@ -16134,7 +16830,7 @@ var init_web_search_types = __esm({
16134
16830
  });
16135
16831
 
16136
16832
  // ../openjaw-mcp/dist/tools/shell.js
16137
- import { spawn } from "node:child_process";
16833
+ import { spawn as spawn2 } from "node:child_process";
16138
16834
  import clipboardy from "clipboardy";
16139
16835
  import notifier from "node-notifier";
16140
16836
  function truncateOutput(text, maxLines = 200) {
@@ -16400,12 +17096,12 @@ function createShellTools(_config, hooks) {
16400
17096
  const shell = input.shell ?? true;
16401
17097
  if (input.background) {
16402
17098
  const { tmpdir: tmpdir13 } = await import("node:os");
16403
- const { join: join48 } = await import("node:path");
17099
+ const { join: join49 } = await import("node:path");
16404
17100
  const { randomUUID: randomUUID15 } = await import("node:crypto");
16405
17101
  const { createWriteStream: createWriteStream2 } = await import("node:fs");
16406
17102
  const taskId = randomUUID15().slice(0, 8);
16407
- const outputPath = join48(tmpdir13(), `oj-bg-${taskId}.log`);
16408
- const detached = spawn(command, [], {
17103
+ const outputPath = join49(tmpdir13(), `oj-bg-${taskId}.log`);
17104
+ const detached = spawn2(command, [], {
16409
17105
  shell,
16410
17106
  cwd,
16411
17107
  detached: true,
@@ -16424,8 +17120,8 @@ function createShellTools(_config, hooks) {
16424
17120
  message: `Command started in background (PID: ${detached.pid}). Output: ${outputPath}`
16425
17121
  };
16426
17122
  }
16427
- return new Promise((resolve6) => {
16428
- const proc = spawn(command, [], {
17123
+ return new Promise((resolve7) => {
17124
+ const proc = spawn2(command, [], {
16429
17125
  shell,
16430
17126
  cwd,
16431
17127
  timeout,
@@ -16442,7 +17138,7 @@ function createShellTools(_config, hooks) {
16442
17138
  proc.on("close", (code) => {
16443
17139
  const stdoutResult = truncateOutput(stdout.trim());
16444
17140
  const stderrResult = truncateOutput(stderr.trim());
16445
- resolve6({
17141
+ resolve7({
16446
17142
  command,
16447
17143
  exitCode: code,
16448
17144
  stdout: stdoutResult.text,
@@ -16453,7 +17149,7 @@ function createShellTools(_config, hooks) {
16453
17149
  });
16454
17150
  });
16455
17151
  proc.on("error", (error) => {
16456
- resolve6({
17152
+ resolve7({
16457
17153
  command,
16458
17154
  exitCode: -1,
16459
17155
  stdout: "",
@@ -16487,8 +17183,8 @@ function createShellTools(_config, hooks) {
16487
17183
  },
16488
17184
  requiresConfirmation: false,
16489
17185
  execute: /* @__PURE__ */ __name(async (input) => {
16490
- const { writeFileSync: writeFileSync23, unlinkSync: unlinkSync9 } = await import("node:fs");
16491
- const { join: join48 } = await import("node:path");
17186
+ const { writeFileSync: writeFileSync23, unlinkSync: unlinkSync10 } = await import("node:fs");
17187
+ const { join: join49 } = await import("node:path");
16492
17188
  const { tmpdir: tmpdir13 } = await import("node:os");
16493
17189
  const { randomUUID: randomUUID15 } = await import("node:crypto");
16494
17190
  const { execFile: execFile3 } = await import("node:child_process");
@@ -16507,14 +17203,14 @@ function createShellTools(_config, hooks) {
16507
17203
  const interpreter = interpreterMap[language];
16508
17204
  if (!interpreter)
16509
17205
  return { error: `Unsupported language: ${language}` };
16510
- const tmpFile = join48(tmpdir13(), `oj-code-${randomUUID15().slice(0, 8)}${ext}`);
17206
+ const tmpFile = join49(tmpdir13(), `oj-code-${randomUUID15().slice(0, 8)}${ext}`);
16511
17207
  try {
16512
17208
  writeFileSync23(tmpFile, code, "utf-8");
16513
17209
  const startTime = Date.now();
16514
- const result = await new Promise((resolve6) => {
17210
+ const result = await new Promise((resolve7) => {
16515
17211
  execFile3(interpreter.cmd, [...interpreter.args, tmpFile], { timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout2, stderr2) => {
16516
17212
  const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
16517
- resolve6({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
17213
+ resolve7({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
16518
17214
  });
16519
17215
  });
16520
17216
  const executionTimeMs = Date.now() - startTime;
@@ -16540,7 +17236,7 @@ function createShellTools(_config, hooks) {
16540
17236
  };
16541
17237
  } finally {
16542
17238
  try {
16543
- unlinkSync9(tmpFile);
17239
+ unlinkSync10(tmpFile);
16544
17240
  } catch {
16545
17241
  }
16546
17242
  }
@@ -16607,7 +17303,7 @@ function createShellTools(_config, hooks) {
16607
17303
  required: ["title", "message"]
16608
17304
  },
16609
17305
  execute: /* @__PURE__ */ __name(async (input) => {
16610
- return new Promise((resolve6) => {
17306
+ return new Promise((resolve7) => {
16611
17307
  notifier.notify({
16612
17308
  title: input.title,
16613
17309
  message: input.message,
@@ -16615,9 +17311,9 @@ function createShellTools(_config, hooks) {
16615
17311
  sound: true
16616
17312
  }, (err) => {
16617
17313
  if (err) {
16618
- resolve6({ error: err.message });
17314
+ resolve7({ error: err.message });
16619
17315
  } else {
16620
- resolve6({ success: true });
17316
+ resolve7({ success: true });
16621
17317
  }
16622
17318
  });
16623
17319
  });
@@ -16721,7 +17417,7 @@ function createShellTools(_config, hooks) {
16721
17417
  },
16722
17418
  execute: /* @__PURE__ */ __name(async (input) => {
16723
17419
  const seconds = Math.min(Math.max(0.1, input.seconds), 60);
16724
- await new Promise((resolve6) => setTimeout(resolve6, seconds * 1e3));
17420
+ await new Promise((resolve7) => setTimeout(resolve7, seconds * 1e3));
16725
17421
  return { waited: seconds, message: `Waited ${seconds} seconds` };
16726
17422
  }, "execute")
16727
17423
  },
@@ -17833,7 +18529,7 @@ var init_office_desktop = __esm({
17833
18529
  return Array.isArray(parsed) ? parsed : [parsed];
17834
18530
  }
17835
18531
  sleep(ms) {
17836
- return new Promise((resolve6) => setTimeout(resolve6, ms));
18532
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
17837
18533
  }
17838
18534
  };
17839
18535
  }
@@ -19514,7 +20210,7 @@ public class Win32Send {
19514
20210
  }
19515
20211
  }
19516
20212
  sleep(ms) {
19517
- return new Promise((resolve6) => setTimeout(resolve6, ms));
20213
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
19518
20214
  }
19519
20215
  };
19520
20216
  }
@@ -19579,7 +20275,7 @@ function getActiveMonitors() {
19579
20275
  return result;
19580
20276
  }
19581
20277
  function sleep3(ms) {
19582
- return new Promise((resolve6) => setTimeout(resolve6, ms));
20278
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
19583
20279
  }
19584
20280
  async function fileHash(filePath) {
19585
20281
  const data = fs.readFileSync(filePath);
@@ -20261,6 +20957,224 @@ var init_wechat = __esm({
20261
20957
  }
20262
20958
  });
20263
20959
 
20960
+ // ../openjaw-mcp/dist/tools/workiq.js
20961
+ import { spawn as spawn3 } from "node:child_process";
20962
+ import { accessSync, chmodSync, constants, existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
20963
+ import { createRequire as createRequire2 } from "node:module";
20964
+ import { homedir as homedir10 } from "node:os";
20965
+ import { dirname as dirname6, join as join16 } from "node:path";
20966
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
20967
+ function workiqStateFilePath() {
20968
+ return join16(homedir10(), ".work-iq-cli", ".workiq.json");
20969
+ }
20970
+ function workiqPlatformDir(pkgDir) {
20971
+ if (process.platform === "win32") {
20972
+ const nativeDir = join16(pkgDir, "bin", `win-${process.arch}`);
20973
+ if (!existsSync14(nativeDir) && process.arch !== "x64")
20974
+ return "win-x64";
20975
+ return `win-${process.arch}`;
20976
+ }
20977
+ if (process.platform === "darwin")
20978
+ return `osx-${process.arch}`;
20979
+ return `linux-${process.arch}`;
20980
+ }
20981
+ function ensureExecutable(binaryPath) {
20982
+ if (process.platform === "win32")
20983
+ return;
20984
+ try {
20985
+ accessSync(binaryPath, constants.X_OK);
20986
+ } catch {
20987
+ try {
20988
+ chmodSync(binaryPath, 493);
20989
+ } catch {
20990
+ }
20991
+ }
20992
+ }
20993
+ function resolveWorkIQBinaryInPackage(pkgDir) {
20994
+ const binaryName = process.platform === "win32" ? "workiq.exe" : "workiq";
20995
+ const binaryPath = join16(pkgDir, "bin", workiqPlatformDir(pkgDir), binaryName);
20996
+ if (!existsSync14(binaryPath))
20997
+ return null;
20998
+ ensureExecutable(binaryPath);
20999
+ return binaryPath;
21000
+ }
21001
+ function resolveWorkIQBinary(nodeModulesPath) {
21002
+ return resolveWorkIQBinaryInPackage(join16(nodeModulesPath, "@microsoft", "workiq"));
21003
+ }
21004
+ function isWorkIQEulaAccepted() {
21005
+ const path3 = workiqStateFilePath();
21006
+ if (!existsSync14(path3))
21007
+ return false;
21008
+ try {
21009
+ const parsed = JSON.parse(readFileSync9(path3, "utf-8"));
21010
+ if (typeof parsed !== "object" || parsed === null)
21011
+ return false;
21012
+ const obj = parsed;
21013
+ const v = obj["I-accept-EULA"] ?? obj["i-accept-eula"] ?? obj.eulaAccepted ?? obj.eula_accepted ?? obj.acceptedEula ?? obj.eula;
21014
+ if (typeof v === "string")
21015
+ return v.trim().toLowerCase() === "true";
21016
+ return v === true || v === 1;
21017
+ } catch {
21018
+ return false;
21019
+ }
21020
+ }
21021
+ function findWorkIQPackageDir() {
21022
+ const anchors = [
21023
+ () => fileURLToPath4(import.meta.url),
21024
+ () => createRequire2(import.meta.url).resolve("@charzhu/openjaw/package.json"),
21025
+ () => join16(process.cwd(), "index.js")
21026
+ ];
21027
+ for (const getAnchor of anchors) {
21028
+ try {
21029
+ const req = createRequire2(getAnchor());
21030
+ const pkgJson = req.resolve("@microsoft/workiq/package.json");
21031
+ return dirname6(pkgJson);
21032
+ } catch {
21033
+ }
21034
+ }
21035
+ return null;
21036
+ }
21037
+ function findWorkIQBinary() {
21038
+ const pkgDir = findWorkIQPackageDir();
21039
+ if (pkgDir) {
21040
+ const found = resolveWorkIQBinaryInPackage(pkgDir);
21041
+ if (found)
21042
+ return found;
21043
+ }
21044
+ for (const nm of [join16(process.cwd(), "node_modules")]) {
21045
+ const found = resolveWorkIQBinary(nm);
21046
+ if (found)
21047
+ return found;
21048
+ }
21049
+ return null;
21050
+ }
21051
+ function runWorkIQ(binaryPath, args, stdinText, timeoutMs) {
21052
+ return new Promise((resolve7) => {
21053
+ const proc = spawn3(binaryPath, args, {
21054
+ stdio: ["pipe", "pipe", "pipe"],
21055
+ windowsHide: true,
21056
+ timeout: timeoutMs
21057
+ });
21058
+ let stdout = "";
21059
+ let stderr = "";
21060
+ proc.stdout?.on("data", (d) => {
21061
+ stdout += d.toString();
21062
+ });
21063
+ proc.stderr?.on("data", (d) => {
21064
+ stderr += d.toString();
21065
+ });
21066
+ proc.on("error", (err) => {
21067
+ resolve7({ exitCode: -1, stdout, stderr, error: err.message });
21068
+ });
21069
+ proc.on("close", (code) => {
21070
+ resolve7({ exitCode: code, stdout: stdout.trim(), stderr: stderr.trim() });
21071
+ });
21072
+ if (stdinText !== void 0) {
21073
+ proc.stdin?.write(stdinText.endsWith("\n") ? stdinText : stdinText + "\n");
21074
+ }
21075
+ proc.stdin?.end();
21076
+ });
21077
+ }
21078
+ function createWorkIQTools(_config) {
21079
+ return [
21080
+ {
21081
+ name: "workiq_ask",
21082
+ description: 'Query Microsoft 365 work data (your emails, meetings, calendar, documents, people, projects) by asking a natural-language question. Backed by the WorkIQ CLI. Prefer this for "what/who/when" questions about your work context. First use requires accepting the WorkIQ license via workiq_accept_eula.',
21083
+ parameters: {
21084
+ type: "object",
21085
+ properties: {
21086
+ question: {
21087
+ type: "string",
21088
+ description: 'The natural-language question about your M365 work data, e.g. "what meetings do I have tomorrow?"'
21089
+ },
21090
+ timeout: {
21091
+ type: "number",
21092
+ description: "Timeout in milliseconds (default 120000, max 300000)."
21093
+ }
21094
+ },
21095
+ required: ["question"]
21096
+ },
21097
+ requiresConfirmation: false,
21098
+ execute: /* @__PURE__ */ __name(async (input) => {
21099
+ if (!isWorkIQEulaAccepted())
21100
+ return EULA_REFUSAL;
21101
+ const binary = findWorkIQBinary();
21102
+ if (!binary) {
21103
+ return {
21104
+ error: "WorkIQ CLI is unavailable: no @microsoft/workiq binary was found for this platform/arch. It ships as a bundled dependency \u2014 try reinstalling openjaw-mcp dependencies (`npm install`)."
21105
+ };
21106
+ }
21107
+ const question = String(input.question ?? "").trim();
21108
+ if (!question)
21109
+ return { error: "workiq_ask requires a non-empty question." };
21110
+ const timeout = Math.min(Number(input.timeout) || 12e4, 3e5);
21111
+ const result = await runWorkIQ(binary, ["ask"], question, timeout);
21112
+ if (result.error) {
21113
+ return { error: `WorkIQ failed to run: ${result.error}`, stderr: result.stderr };
21114
+ }
21115
+ return {
21116
+ answer: result.stdout,
21117
+ ...result.stderr ? { stderr: result.stderr } : {},
21118
+ success: result.exitCode === 0,
21119
+ ...result.exitCode !== 0 ? { exitCode: result.exitCode } : {}
21120
+ };
21121
+ }, "execute")
21122
+ },
21123
+ {
21124
+ name: "workiq_accept_eula",
21125
+ description: "Accept the WorkIQ CLI license agreement (one-time, required before workiq_ask works). Runs `workiq accept-eula`; the CLI records acceptance in ~/.work-iq-cli/.workiq.json and it persists across all apps on this machine.",
21126
+ parameters: {
21127
+ type: "object",
21128
+ properties: {}
21129
+ },
21130
+ requiresConfirmation: true,
21131
+ execute: /* @__PURE__ */ __name(async () => {
21132
+ const binary = findWorkIQBinary();
21133
+ if (!binary) {
21134
+ return {
21135
+ error: "WorkIQ CLI is unavailable: no @microsoft/workiq binary was found for this platform/arch. It ships as a bundled dependency \u2014 try reinstalling openjaw-mcp dependencies (`npm install`)."
21136
+ };
21137
+ }
21138
+ if (isWorkIQEulaAccepted()) {
21139
+ return { accepted: true, message: "WorkIQ license was already accepted." };
21140
+ }
21141
+ const result = await runWorkIQ(binary, ["accept-eula"], void 0, 6e4);
21142
+ if (result.error) {
21143
+ return { error: `Failed to run accept-eula: ${result.error}`, stderr: result.stderr };
21144
+ }
21145
+ const accepted = isWorkIQEulaAccepted();
21146
+ return {
21147
+ accepted,
21148
+ success: result.exitCode === 0 && accepted,
21149
+ ...result.stdout ? { output: result.stdout } : {},
21150
+ ...result.stderr ? { stderr: result.stderr } : {},
21151
+ ...!accepted ? { message: "accept-eula ran but acceptance was not recorded; the user may have declined in the CLI." } : {}
21152
+ };
21153
+ }, "execute")
21154
+ }
21155
+ ];
21156
+ }
21157
+ var EULA_REFUSAL;
21158
+ var init_workiq = __esm({
21159
+ "../openjaw-mcp/dist/tools/workiq.js"() {
21160
+ "use strict";
21161
+ __name(workiqStateFilePath, "workiqStateFilePath");
21162
+ __name(workiqPlatformDir, "workiqPlatformDir");
21163
+ __name(ensureExecutable, "ensureExecutable");
21164
+ __name(resolveWorkIQBinaryInPackage, "resolveWorkIQBinaryInPackage");
21165
+ __name(resolveWorkIQBinary, "resolveWorkIQBinary");
21166
+ __name(isWorkIQEulaAccepted, "isWorkIQEulaAccepted");
21167
+ __name(findWorkIQPackageDir, "findWorkIQPackageDir");
21168
+ __name(findWorkIQBinary, "findWorkIQBinary");
21169
+ __name(runWorkIQ, "runWorkIQ");
21170
+ EULA_REFUSAL = {
21171
+ eula_required: true,
21172
+ message: "WorkIQ requires a one-time license agreement before first use. Call the `workiq_accept_eula` tool to accept it (the user will be asked to confirm), then retry `workiq_ask`."
21173
+ };
21174
+ __name(createWorkIQTools, "createWorkIQTools");
21175
+ }
21176
+ });
21177
+
20264
21178
  // ../openjaw-mcp/dist/tools/categories.js
20265
21179
  function categoryForTool2(toolName) {
20266
21180
  for (const [prefix, category] of TOOL_PREFIX_MAP) {
@@ -20282,12 +21196,16 @@ var init_categories = __esm({
20282
21196
  init_memory();
20283
21197
  init_office();
20284
21198
  init_wechat();
21199
+ init_workiq();
20285
21200
  TOOL_CATEGORIES = {
20286
21201
  browser: /* @__PURE__ */ __name((config, browser) => createBrowseTools(config, browser), "browser"),
20287
21202
  email: /* @__PURE__ */ __name((config, browser) => createEmailTools(config, browser), "email"),
20288
21203
  teams: /* @__PURE__ */ __name((config, browser) => createChatTools(config, browser), "teams"),
20289
21204
  files: /* @__PURE__ */ __name((config) => createFileTools(config), "files"),
20290
- system: /* @__PURE__ */ __name((config, _browser, hooks) => createShellTools(config, hooks), "system"),
21205
+ // WorkIQ CLI tools (M365 natural-language query) ride the always-loaded
21206
+ // `system` category alongside shell tools — no separate profile wiring,
21207
+ // and they auth themselves so there's nothing to gate at load time.
21208
+ system: /* @__PURE__ */ __name((config, _browser, hooks) => [...createShellTools(config, hooks), ...createWorkIQTools(config)], "system"),
20291
21209
  memory: /* @__PURE__ */ __name((config) => createMemoryTools(config), "memory"),
20292
21210
  office: /* @__PURE__ */ __name((config) => createOfficeTools(config), "office"),
20293
21211
  wechat: /* @__PURE__ */ __name((config) => createWeChatTools(config), "wechat")
@@ -20325,6 +21243,7 @@ var init_categories = __esm({
20325
21243
  ["sleep", "system"],
20326
21244
  ["ask_user", "system"],
20327
21245
  ["config", "system"],
21246
+ ["workiq_", "system"],
20328
21247
  ["todo_write", "memory"]
20329
21248
  ];
20330
21249
  __name(categoryForTool2, "categoryForTool");
@@ -20593,11 +21512,11 @@ var init_registry = __esm({
20593
21512
  });
20594
21513
 
20595
21514
  // src/prompts/identity.ts
20596
- import { readFileSync as readFileSync9 } from "node:fs";
20597
- import { join as join16 } from "node:path";
21515
+ import { readFileSync as readFileSync10 } from "node:fs";
21516
+ import { join as join17 } from "node:path";
20598
21517
  function getIdentitySection() {
20599
21518
  if (cached2 === void 0) {
20600
- cached2 = readFileSync9(join16(packagePromptsDir(), "IDENTITY.md"), "utf8");
21519
+ cached2 = readFileSync10(join17(packagePromptsDir(), "IDENTITY.md"), "utf8");
20601
21520
  }
20602
21521
  return cached2;
20603
21522
  }
@@ -20611,11 +21530,11 @@ var init_identity = __esm({
20611
21530
  });
20612
21531
 
20613
21532
  // src/prompts/reasoning.ts
20614
- import { readFileSync as readFileSync10 } from "node:fs";
20615
- import { join as join17 } from "node:path";
21533
+ import { readFileSync as readFileSync11 } from "node:fs";
21534
+ import { join as join18 } from "node:path";
20616
21535
  function getReasoningSection() {
20617
21536
  if (cached3 === void 0) {
20618
- cached3 = readFileSync10(join17(packagePromptsDir(), "REASONING.md"), "utf8");
21537
+ cached3 = readFileSync11(join18(packagePromptsDir(), "REASONING.md"), "utf8");
20619
21538
  }
20620
21539
  return cached3;
20621
21540
  }
@@ -20629,11 +21548,11 @@ var init_reasoning = __esm({
20629
21548
  });
20630
21549
 
20631
21550
  // src/prompts/safety.ts
20632
- import { readFileSync as readFileSync11 } from "node:fs";
20633
- import { join as join18 } from "node:path";
21551
+ import { readFileSync as readFileSync12 } from "node:fs";
21552
+ import { join as join19 } from "node:path";
20634
21553
  function getSafetySection() {
20635
21554
  if (cached4 === void 0) {
20636
- cached4 = readFileSync11(join18(packagePromptsDir(), "SAFETY.md"), "utf8");
21555
+ cached4 = readFileSync12(join19(packagePromptsDir(), "SAFETY.md"), "utf8");
20637
21556
  }
20638
21557
  return cached4;
20639
21558
  }
@@ -20647,11 +21566,11 @@ var init_safety = __esm({
20647
21566
  });
20648
21567
 
20649
21568
  // src/prompts/computerUse.ts
20650
- import { readFileSync as readFileSync12 } from "node:fs";
20651
- import { join as join19 } from "node:path";
21569
+ import { readFileSync as readFileSync13 } from "node:fs";
21570
+ import { join as join20 } from "node:path";
20652
21571
  function getComputerUseSection() {
20653
21572
  if (cached5 === void 0) {
20654
- cached5 = readFileSync12(join19(packagePromptsDir(), "COMPUTER_USE.md"), "utf8");
21573
+ cached5 = readFileSync13(join20(packagePromptsDir(), "COMPUTER_USE.md"), "utf8");
20655
21574
  }
20656
21575
  return cached5;
20657
21576
  }
@@ -20665,19 +21584,19 @@ var init_computerUse = __esm({
20665
21584
  });
20666
21585
 
20667
21586
  // src/prompts/user.ts
20668
- import { readFileSync as readFileSync13, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
20669
- import { join as join20 } from "node:path";
20670
- import { homedir as homedir10 } from "node:os";
21587
+ import { readFileSync as readFileSync14, existsSync as existsSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
21588
+ import { join as join21 } from "node:path";
21589
+ import { homedir as homedir11 } from "node:os";
20671
21590
  function getUserSection() {
20672
21591
  if (cached6 === void 0) {
20673
- if (!existsSync13(userOverridePath)) {
20674
- const bundledPath = join20(packagePromptsDir(), "USER.md");
20675
- if (existsSync13(bundledPath)) {
20676
- if (!existsSync13(configDir)) mkdirSync10(configDir, { recursive: true });
20677
- writeFileSync9(userOverridePath, readFileSync13(bundledPath, "utf8"), "utf8");
21592
+ if (!existsSync15(userOverridePath)) {
21593
+ const bundledPath = join21(packagePromptsDir(), "USER.md");
21594
+ if (existsSync15(bundledPath)) {
21595
+ if (!existsSync15(configDir)) mkdirSync10(configDir, { recursive: true });
21596
+ writeFileSync9(userOverridePath, readFileSync14(bundledPath, "utf8"), "utf8");
20678
21597
  }
20679
21598
  }
20680
- cached6 = existsSync13(userOverridePath) ? readFileSync13(userOverridePath, "utf8") : null;
21599
+ cached6 = existsSync15(userOverridePath) ? readFileSync14(userOverridePath, "utf8") : null;
20681
21600
  }
20682
21601
  return cached6;
20683
21602
  }
@@ -20686,8 +21605,8 @@ var init_user = __esm({
20686
21605
  "src/prompts/user.ts"() {
20687
21606
  "use strict";
20688
21607
  init_packageRoot();
20689
- configDir = join20(homedir10(), ".openjaw-agent");
20690
- userOverridePath = join20(configDir, "USER.md");
21608
+ configDir = join21(homedir11(), ".openjaw-agent");
21609
+ userOverridePath = join21(configDir, "USER.md");
20691
21610
  __name(getUserSection, "getUserSection");
20692
21611
  }
20693
21612
  });
@@ -20699,8 +21618,8 @@ __export(memory_exports, {
20699
21618
  setMemoryPrefetchQuery: () => setMemoryPrefetchQuery,
20700
21619
  shouldSkipPrefetch: () => shouldSkipPrefetch
20701
21620
  });
20702
- import { join as join21 } from "node:path";
20703
- import { homedir as homedir11 } from "node:os";
21621
+ import { join as join22 } from "node:path";
21622
+ import { homedir as homedir12 } from "node:os";
20704
21623
  function setMemoryPrefetchQuery(query) {
20705
21624
  currentQuery = query;
20706
21625
  }
@@ -20751,7 +21670,7 @@ function getMemorySection() {
20751
21670
  if (shouldSkipPrefetch(currentQuery)) return null;
20752
21671
  try {
20753
21672
  const { DatabaseSync: DatabaseSync2 } = __require("node:sqlite");
20754
- const dbPath = join21(homedir11(), ".openjaw", "memory.db");
21673
+ const dbPath = join22(homedir12(), ".openjaw", "memory.db");
20755
21674
  let db2;
20756
21675
  try {
20757
21676
  db2 = new DatabaseSync2(dbPath, { readOnly: true });
@@ -20966,9 +21885,9 @@ var init_frontmatter = __esm({
20966
21885
  });
20967
21886
 
20968
21887
  // src/skills/registry.ts
20969
- import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2, statSync } from "node:fs";
20970
- import { join as join22 } from "node:path";
20971
- import { homedir as homedir12 } from "node:os";
21888
+ import { existsSync as existsSync16, readFileSync as readFileSync15, readdirSync as readdirSync2, statSync } from "node:fs";
21889
+ import { join as join23 } from "node:path";
21890
+ import { homedir as homedir13 } from "node:os";
20972
21891
  function skillRoots() {
20973
21892
  return rootsOverride ?? { bundledDir: packageSkillsDir(), userDir: DEFAULT_USER_DIR };
20974
21893
  }
@@ -20988,7 +21907,7 @@ function loadSkillBody(skillName) {
20988
21907
  const skill = findSkill(skillName);
20989
21908
  if (!skill) return null;
20990
21909
  try {
20991
- const content = readFileSync14(skill.filePath, "utf-8").trim();
21910
+ const content = readFileSync15(skill.filePath, "utf-8").trim();
20992
21911
  const parsed = parseSkillFile(content, `${skill.name}.md`);
20993
21912
  return parsed.body;
20994
21913
  } catch {
@@ -21044,7 +21963,7 @@ function normalizeSkillName(name) {
21044
21963
  function loadFlatSkillsFromDir(dir2, source, priority, out) {
21045
21964
  for (const entry of safeReadDir(dir2)) {
21046
21965
  if (!entry.isFile() || !isMarkdown(entry.name)) continue;
21047
- const filePath = join22(dir2, entry.name);
21966
+ const filePath = join23(dir2, entry.name);
21048
21967
  const skill = parseSkillAtPath(filePath, dir2, entry.name, source, entry.name);
21049
21968
  if (skill) putSkill(out, skill, priority);
21050
21969
  }
@@ -21052,10 +21971,10 @@ function loadFlatSkillsFromDir(dir2, source, priority, out) {
21052
21971
  function loadPackagedSkillsFromDir(dir2, out) {
21053
21972
  for (const entry of safeReadDir(dir2)) {
21054
21973
  if (!entry.isDirectory()) continue;
21055
- const rootDir2 = join22(dir2, entry.name);
21974
+ const rootDir2 = join23(dir2, entry.name);
21056
21975
  const entrypoint = findPackageEntrypoint(rootDir2);
21057
21976
  if (!entrypoint) continue;
21058
- const filePath = join22(rootDir2, entrypoint);
21977
+ const filePath = join23(rootDir2, entrypoint);
21059
21978
  const skill = parseSkillAtPath(filePath, rootDir2, entrypoint, "user", `${entry.name}.md`);
21060
21979
  if (skill) putSkill(out, skill, 2);
21061
21980
  }
@@ -21071,7 +21990,7 @@ function findPackageEntrypoint(dir2) {
21071
21990
  }
21072
21991
  function parseSkillAtPath(filePath, rootDir2, entrypoint, source, fallbackFilename) {
21073
21992
  try {
21074
- const content = readFileSync14(filePath, "utf-8").trim();
21993
+ const content = readFileSync15(filePath, "utf-8").trim();
21075
21994
  if (!content) return null;
21076
21995
  const parsed = parseSkillFile(content, fallbackFilename);
21077
21996
  const name = normalizeSkillName(parsed.meta.name);
@@ -21096,7 +22015,7 @@ function putSkill(out, skill, priority) {
21096
22015
  }
21097
22016
  }
21098
22017
  function safeReadDir(dir2) {
21099
- if (!existsSync14(dir2)) return [];
22018
+ if (!existsSync16(dir2)) return [];
21100
22019
  try {
21101
22020
  return readdirSync2(dir2, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21102
22021
  } catch {
@@ -21108,11 +22027,11 @@ function buildRegistrySignature() {
21108
22027
  return [signatureForDir(roots.bundledDir), signatureForDir(roots.userDir)].join("|");
21109
22028
  }
21110
22029
  function signatureForDir(dir2) {
21111
- if (!existsSync14(dir2)) return `${dir2}:missing`;
22030
+ if (!existsSync16(dir2)) return `${dir2}:missing`;
21112
22031
  const parts = [];
21113
22032
  try {
21114
22033
  for (const entry of safeReadDir(dir2)) {
21115
- const fullPath = join22(dir2, entry.name);
22034
+ const fullPath = join23(dir2, entry.name);
21116
22035
  if (entry.isFile()) {
21117
22036
  if (!isMarkdown(entry.name)) continue;
21118
22037
  parts.push(fileSignature(fullPath, `f:${entry.name}`));
@@ -21120,7 +22039,7 @@ function signatureForDir(dir2) {
21120
22039
  parts.push(fileSignature(fullPath, `d:${entry.name}`));
21121
22040
  for (const child of safeReadDir(fullPath)) {
21122
22041
  if (child.isFile() && isMarkdown(child.name)) {
21123
- parts.push(fileSignature(join22(fullPath, child.name), `d:${entry.name}/${child.name}`));
22042
+ parts.push(fileSignature(join23(fullPath, child.name), `d:${entry.name}/${child.name}`));
21124
22043
  }
21125
22044
  }
21126
22045
  }
@@ -21147,7 +22066,7 @@ var init_registry2 = __esm({
21147
22066
  "use strict";
21148
22067
  init_frontmatter();
21149
22068
  init_packageRoot();
21150
- DEFAULT_USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
22069
+ DEFAULT_USER_DIR = join23(homedir13(), ".openjaw-agent", "skills");
21151
22070
  ENTRYPOINT_NAMES = ["SKILL.md", "skill.md"];
21152
22071
  cachedSkills = null;
21153
22072
  rootsOverride = null;
@@ -21213,9 +22132,9 @@ __export(context_exports, {
21213
22132
  getContextSection: () => getContextSection,
21214
22133
  setActiveBridges: () => setActiveBridges
21215
22134
  });
21216
- import { existsSync as existsSync15, readFileSync as readFileSync15, readdirSync as readdirSync3 } from "node:fs";
22135
+ import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync3 } from "node:fs";
21217
22136
  import { execSync as execSync4 } from "node:child_process";
21218
- import { join as join23 } from "node:path";
22137
+ import { join as join24 } from "node:path";
21219
22138
  function detectProjectInfo() {
21220
22139
  const cwd = process.cwd();
21221
22140
  const lines = [];
@@ -21229,9 +22148,9 @@ function detectProjectInfo() {
21229
22148
  } catch {
21230
22149
  }
21231
22150
  try {
21232
- const pkgPath = join23(cwd, "package.json");
21233
- if (existsSync15(pkgPath)) {
21234
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf8"));
22151
+ const pkgPath = join24(cwd, "package.json");
22152
+ if (existsSync17(pkgPath)) {
22153
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
21235
22154
  const name = pkg.name || "unknown";
21236
22155
  const desc = pkg.description ? ` \u2014 ${pkg.description}`.slice(0, 120) : "";
21237
22156
  lines.push(`- Project: ${name}${desc}`);
@@ -21255,7 +22174,7 @@ function detectProjectInfo() {
21255
22174
  ["package.json", "JavaScript"]
21256
22175
  ];
21257
22176
  for (const [file2, lang] of indicators) {
21258
- if (existsSync15(join23(cwd, file2))) {
22177
+ if (existsSync17(join24(cwd, file2))) {
21259
22178
  lines.push(`- Language: ${lang} (${file2})`);
21260
22179
  break;
21261
22180
  }
@@ -21269,9 +22188,9 @@ function detectProjectInfo() {
21269
22188
  } catch {
21270
22189
  }
21271
22190
  try {
21272
- const readmePath = join23(cwd, "README.md");
21273
- if (existsSync15(readmePath)) {
21274
- let content = readFileSync15(readmePath, "utf8").slice(0, 500).replace(/\r?\n/g, " ").trim();
22191
+ const readmePath = join24(cwd, "README.md");
22192
+ if (existsSync17(readmePath)) {
22193
+ let content = readFileSync16(readmePath, "utf8").slice(0, 500).replace(/\r?\n/g, " ").trim();
21275
22194
  if (content.length > 300) content = content.slice(0, 297) + "...";
21276
22195
  if (content) lines.push(`- README: ${content}`);
21277
22196
  }
@@ -21380,6 +22299,30 @@ var init_mcp = __esm({
21380
22299
  }
21381
22300
  });
21382
22301
 
22302
+ // src/prompts/workiq.ts
22303
+ function getWorkIQSection() {
22304
+ if (cached7 === void 0) {
22305
+ cached7 = [
22306
+ "## Microsoft 365 work data (WorkIQ)",
22307
+ "",
22308
+ 'You can answer questions about the user\'s work context \u2014 their emails, meetings, calendar, documents, people, and projects \u2014 using the `workiq_ask` tool. Pass a natural-language question (e.g. "what meetings do I have tomorrow?", "summarize my unread email from Alice") and it returns an answer from the WorkIQ CLI.',
22309
+ "",
22310
+ "Guidance:",
22311
+ "- Prefer `workiq_ask` over web search or guessing when the user asks about *their own* work data (meetings, emails, documents, people, projects).",
22312
+ "- First use requires a one-time license acceptance. If `workiq_ask` returns `eula_required`, call `workiq_accept_eula` (the user will be asked to confirm) and then retry the original question.",
22313
+ "- `workiq_ask` signs the user in on first use via the system account; you do not need to handle authentication."
22314
+ ].join("\n");
22315
+ }
22316
+ return cached7;
22317
+ }
22318
+ var cached7;
22319
+ var init_workiq2 = __esm({
22320
+ "src/prompts/workiq.ts"() {
22321
+ "use strict";
22322
+ __name(getWorkIQSection, "getWorkIQSection");
22323
+ }
22324
+ });
22325
+
21383
22326
  // src/prompts/index.ts
21384
22327
  var prompts_exports = {};
21385
22328
  __export(prompts_exports, {
@@ -21408,6 +22351,7 @@ var init_prompts = __esm({
21408
22351
  init_skills();
21409
22352
  init_context();
21410
22353
  init_mcp();
22354
+ init_workiq2();
21411
22355
  init_sections();
21412
22356
  buildSections = /* @__PURE__ */ __name((opts = {}) => [
21413
22357
  // Static sections (cached across calls)
@@ -21415,6 +22359,7 @@ var init_prompts = __esm({
21415
22359
  systemPromptSection("reasoning", () => getReasoningSection()),
21416
22360
  systemPromptSection("safety", () => getSafetySection()),
21417
22361
  systemPromptSection("computerUse", () => getComputerUseSection()),
22362
+ systemPromptSection("workiq", () => getWorkIQSection()),
21418
22363
  // Dynamic boundary marker
21419
22364
  systemPromptSection("boundary", () => SYSTEM_PROMPT_DYNAMIC_BOUNDARY),
21420
22365
  // Dynamic sections (memoized within a resolve cycle via section cache)
@@ -21534,9 +22479,9 @@ __export(telegram_exports, {
21534
22479
  TelegramBridge: () => TelegramBridge
21535
22480
  });
21536
22481
  import TelegramBot from "node-telegram-bot-api";
21537
- import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, existsSync as existsSync16 } from "node:fs";
21538
- import { join as join24 } from "node:path";
21539
- import { tmpdir as tmpdir4, homedir as homedir13 } from "node:os";
22482
+ import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync4, existsSync as existsSync18 } from "node:fs";
22483
+ import { join as join25 } from "node:path";
22484
+ import { tmpdir as tmpdir4, homedir as homedir14 } from "node:os";
21540
22485
  import { randomUUID as randomUUID4 } from "node:crypto";
21541
22486
  var MAX_TELEGRAM_MSG, MIME_TYPES, TelegramBridge;
21542
22487
  var init_telegram = __esm({
@@ -21737,12 +22682,12 @@ var init_telegram = __esm({
21737
22682
  Options: ${chunk.choices.join(" | ")}` : "";
21738
22683
  await this.bot.sendMessage(chatId, `\u2753 ${question}${choicesText}`);
21739
22684
  updateStatus("\u2753 Waiting for your response...");
21740
- const userReply = await new Promise((resolve6) => {
21741
- this._pendingReplyResolver = resolve6;
22685
+ const userReply = await new Promise((resolve7) => {
22686
+ this._pendingReplyResolver = resolve7;
21742
22687
  setTimeout(() => {
21743
- if (this._pendingReplyResolver === resolve6) {
22688
+ if (this._pendingReplyResolver === resolve7) {
21744
22689
  this._pendingReplyResolver = null;
21745
- resolve6("[No response \u2014 timed out]");
22690
+ resolve7("[No response \u2014 timed out]");
21746
22691
  }
21747
22692
  }, 5 * 60 * 1e3);
21748
22693
  });
@@ -21779,7 +22724,7 @@ Options: ${chunk.choices.join(" | ")}` : "";
21779
22724
  try {
21780
22725
  const fileId = voice.file_id;
21781
22726
  const filePath = await this.bot.getFileLink(fileId);
21782
- const tempFile = join24(tmpdir4(), `oj-tg-voice-${randomUUID4().slice(0, 8)}.ogg`);
22727
+ const tempFile = join25(tmpdir4(), `oj-tg-voice-${randomUUID4().slice(0, 8)}.ogg`);
21783
22728
  const response = await fetch(filePath);
21784
22729
  const buffer = Buffer.from(await response.arrayBuffer());
21785
22730
  writeFileSync10(tempFile, buffer);
@@ -21795,7 +22740,7 @@ Options: ${chunk.choices.join(" | ")}` : "";
21795
22740
  transcription = "[Could not transcribe voice message. Please type your message instead.]";
21796
22741
  }
21797
22742
  try {
21798
- if (existsSync16(tempFile)) unlinkSync3(tempFile);
22743
+ if (existsSync18(tempFile)) unlinkSync4(tempFile);
21799
22744
  } catch {
21800
22745
  }
21801
22746
  if (transcription.startsWith("[")) {
@@ -21853,7 +22798,7 @@ Options: ${chunk.choices.join(" | ")}` : "";
21853
22798
  const response = await fetch(filePath);
21854
22799
  const buffer = Buffer.from(await response.arrayBuffer());
21855
22800
  const ext = fileName.includes(".") ? "." + fileName.split(".").pop() : "";
21856
- const tempPath = join24(tmpdir4(), `oj-tg-doc-${randomUUID4().slice(0, 8)}${ext}`);
22801
+ const tempPath = join25(tmpdir4(), `oj-tg-doc-${randomUUID4().slice(0, 8)}${ext}`);
21857
22802
  writeFileSync10(tempPath, buffer);
21858
22803
  const text = caption || `I've sent you a file: ${fileName}. It's saved at ${tempPath}. Please analyze it.`;
21859
22804
  const fullText = `${text}
@@ -21861,7 +22806,7 @@ Options: ${chunk.choices.join(" | ")}` : "";
21861
22806
  [File received: ${fileName} (${buffer.length} bytes), saved to: ${tempPath}]`;
21862
22807
  await this.handleText(chatId, fullText);
21863
22808
  try {
21864
- if (existsSync16(tempPath)) unlinkSync3(tempPath);
22809
+ if (existsSync18(tempPath)) unlinkSync4(tempPath);
21865
22810
  } catch {
21866
22811
  }
21867
22812
  } catch {
@@ -21887,12 +22832,12 @@ Options: ${chunk.choices.join(" | ")}` : "";
21887
22832
  let match;
21888
22833
  while ((match = pattern.exec(normalized)) !== null) {
21889
22834
  const filePath = match[1];
21890
- const resolvedPath = filePath.startsWith("~") ? join24(homedir13(), filePath.slice(1)) : filePath;
22835
+ const resolvedPath = filePath.startsWith("~") ? join25(homedir14(), filePath.slice(1)) : filePath;
21891
22836
  if (sentFiles.has(resolvedPath)) continue;
21892
22837
  if (filePath.includes("http") || filePath.includes("node_modules")) continue;
21893
22838
  const fileName = filePath.split(/[\\/]/).pop() || "";
21894
22839
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
21895
- if (existsSync16(resolvedPath)) {
22840
+ if (existsSync18(resolvedPath)) {
21896
22841
  try {
21897
22842
  const { statSync: statSync5 } = await import("node:fs");
21898
22843
  const stat2 = statSync5(resolvedPath);
@@ -22056,26 +23001,27 @@ var init_meta = __esm({
22056
23001
  // src/mcp-client.ts
22057
23002
  var mcp_client_exports = {};
22058
23003
  __export(mcp_client_exports, {
22059
- MCPClientManager: () => MCPClientManager
23004
+ MCPClientManager: () => MCPClientManager,
23005
+ restoreStdinAfterReadlinePrompt: () => restoreStdinAfterReadlinePrompt
22060
23006
  });
22061
23007
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
22062
23008
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
22063
23009
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
22064
23010
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
22065
- import { existsSync as existsSync17, readFileSync as readFileSync17, readdirSync as readdirSync4, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11 } from "node:fs";
22066
- import { join as join25, dirname as dirname5, resolve } from "node:path";
22067
- import { homedir as homedir14 } from "node:os";
23011
+ import { existsSync as existsSync19, readFileSync as readFileSync18, readdirSync as readdirSync4, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11 } from "node:fs";
23012
+ import { join as join26, dirname as dirname7, resolve as resolve2 } from "node:path";
23013
+ import { homedir as homedir15 } from "node:os";
22068
23014
  import { execSync as execSync5 } from "node:child_process";
22069
23015
  import { EventEmitter } from "node:events";
22070
23016
  import * as readline from "node:readline";
22071
23017
  function findCopilotOAuthToken(serverUrl) {
22072
- if (!existsSync17(COPILOT_OAUTH_DIR)) return null;
23018
+ if (!existsSync19(COPILOT_OAUTH_DIR)) return null;
22073
23019
  try {
22074
23020
  const files = readdirSync4(COPILOT_OAUTH_DIR).filter((f) => f.endsWith(".tokens.json"));
22075
23021
  for (const file2 of files) {
22076
- const filePath = join25(COPILOT_OAUTH_DIR, file2);
23022
+ const filePath = join26(COPILOT_OAUTH_DIR, file2);
22077
23023
  try {
22078
- const data = JSON.parse(readFileSync17(filePath, "utf-8"));
23024
+ const data = JSON.parse(readFileSync18(filePath, "utf-8"));
22079
23025
  if (data.scope && data.accessToken) {
22080
23026
  const urlDomain = new URL(serverUrl).hostname;
22081
23027
  if (data.scope.includes(urlDomain)) {
@@ -22091,18 +23037,18 @@ function findCopilotOAuthToken(serverUrl) {
22091
23037
  }
22092
23038
  function refreshCopilotToken(refreshToken, tokenFilePath) {
22093
23039
  try {
22094
- const existing = JSON.parse(readFileSync17(tokenFilePath, "utf-8"));
23040
+ const existing = JSON.parse(readFileSync18(tokenFilePath, "utf-8"));
22095
23041
  const jwtParts = existing.accessToken.split(".");
22096
23042
  if (jwtParts.length < 2) return null;
22097
23043
  const padded = jwtParts[1] + "=".repeat(4 - jwtParts[1].length % 4);
22098
23044
  const claims = JSON.parse(Buffer.from(padded, "base64").toString("utf-8"));
22099
- const clientId = claims.azp;
23045
+ const clientId2 = claims.azp;
22100
23046
  const tid = claims.tid;
22101
23047
  const aud = claims.aud;
22102
- if (!clientId || !tid || !aud) return null;
23048
+ if (!clientId2 || !tid || !aud) return null;
22103
23049
  const scope = `${aud}/.default offline_access`;
22104
23050
  const bodyParams = [
22105
- `client_id=${clientId}`,
23051
+ `client_id=${clientId2}`,
22106
23052
  `grant_type=refresh_token`,
22107
23053
  `refresh_token=${encodeURIComponent(refreshToken)}`,
22108
23054
  `scope=${encodeURIComponent(scope)}`
@@ -22162,7 +23108,7 @@ function discoverAllConfigs(cwd) {
22162
23108
  for (const { name: pluginName, servers } of pluginConfigs) {
22163
23109
  addServers(servers, `~/.copilot/.../plugins/${pluginName}/.mcp.json`, "plugin", false);
22164
23110
  }
22165
- const vscodeConfig = readVSCodeMcpConfig(join25(cwd, ".vscode", "mcp.json"), cwd);
23111
+ const vscodeConfig = readVSCodeMcpConfig(join26(cwd, ".vscode", "mcp.json"), cwd);
22166
23112
  if (vscodeConfig) {
22167
23113
  addServers(vscodeConfig, ".vscode/mcp.json", "vscode", false);
22168
23114
  }
@@ -22173,18 +23119,18 @@ function discoverAllConfigs(cwd) {
22173
23119
  return result;
22174
23120
  }
22175
23121
  function readMcpConfig(filePath) {
22176
- if (!existsSync17(filePath)) return null;
23122
+ if (!existsSync19(filePath)) return null;
22177
23123
  try {
22178
- const raw = JSON.parse(readFileSync17(filePath, "utf-8"));
23124
+ const raw = JSON.parse(readFileSync18(filePath, "utf-8"));
22179
23125
  return raw.mcpServers || null;
22180
23126
  } catch {
22181
23127
  return null;
22182
23128
  }
22183
23129
  }
22184
23130
  function readVSCodeMcpConfig(filePath, cwd) {
22185
- if (!existsSync17(filePath)) return null;
23131
+ if (!existsSync19(filePath)) return null;
22186
23132
  try {
22187
- const raw = JSON.parse(readFileSync17(filePath, "utf-8"));
23133
+ const raw = JSON.parse(readFileSync18(filePath, "utf-8"));
22188
23134
  const servers = raw.servers;
22189
23135
  if (!servers || typeof servers !== "object") return null;
22190
23136
  const result = {};
@@ -22205,10 +23151,10 @@ function readVSCodeMcpConfig(filePath, cwd) {
22205
23151
  }
22206
23152
  function walkParentsForMcpJson(startDir) {
22207
23153
  const results = [];
22208
- let dir2 = resolve(startDir);
22209
- const root = dirname5(dir2) === dir2 ? dir2 : "";
23154
+ let dir2 = resolve2(startDir);
23155
+ const root = dirname7(dir2) === dir2 ? dir2 : "";
22210
23156
  while (dir2 && dir2 !== root) {
22211
- const filePath = join25(dir2, ".mcp.json");
23157
+ const filePath = join26(dir2, ".mcp.json");
22212
23158
  const config = readMcpConfig(filePath);
22213
23159
  if (config) {
22214
23160
  const filtered = {};
@@ -22219,20 +23165,20 @@ function walkParentsForMcpJson(startDir) {
22219
23165
  results.push({ path: filePath, servers: filtered });
22220
23166
  }
22221
23167
  }
22222
- const parent = dirname5(dir2);
23168
+ const parent = dirname7(dir2);
22223
23169
  if (parent === dir2) break;
22224
23170
  dir2 = parent;
22225
23171
  }
22226
23172
  return results;
22227
23173
  }
22228
23174
  function discoverCopilotPlugins() {
22229
- if (!existsSync17(COPILOT_PLUGINS_DIR)) return [];
23175
+ if (!existsSync19(COPILOT_PLUGINS_DIR)) return [];
22230
23176
  const results = [];
22231
23177
  try {
22232
23178
  const entries = readdirSync4(COPILOT_PLUGINS_DIR, { withFileTypes: true });
22233
23179
  for (const entry of entries) {
22234
23180
  if (!entry.isDirectory()) continue;
22235
- const mcpPath = join25(COPILOT_PLUGINS_DIR, entry.name, ".mcp.json");
23181
+ const mcpPath = join26(COPILOT_PLUGINS_DIR, entry.name, ".mcp.json");
22236
23182
  const config = readMcpConfig(mcpPath);
22237
23183
  if (config) {
22238
23184
  const filtered = {};
@@ -22249,9 +23195,9 @@ function discoverCopilotPlugins() {
22249
23195
  return results;
22250
23196
  }
22251
23197
  function discoverAgencyBuiltins() {
22252
- if (!existsSync17(AGENCY_CONFIG) || !existsSync17(AGENCY_EXE)) return {};
23198
+ if (!existsSync19(AGENCY_CONFIG) || !existsSync19(AGENCY_EXE)) return {};
22253
23199
  try {
22254
- const content = readFileSync17(AGENCY_CONFIG, "utf-8");
23200
+ const content = readFileSync18(AGENCY_CONFIG, "utf-8");
22255
23201
  const result = {};
22256
23202
  const builtinsMatch = content.match(/\[mcps\.builtins\]([\s\S]*?)(?=\n\[|$)/);
22257
23203
  if (!builtinsMatch) return {};
@@ -22319,17 +23265,17 @@ function truncateDescription(desc, maxLen) {
22319
23265
  return desc.slice(0, maxLen - 3) + "...";
22320
23266
  }
22321
23267
  function loadConsent() {
22322
- if (!existsSync17(OJ_CONSENT_FILE)) {
23268
+ if (!existsSync19(OJ_CONSENT_FILE)) {
22323
23269
  return { approved: [], denied: [] };
22324
23270
  }
22325
23271
  try {
22326
- return JSON.parse(readFileSync17(OJ_CONSENT_FILE, "utf-8"));
23272
+ return JSON.parse(readFileSync18(OJ_CONSENT_FILE, "utf-8"));
22327
23273
  } catch {
22328
23274
  return { approved: [], denied: [] };
22329
23275
  }
22330
23276
  }
22331
23277
  function saveConsent(data) {
22332
- if (!existsSync17(OJ_AGENT_DIR)) {
23278
+ if (!existsSync19(OJ_AGENT_DIR)) {
22333
23279
  mkdirSync11(OJ_AGENT_DIR, { recursive: true });
22334
23280
  }
22335
23281
  writeFileSync11(OJ_CONSENT_FILE, JSON.stringify(data, null, 2) + "\n", "utf-8");
@@ -22340,66 +23286,82 @@ async function promptConsent(servers) {
22340
23286
  input: process.stdin,
22341
23287
  output: process.stderr
22342
23288
  });
22343
- const ask = /* @__PURE__ */ __name((question) => new Promise((resolve6) => rl.question(question, resolve6)), "ask");
22344
- const w = process.stderr.columns || 80;
22345
- const inner = w - 4;
22346
- const hLine = "\u2500".repeat(inner);
22347
- const pad = /* @__PURE__ */ __name((s, len) => s + " ".repeat(Math.max(0, len - stripAnsi(s).length)), "pad");
22348
- console.error("");
22349
- console.error(` \x1B[36m\u256D${hLine}\u256E\x1B[0m`);
22350
- console.error(` \x1B[36m\u2502\x1B[0m ${pad("\x1B[36m\x1B[1m\u{1F50C} New MCP Servers Discovered\x1B[0m", inner - 1)}\x1B[36m\u2502\x1B[0m`);
22351
- console.error(` \x1B[36m\u251C${hLine}\u2524\x1B[0m`);
22352
- const bySource = /* @__PURE__ */ new Map();
22353
- for (const s of servers) {
22354
- const list = bySource.get(s.source) || [];
22355
- list.push(s);
22356
- bySource.set(s.source, list);
22357
- }
22358
- for (const [source, sourceServers] of bySource) {
22359
- const srcLine = `\x1B[2m From ${source}\x1B[0m`;
22360
- console.error(` \x1B[36m\u2502\x1B[0m ${pad(srcLine, inner - 1)}\x1B[36m\u2502\x1B[0m`);
22361
- for (const s of sourceServers) {
22362
- const cmdParts = [s.def.command, ...(s.def.args || []).slice(0, 2)];
22363
- const cmdStr = cmdParts.join(" ");
22364
- const truncCmd = cmdStr.length > inner - s.name.length - 12 ? cmdStr.slice(0, inner - s.name.length - 15) + "..." : cmdStr;
22365
- const serverLine = ` \x1B[33m\u25CB\x1B[0m \x1B[1m${s.name}\x1B[0m \x1B[2m${truncCmd}\x1B[0m`;
22366
- console.error(` \x1B[36m\u2502\x1B[0m ${pad(serverLine, inner - 1)}\x1B[36m\u2502\x1B[0m`);
22367
- }
22368
- console.error(` \x1B[36m\u2502\x1B[0m ${" ".repeat(inner - 1)}\x1B[36m\u2502\x1B[0m`);
22369
- }
22370
- console.error(` \x1B[36m\u251C${hLine}\u2524\x1B[0m`);
22371
- console.error(` \x1B[36m\u2502\x1B[0m ${pad("\x1B[2m Y = enable all \xB7 N = skip all \xB7 S = select individually\x1B[0m", inner - 1)}\x1B[36m\u2502\x1B[0m`);
22372
- console.error(` \x1B[36m\u2570${hLine}\u256F\x1B[0m`);
22373
- console.error("");
22374
- const answer = await ask(" \x1B[36m\u276F\x1B[0m Enable these servers? \x1B[2m(Y/n/s)\x1B[0m: ");
22375
- const trimmed = answer.trim().toLowerCase();
22376
- let approved;
22377
- if (trimmed === "" || trimmed === "y" || trimmed === "yes") {
22378
- approved = servers.map((s) => s.name);
22379
- console.error(` \x1B[32m\u25CF All ${approved.length} servers enabled\x1B[0m`);
22380
- } else if (trimmed === "n" || trimmed === "no") {
22381
- approved = [];
22382
- console.error(` \x1B[33m\u25CB All servers skipped\x1B[0m`);
22383
- } else if (trimmed === "s" || trimmed === "select") {
22384
- approved = [];
23289
+ try {
23290
+ const ask = /* @__PURE__ */ __name((question) => new Promise((resolve7) => rl.question(question, resolve7)), "ask");
23291
+ const w = process.stderr.columns || 80;
23292
+ const inner = w - 4;
23293
+ const hLine = "\u2500".repeat(inner);
23294
+ const pad = /* @__PURE__ */ __name((s, len) => s + " ".repeat(Math.max(0, len - stripAnsi(s).length)), "pad");
22385
23295
  console.error("");
23296
+ console.error(` \x1B[36m\u256D${hLine}\u256E\x1B[0m`);
23297
+ console.error(` \x1B[36m\u2502\x1B[0m ${pad("\x1B[36m\x1B[1m\u{1F50C} New MCP Servers Discovered\x1B[0m", inner - 1)}\x1B[36m\u2502\x1B[0m`);
23298
+ console.error(` \x1B[36m\u251C${hLine}\u2524\x1B[0m`);
23299
+ const bySource = /* @__PURE__ */ new Map();
22386
23300
  for (const s of servers) {
22387
- const a = await ask(` \x1B[36m\u276F\x1B[0m Enable \x1B[33m\x1B[1m${s.name}\x1B[0m? \x1B[2m(y/N)\x1B[0m: `);
22388
- if (a.trim().toLowerCase() === "y" || a.trim().toLowerCase() === "yes") {
22389
- approved.push(s.name);
22390
- console.error(` \x1B[32m\u25CF ${s.name} enabled\x1B[0m`);
22391
- } else {
22392
- console.error(` \x1B[2m\u25CB ${s.name} skipped\x1B[0m`);
23301
+ const list = bySource.get(s.source) || [];
23302
+ list.push(s);
23303
+ bySource.set(s.source, list);
23304
+ }
23305
+ for (const [source, sourceServers] of bySource) {
23306
+ const srcLine = `\x1B[2m From ${source}\x1B[0m`;
23307
+ console.error(` \x1B[36m\u2502\x1B[0m ${pad(srcLine, inner - 1)}\x1B[36m\u2502\x1B[0m`);
23308
+ for (const s of sourceServers) {
23309
+ const cmdParts = [s.def.command, ...(s.def.args || []).slice(0, 2)];
23310
+ const cmdStr = cmdParts.join(" ");
23311
+ const truncCmd = cmdStr.length > inner - s.name.length - 12 ? cmdStr.slice(0, inner - s.name.length - 15) + "..." : cmdStr;
23312
+ const serverLine = ` \x1B[33m\u25CB\x1B[0m \x1B[1m${s.name}\x1B[0m \x1B[2m${truncCmd}\x1B[0m`;
23313
+ console.error(` \x1B[36m\u2502\x1B[0m ${pad(serverLine, inner - 1)}\x1B[36m\u2502\x1B[0m`);
23314
+ }
23315
+ console.error(` \x1B[36m\u2502\x1B[0m ${" ".repeat(inner - 1)}\x1B[36m\u2502\x1B[0m`);
23316
+ }
23317
+ console.error(` \x1B[36m\u251C${hLine}\u2524\x1B[0m`);
23318
+ console.error(` \x1B[36m\u2502\x1B[0m ${pad("\x1B[2m Y = enable all \xB7 N = skip all \xB7 S = select individually\x1B[0m", inner - 1)}\x1B[36m\u2502\x1B[0m`);
23319
+ console.error(` \x1B[36m\u2570${hLine}\u256F\x1B[0m`);
23320
+ console.error("");
23321
+ const answer = await ask(" \x1B[36m\u276F\x1B[0m Enable these servers? \x1B[2m(Y/n/s)\x1B[0m: ");
23322
+ const trimmed = answer.trim().toLowerCase();
23323
+ let approved;
23324
+ if (trimmed === "" || trimmed === "y" || trimmed === "yes") {
23325
+ approved = servers.map((s) => s.name);
23326
+ console.error(` \x1B[32m\u25CF All ${approved.length} servers enabled\x1B[0m`);
23327
+ } else if (trimmed === "n" || trimmed === "no") {
23328
+ approved = [];
23329
+ console.error(` \x1B[33m\u25CB All servers skipped\x1B[0m`);
23330
+ } else if (trimmed === "s" || trimmed === "select") {
23331
+ approved = [];
23332
+ console.error("");
23333
+ for (const s of servers) {
23334
+ const a = await ask(` \x1B[36m\u276F\x1B[0m Enable \x1B[33m\x1B[1m${s.name}\x1B[0m? \x1B[2m(y/N)\x1B[0m: `);
23335
+ if (a.trim().toLowerCase() === "y" || a.trim().toLowerCase() === "yes") {
23336
+ approved.push(s.name);
23337
+ console.error(` \x1B[32m\u25CF ${s.name} enabled\x1B[0m`);
23338
+ } else {
23339
+ console.error(` \x1B[2m\u25CB ${s.name} skipped\x1B[0m`);
23340
+ }
22393
23341
  }
22394
- }
22395
- console.error(`
23342
+ console.error(`
22396
23343
  \x1B[32m${approved.length}/${servers.length} servers enabled\x1B[0m`);
22397
- } else {
22398
- approved = servers.map((s) => s.name);
23344
+ } else {
23345
+ approved = servers.map((s) => s.name);
23346
+ }
23347
+ console.error("");
23348
+ return approved;
23349
+ } finally {
23350
+ rl.close();
23351
+ restoreStdinAfterReadlinePrompt();
23352
+ }
23353
+ }
23354
+ function restoreStdinAfterReadlinePrompt(input = process.stdin) {
23355
+ try {
23356
+ if (input.isTTY && input.setRawMode) {
23357
+ input.setRawMode(false);
23358
+ }
23359
+ } catch {
23360
+ }
23361
+ try {
23362
+ input.pause();
23363
+ } catch {
22399
23364
  }
22400
- console.error("");
22401
- rl.close();
22402
- return approved;
22403
23365
  }
22404
23366
  function stripAnsi(str) {
22405
23367
  return str.replace(/\x1b\[[0-9;]*m/g, "");
@@ -22408,14 +23370,14 @@ var OJ_AGENT_DIR, OJ_MCP_CONFIG, OJ_CONSENT_FILE, COPILOT_MCP_CONFIG, COPILOT_PL
22408
23370
  var init_mcp_client = __esm({
22409
23371
  "src/mcp-client.ts"() {
22410
23372
  "use strict";
22411
- OJ_AGENT_DIR = join25(homedir14(), ".openjaw-agent");
22412
- OJ_MCP_CONFIG = join25(OJ_AGENT_DIR, "mcp.json");
22413
- OJ_CONSENT_FILE = join25(OJ_AGENT_DIR, "mcp-consent.json");
22414
- COPILOT_MCP_CONFIG = join25(homedir14(), ".copilot", "mcp-config.json");
22415
- COPILOT_PLUGINS_DIR = join25(homedir14(), ".copilot", "installed-plugins", "copilot-plugins");
22416
- COPILOT_OAUTH_DIR = join25(homedir14(), ".copilot", "mcp-oauth-config");
22417
- AGENCY_CONFIG = join25(homedir14(), ".agency", "agency.toml");
22418
- AGENCY_EXE = join25(process.env.APPDATA || join25(homedir14(), "AppData", "Roaming"), "agency", "CurrentVersion", "agency.exe");
23373
+ OJ_AGENT_DIR = join26(homedir15(), ".openjaw-agent");
23374
+ OJ_MCP_CONFIG = join26(OJ_AGENT_DIR, "mcp.json");
23375
+ OJ_CONSENT_FILE = join26(OJ_AGENT_DIR, "mcp-consent.json");
23376
+ COPILOT_MCP_CONFIG = join26(homedir15(), ".copilot", "mcp-config.json");
23377
+ COPILOT_PLUGINS_DIR = join26(homedir15(), ".copilot", "installed-plugins", "copilot-plugins");
23378
+ COPILOT_OAUTH_DIR = join26(homedir15(), ".copilot", "mcp-oauth-config");
23379
+ AGENCY_CONFIG = join26(homedir15(), ".agency", "agency.toml");
23380
+ AGENCY_EXE = join26(process.env.APPDATA || join26(homedir15(), "AppData", "Roaming"), "agency", "CurrentVersion", "agency.exe");
22419
23381
  SELF_PATTERNS = ["openjaw-mcp/dist/index.js", "openjaw-mcp\\dist\\index.js", "openjaw-agent"];
22420
23382
  __name(findCopilotOAuthToken, "findCopilotOAuthToken");
22421
23383
  __name(refreshCopilotToken, "refreshCopilotToken");
@@ -22615,11 +23577,11 @@ var init_mcp_client = __esm({
22615
23577
  * Add a new MCP server at runtime. Saves to ~/.openjaw-agent/mcp.json and connects.
22616
23578
  */
22617
23579
  async addServer(name, def) {
22618
- if (!existsSync17(OJ_AGENT_DIR)) mkdirSync11(OJ_AGENT_DIR, { recursive: true });
23580
+ if (!existsSync19(OJ_AGENT_DIR)) mkdirSync11(OJ_AGENT_DIR, { recursive: true });
22619
23581
  let config = {};
22620
- if (existsSync17(OJ_MCP_CONFIG)) {
23582
+ if (existsSync19(OJ_MCP_CONFIG)) {
22621
23583
  try {
22622
- const raw = JSON.parse(readFileSync17(OJ_MCP_CONFIG, "utf-8"));
23584
+ const raw = JSON.parse(readFileSync18(OJ_MCP_CONFIG, "utf-8"));
22623
23585
  config = raw.mcpServers || {};
22624
23586
  } catch {
22625
23587
  }
@@ -22727,6 +23689,7 @@ var init_mcp_client = __esm({
22727
23689
  const headers = {};
22728
23690
  if (def.env?.AUTHORIZATION) headers["Authorization"] = def.env.AUTHORIZATION;
22729
23691
  if (def.env?.AUTH_TOKEN) headers["Authorization"] = `Bearer ${def.env.AUTH_TOKEN}`;
23692
+ if (def.headers) Object.assign(headers, def.headers);
22730
23693
  transport = new SSEClientTransport(new URL(def.url), {
22731
23694
  requestInit: Object.keys(headers).length > 0 ? { headers } : void 0
22732
23695
  });
@@ -22747,10 +23710,10 @@ var init_mcp_client = __esm({
22747
23710
  let tokenExpiry = 0;
22748
23711
  if (copilotTokenFile) {
22749
23712
  try {
22750
- const cached7 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
22751
- cachedToken = cached7.accessToken;
22752
- cachedRefreshToken = cached7.refreshToken;
22753
- tokenExpiry = cached7.expiresAt || 0;
23713
+ const cached8 = JSON.parse(readFileSync18(copilotTokenFile, "utf-8"));
23714
+ cachedToken = cached8.accessToken;
23715
+ cachedRefreshToken = cached8.refreshToken;
23716
+ tokenExpiry = cached8.expiresAt || 0;
22754
23717
  } catch {
22755
23718
  }
22756
23719
  }
@@ -22790,11 +23753,15 @@ var init_mcp_client = __esm({
22790
23753
  return globalThis.fetch(url, { ...init, headers });
22791
23754
  }, "customFetch");
22792
23755
  }
23756
+ const requestHeaders = {};
23757
+ if (staticAuth) requestHeaders["Authorization"] = staticAuth;
23758
+ if (def.headers) Object.assign(requestHeaders, def.headers);
22793
23759
  const transportOpts = {};
22794
23760
  if (customFetch) {
22795
23761
  transportOpts.fetch = customFetch;
22796
- } else if (staticAuth) {
22797
- transportOpts.requestInit = { headers: { Authorization: staticAuth } };
23762
+ }
23763
+ if (Object.keys(requestHeaders).length > 0) {
23764
+ transportOpts.requestInit = { headers: requestHeaders };
22798
23765
  }
22799
23766
  transport = new StreamableHTTPClientTransport(new URL(def.url), transportOpts);
22800
23767
  } else {
@@ -22855,6 +23822,7 @@ var init_mcp_client = __esm({
22855
23822
  __name(loadConsent, "loadConsent");
22856
23823
  __name(saveConsent, "saveConsent");
22857
23824
  __name(promptConsent, "promptConsent");
23825
+ __name(restoreStdinAfterReadlinePrompt, "restoreStdinAfterReadlinePrompt");
22858
23826
  __name(stripAnsi, "stripAnsi");
22859
23827
  }
22860
23828
  });
@@ -23185,13 +24153,13 @@ __export(scheduler_exports, {
23185
24153
  stopAllTasks: () => stopAllTasks,
23186
24154
  stopTask: () => stopTask
23187
24155
  });
23188
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync18, mkdirSync as mkdirSync12 } from "node:fs";
23189
- import { join as join26 } from "node:path";
23190
- import { homedir as homedir15 } from "node:os";
24156
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync12, existsSync as existsSync20, mkdirSync as mkdirSync12 } from "node:fs";
24157
+ import { join as join27 } from "node:path";
24158
+ import { homedir as homedir16 } from "node:os";
23191
24159
  function getJobsPath() {
23192
- const dir2 = join26(homedir15(), ".openjaw-agent");
23193
- if (!existsSync18(dir2)) mkdirSync12(dir2, { recursive: true });
23194
- return join26(dir2, "cron-jobs.json");
24160
+ const dir2 = join27(homedir16(), ".openjaw-agent");
24161
+ if (!existsSync20(dir2)) mkdirSync12(dir2, { recursive: true });
24162
+ return join27(dir2, "cron-jobs.json");
23195
24163
  }
23196
24164
  function parseInterval(input) {
23197
24165
  const match = input.match(/^(\d+)\s*(s|sec|seconds?|m|min|minutes?|h|hr|hours?|d|day|days?)$/i);
@@ -23328,9 +24296,9 @@ function saveJobs() {
23328
24296
  }
23329
24297
  function loadJobs() {
23330
24298
  const path3 = getJobsPath();
23331
- if (!existsSync18(path3)) return [];
24299
+ if (!existsSync20(path3)) return [];
23332
24300
  try {
23333
- const raw = readFileSync18(path3, "utf8");
24301
+ const raw = readFileSync19(path3, "utf8");
23334
24302
  return JSON.parse(raw);
23335
24303
  } catch {
23336
24304
  return [];
@@ -23562,10 +24530,10 @@ __export(repl_exports, {
23562
24530
  });
23563
24531
  import * as readline2 from "readline";
23564
24532
  import { readFile as readFile4 } from "node:fs/promises";
23565
- import { existsSync as existsSync19 } from "node:fs";
23566
- import { join as join27, dirname as dirname6 } from "node:path";
23567
- import { homedir as homedir16 } from "node:os";
23568
- import { fileURLToPath as fileURLToPath3 } from "node:url";
24533
+ import { existsSync as existsSync21 } from "node:fs";
24534
+ import { join as join28, dirname as dirname8 } from "node:path";
24535
+ import { homedir as homedir17 } from "node:os";
24536
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
23569
24537
  async function selectPrompt(message, choices, defaultIdx) {
23570
24538
  const { default: select } = await import("@inquirer/select");
23571
24539
  return select({ message, choices, default: defaultIdx });
@@ -23581,8 +24549,8 @@ async function loadPrompts(promptsDir) {
23581
24549
  const files = ["IDENTITY.md", "REASONING.md", "COMPUTER_USE.md", "SAFETY.md", "USER.md"];
23582
24550
  const parts = [];
23583
24551
  for (const file2 of files) {
23584
- const path3 = join27(promptsDir, file2);
23585
- if (existsSync19(path3)) {
24552
+ const path3 = join28(promptsDir, file2);
24553
+ if (existsSync21(path3)) {
23586
24554
  const content = await readFile4(path3, "utf8");
23587
24555
  parts.push(content);
23588
24556
  }
@@ -23598,11 +24566,11 @@ async function loadPrompts(promptsDir) {
23598
24566
  }
23599
24567
  async function loadMemorySummary() {
23600
24568
  const memoryPaths = [
23601
- join27(homedir16(), ".openjaw", "MEMORY.md"),
23602
- join27(homedir16(), ".openjaw", "memory", "MEMORY.md")
24569
+ join28(homedir17(), ".openjaw", "MEMORY.md"),
24570
+ join28(homedir17(), ".openjaw", "memory", "MEMORY.md")
23603
24571
  ];
23604
24572
  for (const memoryPath of memoryPaths) {
23605
- if (existsSync19(memoryPath)) {
24573
+ if (existsSync21(memoryPath)) {
23606
24574
  const content = await readFile4(memoryPath, "utf8");
23607
24575
  return content.slice(0, 2e3);
23608
24576
  }
@@ -23610,8 +24578,8 @@ async function loadMemorySummary() {
23610
24578
  return "";
23611
24579
  }
23612
24580
  async function startREPL(resumeSessionId) {
23613
- const promptsDir = join27(__dirname2, "..", "prompts");
23614
- const packagePromptsDir2 = existsSync19(promptsDir) ? promptsDir : join27(homedir16(), ".openjaw-agent", "prompts");
24581
+ const promptsDir = join28(__dirname2, "..", "prompts");
24582
+ const packagePromptsDir2 = existsSync21(promptsDir) ? promptsDir : join28(homedir17(), ".openjaw-agent", "prompts");
23615
24583
  await ensureDirectories();
23616
24584
  const config = await loadConfig();
23617
24585
  const systemPrompt = await loadPrompts(packagePromptsDir2);
@@ -24175,8 +25143,8 @@ var init_repl = __esm({
24175
25143
  init_connect();
24176
25144
  init_scheduler();
24177
25145
  __name(selectPrompt, "selectPrompt");
24178
- __filename2 = fileURLToPath3(import.meta.url);
24179
- __dirname2 = dirname6(__filename2);
25146
+ __filename2 = fileURLToPath5(import.meta.url);
25147
+ __dirname2 = dirname8(__filename2);
24180
25148
  C = {
24181
25149
  reset: "\x1B[0m",
24182
25150
  dim: "\x1B[2m",
@@ -24201,9 +25169,9 @@ var init_repl = __esm({
24201
25169
  });
24202
25170
 
24203
25171
  // src/voice/tts.ts
24204
- import { execSync as execSync6, spawn as spawn2 } from "node:child_process";
24205
- import { existsSync as existsSync20, unlinkSync as unlinkSync4, mkdirSync as mkdirSync14 } from "node:fs";
24206
- import { join as join28 } from "node:path";
25172
+ import { execSync as execSync6, spawn as spawn4 } from "node:child_process";
25173
+ import { existsSync as existsSync22, unlinkSync as unlinkSync5, mkdirSync as mkdirSync14 } from "node:fs";
25174
+ import { join as join29 } from "node:path";
24207
25175
  import { tmpdir as tmpdir5 } from "node:os";
24208
25176
  import { randomUUID as randomUUID5 } from "node:crypto";
24209
25177
  function hasEdgeTts() {
@@ -24257,8 +25225,8 @@ async function listVoices() {
24257
25225
  }
24258
25226
  }
24259
25227
  async function speakEdgeTts(text, voice, options) {
24260
- if (!existsSync20(TTS_TEMP_DIR)) mkdirSync14(TTS_TEMP_DIR, { recursive: true });
24261
- const audioFile = join28(TTS_TEMP_DIR, `tts-${randomUUID5().slice(0, 8)}.mp3`);
25228
+ if (!existsSync22(TTS_TEMP_DIR)) mkdirSync14(TTS_TEMP_DIR, { recursive: true });
25229
+ const audioFile = join29(TTS_TEMP_DIR, `tts-${randomUUID5().slice(0, 8)}.mp3`);
24262
25230
  let playerProc = null;
24263
25231
  let stopped = false;
24264
25232
  try {
@@ -24270,9 +25238,9 @@ async function speakEdgeTts(text, voice, options) {
24270
25238
  timeout: 15e3,
24271
25239
  stdio: ["pipe", "pipe", "pipe"]
24272
25240
  });
24273
- if (stopped || !existsSync20(audioFile)) return { stop: /* @__PURE__ */ __name(() => {
25241
+ if (stopped || !existsSync22(audioFile)) return { stop: /* @__PURE__ */ __name(() => {
24274
25242
  }, "stop") };
24275
- playerProc = spawn2("powershell.exe", [
25243
+ playerProc = spawn4("powershell.exe", [
24276
25244
  "-NoProfile",
24277
25245
  "-Command",
24278
25246
  `Add-Type -AssemblyName PresentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]"${audioFile}"); $p.Play(); Start-Sleep -Milliseconds 500; while($p.Position -lt $p.NaturalDuration.TimeSpan -and $p.HasAudio){ Start-Sleep -Milliseconds 200 }; $p.Close()`
@@ -24282,14 +25250,14 @@ async function speakEdgeTts(text, voice, options) {
24282
25250
  });
24283
25251
  playerProc.on("exit", () => {
24284
25252
  try {
24285
- if (existsSync20(audioFile)) unlinkSync4(audioFile);
25253
+ if (existsSync22(audioFile)) unlinkSync5(audioFile);
24286
25254
  } catch {
24287
25255
  }
24288
25256
  playerProc = null;
24289
25257
  });
24290
25258
  } catch {
24291
25259
  try {
24292
- if (existsSync20(audioFile)) unlinkSync4(audioFile);
25260
+ if (existsSync22(audioFile)) unlinkSync5(audioFile);
24293
25261
  } catch {
24294
25262
  }
24295
25263
  }
@@ -24301,7 +25269,7 @@ async function speakEdgeTts(text, voice, options) {
24301
25269
  playerProc = null;
24302
25270
  }
24303
25271
  try {
24304
- if (existsSync20(audioFile)) unlinkSync4(audioFile);
25272
+ if (existsSync22(audioFile)) unlinkSync5(audioFile);
24305
25273
  } catch {
24306
25274
  }
24307
25275
  }, "stop")
@@ -24318,7 +25286,7 @@ $synth.Speak('${safeText}')
24318
25286
  $synth.Dispose()
24319
25287
  `;
24320
25288
  const encoded = Buffer.from(script, "utf16le").toString("base64");
24321
- const proc = spawn2("powershell.exe", ["-NoProfile", "-EncodedCommand", encoded], {
25289
+ const proc = spawn4("powershell.exe", ["-NoProfile", "-EncodedCommand", encoded], {
24322
25290
  stdio: ["pipe", "pipe", "pipe"],
24323
25291
  windowsHide: true
24324
25292
  });
@@ -24340,7 +25308,7 @@ var init_tts = __esm({
24340
25308
  "use strict";
24341
25309
  DEFAULT_VOICE_EN = "en-US-AriaNeural";
24342
25310
  DEFAULT_VOICE_ZH = "zh-CN-XiaoxiaoNeural";
24343
- TTS_TEMP_DIR = join28(tmpdir5(), "oj-tts");
25311
+ TTS_TEMP_DIR = join29(tmpdir5(), "oj-tts");
24344
25312
  edgeTtsAvailable = null;
24345
25313
  __name(hasEdgeTts, "hasEdgeTts");
24346
25314
  __name(ensureEdgeTts, "ensureEdgeTts");
@@ -24354,13 +25322,13 @@ var init_tts = __esm({
24354
25322
  });
24355
25323
 
24356
25324
  // src/voice/stt.ts
24357
- import { spawn as spawn3, execSync as execSync7 } from "node:child_process";
24358
- import { join as join29 } from "node:path";
25325
+ import { spawn as spawn5, execSync as execSync7 } from "node:child_process";
25326
+ import { join as join30 } from "node:path";
24359
25327
  import { tmpdir as tmpdir6 } from "node:os";
24360
- import { existsSync as existsSync21, unlinkSync as unlinkSync5 } from "node:fs";
25328
+ import { existsSync as existsSync23, unlinkSync as unlinkSync6 } from "node:fs";
24361
25329
  import { randomUUID as randomUUID6 } from "node:crypto";
24362
25330
  async function listenOnce(timeoutSeconds = 10, language = "en-US") {
24363
- const resultFile = join29(tmpdir6(), `oj-stt-${randomUUID6().slice(0, 8)}.txt`);
25331
+ const resultFile = join30(tmpdir6(), `oj-stt-${randomUUID6().slice(0, 8)}.txt`);
24364
25332
  const script = `
24365
25333
  $ProgressPreference = 'SilentlyContinue'
24366
25334
  $ErrorActionPreference = 'Stop'
@@ -24395,8 +25363,8 @@ try {
24395
25363
  }
24396
25364
  `;
24397
25365
  const encoded = Buffer.from(script, "utf16le").toString("base64");
24398
- return new Promise((resolve6) => {
24399
- const proc = spawn3("powershell.exe", ["-NoProfile", "-STA", "-EncodedCommand", encoded], {
25366
+ return new Promise((resolve7) => {
25367
+ const proc = spawn5("powershell.exe", ["-NoProfile", "-STA", "-EncodedCommand", encoded], {
24400
25368
  stdio: ["pipe", "pipe", "pipe"],
24401
25369
  windowsHide: true
24402
25370
  });
@@ -24410,25 +25378,25 @@ try {
24410
25378
  });
24411
25379
  const timer = setTimeout(() => {
24412
25380
  if (!proc.killed) proc.kill();
24413
- resolve6(null);
25381
+ resolve7(null);
24414
25382
  }, (timeoutSeconds + 5) * 1e3);
24415
25383
  proc.on("exit", () => {
24416
25384
  clearTimeout(timer);
24417
25385
  const output = stdout.replace(/#< CLIXML[\s\S]*/m, "").trim();
24418
25386
  if (output === "NO_SPEECH" || output.startsWith("ERROR") || !output) {
24419
- resolve6(null);
25387
+ resolve7(null);
24420
25388
  return;
24421
25389
  }
24422
25390
  const parts = output.split("|");
24423
25391
  const text = parts[0]?.trim();
24424
25392
  const confidence = parseFloat(parts[1] || "0");
24425
25393
  if (text) {
24426
- resolve6({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
25394
+ resolve7({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
24427
25395
  } else {
24428
- resolve6(null);
25396
+ resolve7(null);
24429
25397
  }
24430
25398
  try {
24431
- if (existsSync21(resultFile)) unlinkSync5(resultFile);
25399
+ if (existsSync23(resultFile)) unlinkSync6(resultFile);
24432
25400
  } catch {
24433
25401
  }
24434
25402
  });
@@ -24662,9 +25630,9 @@ __export(clipboard_image_exports, {
24662
25630
  readClipboardImage: () => readClipboardImage
24663
25631
  });
24664
25632
  import { execSync as execSync8 } from "node:child_process";
24665
- import { join as join30 } from "node:path";
25633
+ import { join as join31 } from "node:path";
24666
25634
  import { tmpdir as tmpdir7 } from "node:os";
24667
- import { readFileSync as readFileSync20, unlinkSync as unlinkSync6, existsSync as existsSync22 } from "node:fs";
25635
+ import { readFileSync as readFileSync21, unlinkSync as unlinkSync7, existsSync as existsSync24 } from "node:fs";
24668
25636
  import { randomUUID as randomUUID7 } from "node:crypto";
24669
25637
  function encodePS(script) {
24670
25638
  return Buffer.from(script, "utf16le").toString("base64");
@@ -24689,7 +25657,7 @@ Add-Type -AssemblyName System.Windows.Forms
24689
25657
  }
24690
25658
  function readClipboardImage() {
24691
25659
  try {
24692
- const tempPath = join30(tmpdir7(), `oj-clip-${randomUUID7().slice(0, 8)}.png`);
25660
+ const tempPath = join31(tmpdir7(), `oj-clip-${randomUUID7().slice(0, 8)}.png`);
24693
25661
  const script = `
24694
25662
  $ProgressPreference = 'SilentlyContinue'
24695
25663
  $ErrorActionPreference = 'Stop'
@@ -24706,16 +25674,16 @@ $img.Dispose()
24706
25674
  { encoding: "utf-8", timeout: 8e3, windowsHide: true, stdio: ["pipe", "pipe", "pipe"] }
24707
25675
  ).trim();
24708
25676
  const clean = output.replace(/#< CLIXML[\s\S]*/m, "").trim();
24709
- if (clean === "NO_IMAGE" || !existsSync22(tempPath)) {
25677
+ if (clean === "NO_IMAGE" || !existsSync24(tempPath)) {
24710
25678
  return null;
24711
25679
  }
24712
25680
  const match = clean.match(/(\d+)x(\d+)/);
24713
25681
  const width = match ? parseInt(match[1], 10) : 0;
24714
25682
  const height = match ? parseInt(match[2], 10) : 0;
24715
- const buffer = readFileSync20(tempPath);
25683
+ const buffer = readFileSync21(tempPath);
24716
25684
  const base64 = buffer.toString("base64");
24717
25685
  try {
24718
- unlinkSync6(tempPath);
25686
+ unlinkSync7(tempPath);
24719
25687
  } catch {
24720
25688
  }
24721
25689
  return { base64, mimeType: "image/png", width, height };
@@ -25541,9 +26509,9 @@ __export(pet_exports, {
25541
26509
  getRandomReaction: () => getRandomReaction,
25542
26510
  renamePet: () => renamePet
25543
26511
  });
25544
- import { existsSync as existsSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync14, mkdirSync as mkdirSync15 } from "node:fs";
25545
- import { join as join31 } from "node:path";
25546
- import { homedir as homedir17 } from "node:os";
26512
+ import { existsSync as existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync14, mkdirSync as mkdirSync15 } from "node:fs";
26513
+ import { join as join32 } from "node:path";
26514
+ import { homedir as homedir18 } from "node:os";
25547
26515
  function hashCode(str) {
25548
26516
  let hash3 = 0;
25549
26517
  for (let i = 0; i < str.length; i++) {
@@ -25561,20 +26529,20 @@ function seededRandom(seed) {
25561
26529
  };
25562
26530
  }
25563
26531
  function getPetPath() {
25564
- return join31(homedir17(), ".openjaw-agent", "pet.json");
26532
+ return join32(homedir18(), ".openjaw-agent", "pet.json");
25565
26533
  }
25566
26534
  function loadStoredPet() {
25567
26535
  const path3 = getPetPath();
25568
- if (!existsSync23(path3)) return null;
26536
+ if (!existsSync25(path3)) return null;
25569
26537
  try {
25570
- return JSON.parse(readFileSync21(path3, "utf-8"));
26538
+ return JSON.parse(readFileSync22(path3, "utf-8"));
25571
26539
  } catch {
25572
26540
  return null;
25573
26541
  }
25574
26542
  }
25575
26543
  function saveStoredPet(data) {
25576
- const dir2 = join31(homedir17(), ".openjaw-agent");
25577
- if (!existsSync23(dir2)) mkdirSync15(dir2, { recursive: true });
26544
+ const dir2 = join32(homedir18(), ".openjaw-agent");
26545
+ if (!existsSync25(dir2)) mkdirSync15(dir2, { recursive: true });
25578
26546
  writeFileSync14(getPetPath(), JSON.stringify(data, null, 2), "utf-8");
25579
26547
  }
25580
26548
  function generatePet(userId) {
@@ -26123,34 +27091,34 @@ ${output}
26123
27091
  }
26124
27092
  }
26125
27093
  function expandUrl(ref, warnings) {
26126
- return new Promise((resolve6) => {
27094
+ return new Promise((resolve7) => {
26127
27095
  const url = ref.target;
26128
27096
  const mod = url.startsWith("https") ? https : http;
26129
27097
  const req = mod.get(url, { timeout: 15e3 }, (res) => {
26130
27098
  if (res.statusCode && (res.statusCode >= 300 && res.statusCode < 400) && res.headers.location) {
26131
27099
  const redirectMod = res.headers.location.startsWith("https") ? https : http;
26132
27100
  const req2 = redirectMod.get(res.headers.location, { timeout: 15e3 }, (res2) => {
26133
- collectResponse(res2, ref, warnings, resolve6);
27101
+ collectResponse(res2, ref, warnings, resolve7);
26134
27102
  });
26135
27103
  req2.on("error", (e) => {
26136
27104
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26137
- resolve6(null);
27105
+ resolve7(null);
26138
27106
  });
26139
27107
  return;
26140
27108
  }
26141
- collectResponse(res, ref, warnings, resolve6);
27109
+ collectResponse(res, ref, warnings, resolve7);
26142
27110
  });
26143
27111
  req.on("error", (e) => {
26144
27112
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26145
- resolve6(null);
27113
+ resolve7(null);
26146
27114
  });
26147
27115
  });
26148
27116
  }
26149
- function collectResponse(res, ref, warnings, resolve6) {
27117
+ function collectResponse(res, ref, warnings, resolve7) {
26150
27118
  if (res.statusCode && res.statusCode >= 400) {
26151
27119
  warnings.push(`\u26A0\uFE0F @url returned HTTP ${res.statusCode}: ${ref.target}`);
26152
27120
  res.resume();
26153
- resolve6(null);
27121
+ resolve7(null);
26154
27122
  return;
26155
27123
  }
26156
27124
  const chunks = [];
@@ -26162,14 +27130,14 @@ function collectResponse(res, ref, warnings, resolve6) {
26162
27130
  text = text.slice(0, 5e4) + "\n\u2026 (truncated)";
26163
27131
  }
26164
27132
  const tokens = estimateTokens2(text);
26165
- resolve6(`\u{1F310} @url:${ref.target} (${tokens} tokens)
27133
+ resolve7(`\u{1F310} @url:${ref.target} (${tokens} tokens)
26166
27134
  \`\`\`
26167
27135
  ${text}
26168
27136
  \`\`\``);
26169
27137
  });
26170
27138
  res.on("error", (e) => {
26171
27139
  warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
26172
- resolve6(null);
27140
+ resolve7(null);
26173
27141
  });
26174
27142
  }
26175
27143
  function stripHtml(html) {
@@ -26670,13 +27638,13 @@ Type /resume <id> to resume.` });
26670
27638
  if (input === "/export") {
26671
27639
  try {
26672
27640
  const { writeFile: writeFile5, mkdir: mkdir5 } = await import("node:fs/promises");
26673
- const { join: join48 } = await import("node:path");
26674
- const { homedir: homedir32 } = await import("node:os");
26675
- const { existsSync: existsSync34 } = await import("node:fs");
26676
- const exportDir = join48(homedir32(), ".openjaw-agent", "exports");
26677
- if (!existsSync34(exportDir)) await mkdir5(exportDir, { recursive: true });
27641
+ const { join: join49 } = await import("node:path");
27642
+ const { homedir: homedir33 } = await import("node:os");
27643
+ const { existsSync: existsSync36 } = await import("node:fs");
27644
+ const exportDir = join49(homedir33(), ".openjaw-agent", "exports");
27645
+ if (!existsSync36(exportDir)) await mkdir5(exportDir, { recursive: true });
26678
27646
  const filename = `session-${agentLoop.sessionId}.md`;
26679
- const filepath = join48(exportDir, filename);
27647
+ const filepath = join49(exportDir, filename);
26680
27648
  const lines = [
26681
27649
  `# OpenJaw Agent Session ${agentLoop.sessionId}`,
26682
27650
  `Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -26895,9 +27863,9 @@ ${list}` });
26895
27863
  const cmd = lang === "python" ? "python" : lang === "node" ? "node" : "pwsh";
26896
27864
  const args = lang === "python" ? ["-i", "-u"] : lang === "node" ? ["-i"] : ["-NoProfile", "-NoLogo"];
26897
27865
  try {
26898
- const { spawn: spawn8 } = await import("node:child_process");
27866
+ const { spawn: spawn10 } = await import("node:child_process");
26899
27867
  if (replRef.current) replRef.current.kill();
26900
- const proc = spawn8(cmd, args, {
27868
+ const proc = spawn10(cmd, args, {
26901
27869
  stdio: ["pipe", "pipe", "pipe"],
26902
27870
  windowsHide: true
26903
27871
  });
@@ -26937,7 +27905,7 @@ ${list}` });
26937
27905
  if (replRef.current && replLangRef.current && !input.startsWith("/")) {
26938
27906
  const proc = replRef.current;
26939
27907
  proc.stdin?.write(input + "\n");
26940
- await new Promise((resolve6) => setTimeout(resolve6, 800));
27908
+ await new Promise((resolve7) => setTimeout(resolve7, 800));
26941
27909
  const flush = proc._ojFlush;
26942
27910
  const output = flush ? flush() : "";
26943
27911
  if (output.trim()) {
@@ -27251,14 +28219,14 @@ Options: ${chunk.choices.join(" | ")}` : chunk.content;
27251
28219
  waitingForAskUserRef.current = true;
27252
28220
  setIsRunning(false);
27253
28221
  setWaitingForAskUser(true);
27254
- await new Promise((resolve6) => {
28222
+ await new Promise((resolve7) => {
27255
28223
  const checkInterval = setInterval(() => {
27256
28224
  if (!waitingForAskUserRef.current || !agentLoop.isWaitingForAskUser) {
27257
28225
  clearInterval(checkInterval);
27258
28226
  waitingForAskUserRef.current = false;
27259
28227
  setWaitingForAskUser(false);
27260
28228
  setIsRunning(true);
27261
- resolve6();
28229
+ resolve7();
27262
28230
  }
27263
28231
  }, 200);
27264
28232
  });
@@ -27533,7 +28501,7 @@ var skill_tool_exports = {};
27533
28501
  __export(skill_tool_exports, {
27534
28502
  createSkillTool: () => createSkillTool
27535
28503
  });
27536
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
28504
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "node:fs";
27537
28505
  function resolveSkillTimeoutMs() {
27538
28506
  const raw = process.env["OPENJAW_SKILL_TIMEOUT_MS"];
27539
28507
  const parsed = raw ? Number(raw) : NaN;
@@ -27554,8 +28522,8 @@ async function withTimeout(promise, ms) {
27554
28522
  try {
27555
28523
  return await Promise.race([
27556
28524
  promise,
27557
- new Promise((resolve6) => {
27558
- timeoutId = setTimeout(() => resolve6(SKILL_TIMEOUT), ms);
28525
+ new Promise((resolve7) => {
28526
+ timeoutId = setTimeout(() => resolve7(SKILL_TIMEOUT), ms);
27559
28527
  })
27560
28528
  ]);
27561
28529
  } finally {
@@ -27701,7 +28669,7 @@ function extractLessons(chunks) {
27701
28669
  }
27702
28670
  function appendLessonsLearned(skillPath, lessons) {
27703
28671
  try {
27704
- const content = readFileSync23(skillPath, "utf8");
28672
+ const content = readFileSync24(skillPath, "utf8");
27705
28673
  const datestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
27706
28674
  if (content.includes("## Lessons Learned")) {
27707
28675
  const updated = content.replace(
@@ -27746,9 +28714,9 @@ var teams_exports = {};
27746
28714
  __export(teams_exports, {
27747
28715
  TeamsBridge: () => TeamsBridge
27748
28716
  });
27749
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync16, existsSync as existsSync25 } from "node:fs";
27750
- import { join as join33 } from "node:path";
27751
- import { homedir as homedir19, tmpdir as tmpdir8 } from "node:os";
28717
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync16, existsSync as existsSync27 } from "node:fs";
28718
+ import { join as join34 } from "node:path";
28719
+ import { homedir as homedir20, tmpdir as tmpdir8 } from "node:os";
27752
28720
  import { randomUUID as randomUUID8 } from "node:crypto";
27753
28721
  var AGENT_PREFIX, POLL_INTERVAL, TeamsBridge;
27754
28722
  var init_teams = __esm({
@@ -27836,7 +28804,7 @@ var init_teams = __esm({
27836
28804
  const { resizeImageFileForAgent: resizeImageFileForAgent2 } = await Promise.resolve().then(() => (init_image_resize(), image_resize_exports));
27837
28805
  imageData = await resizeImageFileForAgent2(imageFile);
27838
28806
  } catch {
27839
- const imgBuffer = readFileSync24(imageFile);
28807
+ const imgBuffer = readFileSync25(imageFile);
27840
28808
  const ext = imageFile.split(".").pop()?.toLowerCase() || "png";
27841
28809
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/png";
27842
28810
  imageData = { base64: imgBuffer.toString("base64"), mimeType };
@@ -27912,7 +28880,7 @@ var init_teams = __esm({
27912
28880
  const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
27913
28881
  if (!resp.ok) return null;
27914
28882
  const buffer = Buffer.from(await resp.arrayBuffer());
27915
- const tempPath = join33(tmpdir8(), `oj-teams-${randomUUID8().slice(0, 6)}-${filename}`);
28883
+ const tempPath = join34(tmpdir8(), `oj-teams-${randomUUID8().slice(0, 6)}-${filename}`);
27916
28884
  writeFileSync16(tempPath, buffer);
27917
28885
  return tempPath;
27918
28886
  } catch {
@@ -27927,7 +28895,7 @@ var init_teams = __esm({
27927
28895
  const contentType = resp.headers.get("content-type") ?? "image/png";
27928
28896
  const ext = contentType.includes("jpeg") ? ".jpg" : contentType.includes("gif") ? ".gif" : ".png";
27929
28897
  const buffer = Buffer.from(await resp.arrayBuffer());
27930
- const tempPath = join33(tmpdir8(), `oj-teams-img-${randomUUID8().slice(0, 6)}${ext}`);
28898
+ const tempPath = join34(tmpdir8(), `oj-teams-img-${randomUUID8().slice(0, 6)}${ext}`);
27931
28899
  writeFileSync16(tempPath, buffer);
27932
28900
  return tempPath;
27933
28901
  } catch {
@@ -27968,7 +28936,7 @@ var init_teams = __esm({
27968
28936
  async sendFileToSelfChat(filePath) {
27969
28937
  const token = this.getGraphToken();
27970
28938
  const fileName = filePath.split(/[\\/]/).pop() || "file";
27971
- const fileBuffer = readFileSync24(filePath);
28939
+ const fileBuffer = readFileSync25(filePath);
27972
28940
  const uploadResp = await fetch(
27973
28941
  `https://graph.microsoft.com/v1.0/me/drive/root:/OpenJaw-Shared/${fileName}:/content`,
27974
28942
  {
@@ -28021,7 +28989,7 @@ var init_teams = __esm({
28021
28989
  if (filePath.includes("http") || filePath.includes("node_modules")) continue;
28022
28990
  const fileName = filePath.split(/[\\/]/).pop() || "";
28023
28991
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
28024
- if (existsSync25(filePath)) {
28992
+ if (existsSync27(filePath)) {
28025
28993
  try {
28026
28994
  const { statSync: statSync5 } = await import("node:fs");
28027
28995
  const stat2 = statSync5(filePath);
@@ -28039,9 +29007,9 @@ var init_teams = __esm({
28039
29007
  }
28040
29008
  /** Get Graph access token from disk. */
28041
29009
  getGraphToken() {
28042
- const tokenPath = join33(homedir19(), ".graph-token", "tokens", "graph-chat.json");
28043
- if (!existsSync25(tokenPath)) throw new Error("Graph token not found");
28044
- const data = JSON.parse(readFileSync24(tokenPath, "utf-8"));
29010
+ const tokenPath = join34(homedir20(), ".graph-token", "tokens", "graph-chat.json");
29011
+ if (!existsSync27(tokenPath)) throw new Error("Graph token not found");
29012
+ const data = JSON.parse(readFileSync25(tokenPath, "utf-8"));
28045
29013
  if (!data.access_token) throw new Error("No access_token");
28046
29014
  return data.access_token;
28047
29015
  }
@@ -28165,12 +29133,12 @@ var init_teams = __esm({
28165
29133
  Options: ${chunk.choices.join(" | ")}` : "";
28166
29134
  await this.sendToSelfChat(`\u2753 ${question}${choicesText}`);
28167
29135
  if (!answerSent) await updateStatus("\u{1F916} OpenJaw Agent \u2014 \u2753 Waiting for your response...");
28168
- const userReply = await new Promise((resolve6) => {
28169
- this._pendingReplyResolver = resolve6;
29136
+ const userReply = await new Promise((resolve7) => {
29137
+ this._pendingReplyResolver = resolve7;
28170
29138
  setTimeout(() => {
28171
- if (this._pendingReplyResolver === resolve6) {
29139
+ if (this._pendingReplyResolver === resolve7) {
28172
29140
  this._pendingReplyResolver = null;
28173
- resolve6("[No response \u2014 timed out]");
29141
+ resolve7("[No response \u2014 timed out]");
28174
29142
  }
28175
29143
  }, 5 * 60 * 1e3);
28176
29144
  });
@@ -28242,8 +29210,8 @@ __export(feishu_exports, {
28242
29210
  FeishuBridge: () => FeishuBridge
28243
29211
  });
28244
29212
  import * as lark from "@larksuiteoapi/node-sdk";
28245
- import { readFileSync as readFileSync25, existsSync as existsSync26 } from "node:fs";
28246
- import { join as join34 } from "node:path";
29213
+ import { readFileSync as readFileSync26, existsSync as existsSync28 } from "node:fs";
29214
+ import { join as join35 } from "node:path";
28247
29215
  import { tmpdir as tmpdir9 } from "node:os";
28248
29216
  import { randomUUID as randomUUID9 } from "node:crypto";
28249
29217
  var FeishuBridge;
@@ -28479,9 +29447,9 @@ var init_feishu = __esm({
28479
29447
  params: { type: "image" }
28480
29448
  });
28481
29449
  if (resp) {
28482
- const tempPath = join34(tmpdir9(), `oj-feishu-img-${randomUUID9().slice(0, 6)}.png`);
29450
+ const tempPath = join35(tmpdir9(), `oj-feishu-img-${randomUUID9().slice(0, 6)}.png`);
28483
29451
  await resp.writeFile(tempPath);
28484
- const buffer = readFileSync25(tempPath);
29452
+ const buffer = readFileSync26(tempPath);
28485
29453
  let imageData;
28486
29454
  try {
28487
29455
  const { resizeImageFileForAgent: resizeImageFileForAgent2 } = await Promise.resolve().then(() => (init_image_resize(), image_resize_exports));
@@ -28502,7 +29470,7 @@ var init_feishu = __esm({
28502
29470
  params: { type: "file" }
28503
29471
  });
28504
29472
  if (resp) {
28505
- const tempPath = join34(tmpdir9(), `oj-feishu-file-${randomUUID9().slice(0, 6)}-${fileName}`);
29473
+ const tempPath = join35(tmpdir9(), `oj-feishu-file-${randomUUID9().slice(0, 6)}-${fileName}`);
28506
29474
  await resp.writeFile(tempPath);
28507
29475
  await this.handleMessage(chatId, `I've sent you a file: ${fileName}. It's saved at ${tempPath}. Please analyze it.`);
28508
29476
  }
@@ -28579,7 +29547,7 @@ var init_feishu = __esm({
28579
29547
  const fileName = filePath.split(/[\\/]/).pop() || "";
28580
29548
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
28581
29549
  if (filePath.includes("http") || filePath.includes("node_modules")) continue;
28582
- const exists = existsSync26(filePath);
29550
+ const exists = existsSync28(filePath);
28583
29551
  this.emit({ type: "system", content: `\u{1F50D} Found path: ${filePath} (exists: ${exists})` });
28584
29552
  if (exists) {
28585
29553
  try {
@@ -28654,12 +29622,12 @@ ${err.stack?.split("\n").slice(0, 3).join("\n")}` : String(err);
28654
29622
  }
28655
29623
  /** Wait for the next user message (used by ask_user flow) */
28656
29624
  waitForUserReply(_chatId) {
28657
- return new Promise((resolve6) => {
28658
- this._pendingReplyResolver = resolve6;
29625
+ return new Promise((resolve7) => {
29626
+ this._pendingReplyResolver = resolve7;
28659
29627
  setTimeout(() => {
28660
- if (this._pendingReplyResolver === resolve6) {
29628
+ if (this._pendingReplyResolver === resolve7) {
28661
29629
  this._pendingReplyResolver = null;
28662
- resolve6("[No response \u2014 timed out]");
29630
+ resolve7("[No response \u2014 timed out]");
28663
29631
  }
28664
29632
  }, 5 * 60 * 1e3);
28665
29633
  });
@@ -28680,10 +29648,10 @@ __export(wechat_exports, {
28680
29648
  sniffMediaKind: () => sniffMediaKind,
28681
29649
  validateMediaForUpload: () => validateMediaForUpload
28682
29650
  });
28683
- import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync3 } from "node:fs";
29651
+ import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync18, unlinkSync as unlinkSync8, statSync as statSync3 } from "node:fs";
28684
29652
  import { mkdirSync as mkdirSync16 } from "node:fs";
28685
- import { extname as extname3, join as join35 } from "node:path";
28686
- import { homedir as homedir21 } from "node:os";
29653
+ import { extname as extname3, join as join36 } from "node:path";
29654
+ import { homedir as homedir22 } from "node:os";
28687
29655
  import { randomBytes, randomUUID as randomUUID10, createDecipheriv, createCipheriv, createHash as createHash3 } from "node:crypto";
28688
29656
  function mimeFromFilename(fileName) {
28689
29657
  return MIME_MAP[extname3(fileName).toLowerCase()] || "application/octet-stream";
@@ -28751,7 +29719,7 @@ function expandEnvVars(p) {
28751
29719
  return v !== void 0 ? v : m;
28752
29720
  });
28753
29721
  if (out.startsWith("~")) {
28754
- out = join35(homedir21(), out.slice(1).replace(/^[\\/]/, ""));
29722
+ out = join36(homedir22(), out.slice(1).replace(/^[\\/]/, ""));
28755
29723
  }
28756
29724
  return out;
28757
29725
  }
@@ -28901,15 +29869,15 @@ var init_wechat2 = __esm({
28901
29869
  }
28902
29870
  // ── Auth ──
28903
29871
  getAccountPath() {
28904
- const dir2 = join35(homedir21(), ".openjaw-agent");
28905
- if (!existsSync27(dir2)) mkdirSync16(dir2, { recursive: true });
28906
- return join35(dir2, "wechat-account.json");
29872
+ const dir2 = join36(homedir22(), ".openjaw-agent");
29873
+ if (!existsSync29(dir2)) mkdirSync16(dir2, { recursive: true });
29874
+ return join36(dir2, "wechat-account.json");
28907
29875
  }
28908
29876
  loadAccount() {
28909
29877
  const path3 = this.getAccountPath();
28910
- if (!existsSync27(path3)) return false;
29878
+ if (!existsSync29(path3)) return false;
28911
29879
  try {
28912
- this.account = JSON.parse(readFileSync26(path3, "utf-8"));
29880
+ this.account = JSON.parse(readFileSync27(path3, "utf-8"));
28913
29881
  return !!this.account?.token;
28914
29882
  } catch {
28915
29883
  return false;
@@ -28925,7 +29893,7 @@ var init_wechat2 = __esm({
28925
29893
  /** Delete saved session and clear current account (for /wechat logout) */
28926
29894
  logout() {
28927
29895
  const path3 = this.getAccountPath();
28928
- if (existsSync27(path3)) unlinkSync7(path3);
29896
+ if (existsSync29(path3)) unlinkSync8(path3);
28929
29897
  this.account = null;
28930
29898
  this.emit({ type: "system", content: "\u{1F7E2} WeChat: \u5DF2\u767B\u51FA\uFF0C\u4E0B\u6B21\u6D88\u606F\u5C06\u91CD\u65B0\u626B\u7801" });
28931
29899
  }
@@ -28950,9 +29918,9 @@ var init_wechat2 = __esm({
28950
29918
  try {
28951
29919
  const qrTerminal = await import("qrcode-terminal");
28952
29920
  const mod = qrTerminal.default || qrTerminal;
28953
- const qrAscii = await new Promise((resolve6, reject) => {
29921
+ const qrAscii = await new Promise((resolve7, reject) => {
28954
29922
  try {
28955
- mod.generate(qrUrl, { small: true }, (out) => resolve6(out));
29923
+ mod.generate(qrUrl, { small: true }, (out) => resolve7(out));
28956
29924
  } catch (e) {
28957
29925
  reject(e);
28958
29926
  }
@@ -29218,12 +30186,12 @@ Scan URL manually: ${qrUrl}` });
29218
30186
  const choicesText = chunk.choices?.length ? `
29219
30187
  \u9009\u9879: ${chunk.choices.join(" | ")}` : "";
29220
30188
  await this.sendText(userId, `\u2753 ${question}${choicesText}`, contextToken);
29221
- const userReply = await new Promise((resolve6) => {
29222
- this._pendingReplyResolver = resolve6;
30189
+ const userReply = await new Promise((resolve7) => {
30190
+ this._pendingReplyResolver = resolve7;
29223
30191
  setTimeout(() => {
29224
- if (this._pendingReplyResolver === resolve6) {
30192
+ if (this._pendingReplyResolver === resolve7) {
29225
30193
  this._pendingReplyResolver = null;
29226
- resolve6("[No response \u2014 timed out]");
30194
+ resolve7("[No response \u2014 timed out]");
29227
30195
  }
29228
30196
  }, 5 * 60 * 1e3);
29229
30197
  });
@@ -29387,7 +30355,7 @@ Scan URL manually: ${qrUrl}` });
29387
30355
  */
29388
30356
  async uploadFile(filePath, fileName, toUserId) {
29389
30357
  if (!this.account) return null;
29390
- const plaintext = readFileSync26(filePath);
30358
+ const plaintext = readFileSync27(filePath);
29391
30359
  const rawsize = plaintext.length;
29392
30360
  if (rawsize === 0) {
29393
30361
  this.emit({ type: "system", content: `\u26A0 WeChat upload skipped (file is empty): ${fileName}` });
@@ -29547,7 +30515,7 @@ Scan URL manually: ${qrUrl}` });
29547
30515
  if (!fileName) continue;
29548
30516
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
29549
30517
  if (filePath.includes("http://") || filePath.includes("https://") || filePath.includes("node_modules")) continue;
29550
- if (!existsSync27(filePath)) continue;
30518
+ if (!existsSync29(filePath)) continue;
29551
30519
  attemptedFiles.add(dedupKey);
29552
30520
  attempted++;
29553
30521
  try {
@@ -29669,9 +30637,9 @@ __export(legacy_ink_ui_exports, {
29669
30637
  });
29670
30638
  import React6 from "react";
29671
30639
  import { render } from "ink";
29672
- import { existsSync as existsSync28 } from "node:fs";
29673
- import { join as join36 } from "node:path";
29674
- import { homedir as homedir22 } from "node:os";
30640
+ import { existsSync as existsSync30 } from "node:fs";
30641
+ import { join as join37 } from "node:path";
30642
+ import { homedir as homedir23 } from "node:os";
29675
30643
  import { EventEmitter as EventEmitter2 } from "node:events";
29676
30644
  async function startInkUI(resumeSessionId, bridges = []) {
29677
30645
  await ensureDirectories();
@@ -29699,12 +30667,12 @@ async function startInkUI(resumeSessionId, bridges = []) {
29699
30667
  });
29700
30668
  const agentLoop = new AgentLoop(agentConfig, toolRegistry, resumeSessionId);
29701
30669
  toolRegistry.setWebSearchExecutor(createWebSearchExecutor(agentLoop));
29702
- const memoryDbPath = join36(homedir22(), ".openjaw", "memory.db");
30670
+ const memoryDbPath = join37(homedir23(), ".openjaw", "memory.db");
29703
30671
  const memoryLegacyPaths = [
29704
- join36(homedir22(), ".openjaw", "MEMORY.md"),
29705
- join36(homedir22(), ".openjaw", "memory", "MEMORY.md")
30672
+ join37(homedir23(), ".openjaw", "MEMORY.md"),
30673
+ join37(homedir23(), ".openjaw", "memory", "MEMORY.md")
29706
30674
  ];
29707
- const memoryStatus = existsSync28(memoryDbPath) ? "loaded" : memoryLegacyPaths.some((p) => existsSync28(p)) ? "legacy" : "empty";
30675
+ const memoryStatus = existsSync30(memoryDbPath) ? "loaded" : memoryLegacyPaths.some((p) => existsSync30(p)) ? "legacy" : "empty";
29708
30676
  const systemPromptFn = /* @__PURE__ */ __name(() => getSystemPrompt({ mcpManager }), "systemPromptFn");
29709
30677
  const { createSkillTool: createSkillTool2 } = await Promise.resolve().then(() => (init_skill_tool(), skill_tool_exports));
29710
30678
  toolRegistry.registerTool(createSkillTool2(agentConfig, toolRegistry, systemPromptFn));
@@ -29971,6 +30939,15 @@ var init_agentBus = __esm({
29971
30939
  hasRpc(method) {
29972
30940
  return this.handlers.has(method);
29973
30941
  }
30942
+ /**
30943
+ * Read the currently-registered handler for `method`, or undefined if
30944
+ * none. The desktop layer uses this to wrap an existing handler with
30945
+ * a lock-aware override (electronMain.ts registers `session.resume`
30946
+ * after registerRpcHandlers and needs to delegate to the original).
30947
+ */
30948
+ getRpc(method) {
30949
+ return this.handlers.get(method);
30950
+ }
29974
30951
  /**
29975
30952
  * RPC dispatch entry point. Mirrors hermes's WebSocket `request(method, params)`
29976
30953
  * but is a direct in-process function call. When no handler is registered,
@@ -30072,9 +31049,9 @@ var init_agentBus = __esm({
30072
31049
  });
30073
31050
 
30074
31051
  // src/bridges/registry.ts
30075
- import { existsSync as existsSync29, unlinkSync as unlinkSync8 } from "node:fs";
30076
- import { homedir as homedir23 } from "node:os";
30077
- import { join as join37 } from "node:path";
31052
+ import { existsSync as existsSync31, unlinkSync as unlinkSync9 } from "node:fs";
31053
+ import { homedir as homedir24 } from "node:os";
31054
+ import { join as join38 } from "node:path";
30078
31055
  var BRIDGE_REGISTRY, BRIDGE_NAMES, isBridgeName, normalizeCredentialsForConfig, BridgeManager, parseListInput;
30079
31056
  var init_registry3 = __esm({
30080
31057
  "src/bridges/registry.ts"() {
@@ -30173,15 +31150,15 @@ var init_registry3 = __esm({
30173
31150
  return { instance, stop: /* @__PURE__ */ __name(() => instance.stop(), "stop") };
30174
31151
  },
30175
31152
  async onForget(instance) {
30176
- const wechatPath = join37(homedir23(), ".openjaw-agent", "wechat-account.json");
31153
+ const wechatPath = join38(homedir24(), ".openjaw-agent", "wechat-account.json");
30177
31154
  if (instance && typeof instance.logout === "function") {
30178
31155
  try {
30179
31156
  instance.logout();
30180
31157
  } catch {
30181
31158
  }
30182
- } else if (existsSync29(wechatPath)) {
31159
+ } else if (existsSync31(wechatPath)) {
30183
31160
  try {
30184
- unlinkSync8(wechatPath);
31161
+ unlinkSync9(wechatPath);
30185
31162
  } catch {
30186
31163
  }
30187
31164
  }
@@ -30454,12 +31431,19 @@ var init_registry3 = __esm({
30454
31431
 
30455
31432
  // src/bootstrap.ts
30456
31433
  import { EventEmitter as EventEmitter4 } from "node:events";
30457
- import { existsSync as existsSync30 } from "node:fs";
30458
- import { homedir as homedir24 } from "node:os";
30459
- import { join as join38 } from "node:path";
31434
+ import { existsSync as existsSync32 } from "node:fs";
31435
+ import { homedir as homedir25 } from "node:os";
31436
+ import { join as join39 } from "node:path";
30460
31437
  async function bootstrapAgent(opts = {}) {
30461
31438
  await ensureDirectories();
30462
31439
  const config = await loadConfig();
31440
+ const { configureWam: configureWam2 } = await Promise.resolve().then(() => (init_wam_token_provider(), wam_token_provider_exports));
31441
+ const authMode = config.microsoft?.auth_mode ?? "auto";
31442
+ configureWam2({
31443
+ enabled: authMode !== "cdp",
31444
+ ...config.microsoft?.client_id ? { clientId: config.microsoft.client_id } : {},
31445
+ ...config.microsoft?.tenant_id ? { tenant: config.microsoft.tenant_id } : {}
31446
+ });
30463
31447
  const toolRegistry = new ToolRegistry(config);
30464
31448
  const memoryStore = new MemoryStore(config);
30465
31449
  await toolRegistry.initializeProfile("core");
@@ -30467,9 +31451,10 @@ async function bootstrapAgent(opts = {}) {
30467
31451
  const { createMetaTool: createMetaTool2 } = await Promise.resolve().then(() => (init_meta(), meta_exports));
30468
31452
  toolRegistry.registerTool(createMetaTool2(toolRegistry));
30469
31453
  const agentConfig = loadAgentConfig();
31454
+ const interactiveMcp = opts.interactiveMcp !== false;
30470
31455
  const mcpManager = new MCPClientManager();
30471
31456
  try {
30472
- await mcpManager.prepare(process.cwd(), true);
31457
+ await mcpManager.prepare(process.cwd(), interactiveMcp);
30473
31458
  } catch (err) {
30474
31459
  process.stderr.write(
30475
31460
  `\x1B[33m\u26A0 MCP discovery failed: ${err instanceof Error ? err.message : String(err)}\x1B[0m
@@ -30486,12 +31471,12 @@ async function bootstrapAgent(opts = {}) {
30486
31471
  });
30487
31472
  const agentLoop = new AgentLoop(agentConfig, toolRegistry, opts.resumeSessionId);
30488
31473
  toolRegistry.setWebSearchExecutor(createWebSearchExecutor(agentLoop));
30489
- const memoryDbPath = join38(homedir24(), ".openjaw", "memory.db");
31474
+ const memoryDbPath = join39(homedir25(), ".openjaw", "memory.db");
30490
31475
  const memoryLegacyPaths = [
30491
- join38(homedir24(), ".openjaw", "MEMORY.md"),
30492
- join38(homedir24(), ".openjaw", "memory", "MEMORY.md")
31476
+ join39(homedir25(), ".openjaw", "MEMORY.md"),
31477
+ join39(homedir25(), ".openjaw", "memory", "MEMORY.md")
30493
31478
  ];
30494
- const memoryStatus = existsSync30(memoryDbPath) ? "loaded" : memoryLegacyPaths.some((p) => existsSync30(p)) ? "legacy" : "empty";
31479
+ const memoryStatus = existsSync32(memoryDbPath) ? "loaded" : memoryLegacyPaths.some((p) => existsSync32(p)) ? "legacy" : "empty";
30495
31480
  const systemPromptFn = /* @__PURE__ */ __name(() => getSystemPrompt({ mcpManager }), "systemPromptFn");
30496
31481
  const { createSkillTool: createSkillTool2 } = await Promise.resolve().then(() => (init_skill_tool(), skill_tool_exports));
30497
31482
  toolRegistry.registerTool(createSkillTool2(agentConfig, toolRegistry, systemPromptFn));
@@ -30724,8 +31709,8 @@ function createPromptCollector(bus, getSessionId) {
30724
31709
  }, "emit");
30725
31710
  const register = /* @__PURE__ */ __name((kind) => {
30726
31711
  const requestId = randomUUID12();
30727
- const promise = new Promise((resolve6) => {
30728
- pending.set(requestId, { kind, resolve: resolve6 });
31712
+ const promise = new Promise((resolve7) => {
31713
+ pending.set(requestId, { kind, resolve: resolve7 });
30729
31714
  });
30730
31715
  return { promise, requestId };
30731
31716
  }, "register");
@@ -31283,7 +32268,7 @@ import { c as _c } from "react/compiler-runtime";
31283
32268
  import { jsx as jsx10 } from "react/jsx-runtime";
31284
32269
  import { jsx as jsx22 } from "react/jsx-runtime";
31285
32270
  import { Buffer as Buffer2 } from "buffer";
31286
- import { spawn as spawn4 } from "child_process";
32271
+ import { spawn as spawn6 } from "child_process";
31287
32272
  import { jsx as jsx32 } from "react/jsx-runtime";
31288
32273
  import { ansiCodesToString, reduceAnsiCodes, tokenize as tokenize2, undoAnsiCodes } from "@alcalzone/ansi-tokenize";
31289
32274
  import emojiRegex from "emoji-regex";
@@ -31353,7 +32338,7 @@ import { Buffer as Buffer3 } from "buffer";
31353
32338
  import { createContext as createContext7, useEffect as useEffect4, useState as useState22 } from "react";
31354
32339
  import { c as _c10 } from "react/compiler-runtime";
31355
32340
  import { jsx as jsx12 } from "react/jsx-runtime";
31356
- import { readFileSync as readFileSync27 } from "fs";
32341
+ import { readFileSync as readFileSync28 } from "fs";
31357
32342
  import codeExcerpt from "code-excerpt";
31358
32343
  import StackUtils from "stack-utils";
31359
32344
  import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -31361,7 +32346,7 @@ import { jsx as jsx14 } from "react/jsx-runtime";
31361
32346
  import { ansiCodesToString as ansiCodesToString3, diffAnsiCodes as diffAnsiCodes2 } from "@alcalzone/ansi-tokenize";
31362
32347
  import { styledCharsFromTokens, tokenize as tokenize3 } from "@alcalzone/ansi-tokenize";
31363
32348
  import bidiFactory from "bidi-js";
31364
- import noop from "lodash-es/noop.js";
32349
+ import noop3 from "lodash-es/noop.js";
31365
32350
  import { LegacyRoot } from "react-reconciler/constants.js";
31366
32351
  import { jsx as jsx15 } from "react/jsx-runtime";
31367
32352
  import { default as default2, UncontrolledTextInput } from "ink-text-input";
@@ -31709,8 +32694,8 @@ function supportsOsc52Clipboard(terminal2 = env.terminal) {
31709
32694
  return OSC52_CAPABLE_TERMINALS.includes(terminal2 ?? "");
31710
32695
  }
31711
32696
  function execFileNoThrow(file2, args, options = {}) {
31712
- return new Promise((resolve6) => {
31713
- const child = spawn4(file2, args, {
32697
+ return new Promise((resolve7) => {
32698
+ const child = spawn6(file2, args, {
31714
32699
  cwd: options.useCwd ? process.cwd() : void 0,
31715
32700
  env: options.env,
31716
32701
  stdio: "pipe"
@@ -31732,13 +32717,13 @@ function execFileNoThrow(file2, args, options = {}) {
31732
32717
  if (timer) {
31733
32718
  clearTimeout(timer);
31734
32719
  }
31735
- resolve6({ stdout, stderr, code: 1, error: String(error) });
32720
+ resolve7({ stdout, stderr, code: 1, error: String(error) });
31736
32721
  });
31737
32722
  child.on("close", (code) => {
31738
32723
  if (timer) {
31739
32724
  clearTimeout(timer);
31740
32725
  }
31741
- resolve6({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
32726
+ resolve7({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
31742
32727
  });
31743
32728
  if (options.input) {
31744
32729
  child.stdin?.write(options.input);
@@ -32950,11 +33935,11 @@ function sliceAnsi(str, start, end) {
32950
33935
  }
32951
33936
  if (end !== void 0) {
32952
33937
  const key = `${start}|${end}|${str}`;
32953
- const cached7 = sliceCache.get(key);
32954
- if (cached7 !== void 0) {
33938
+ const cached8 = sliceCache.get(key);
33939
+ if (cached8 !== void 0) {
32955
33940
  sliceCache.delete(key);
32956
- sliceCache.set(key, cached7);
32957
- return cached7;
33941
+ sliceCache.set(key, cached8);
33942
+ return cached8;
32958
33943
  }
32959
33944
  const result = computeSlice(str, start, end);
32960
33945
  if (sliceCache.size >= SLICE_CACHE_LIMIT) {
@@ -33009,11 +33994,11 @@ function computeSlice(str, start, end) {
33009
33994
  return result;
33010
33995
  }
33011
33996
  function lineWidth(line) {
33012
- const cached7 = cache.get(line);
33013
- if (cached7 !== void 0) {
33997
+ const cached8 = cache.get(line);
33998
+ if (cached8 !== void 0) {
33014
33999
  cache.delete(line);
33015
- cache.set(line, cached7);
33016
- return cached7;
34000
+ cache.set(line, cached8);
34001
+ return cached8;
33017
34002
  }
33018
34003
  const width = stringWidth(line);
33019
34004
  if (cache.size >= MAX_CACHE_SIZE) {
@@ -33030,11 +34015,11 @@ function evictLineWidthCache(keepRatio = 0) {
33030
34015
  }
33031
34016
  function memoizedWrap(text, maxWidth, wrapType) {
33032
34017
  const key = `${maxWidth}|${wrapType}|${text}`;
33033
- const cached7 = wrapCache.get(key);
33034
- if (cached7 !== void 0) {
34018
+ const cached8 = wrapCache.get(key);
34019
+ if (cached8 !== void 0) {
33035
34020
  wrapCache.delete(key);
33036
- wrapCache.set(key, cached7);
33037
- return cached7;
34021
+ wrapCache.set(key, cached8);
34022
+ return cached8;
33038
34023
  }
33039
34024
  const result = computeWrap(text, maxWidth, wrapType);
33040
34025
  if (wrapCache.size >= WRAP_CACHE_LIMIT) {
@@ -34994,9 +35979,9 @@ function collectRemovedRects(parent, removed, underAbsolute = false) {
34994
35979
  }
34995
35980
  const elem = removed;
34996
35981
  const isAbsolute2 = underAbsolute || elem.style.position === "absolute";
34997
- const cached7 = nodeCache.get(elem);
34998
- if (cached7) {
34999
- addPendingClear(parent, cached7, isAbsolute2);
35982
+ const cached8 = nodeCache.get(elem);
35983
+ if (cached8) {
35984
+ addPendingClear(parent, cached8, isAbsolute2);
35000
35985
  nodeCache.delete(elem);
35001
35986
  }
35002
35987
  for (const child of elem.childNodes) {
@@ -36711,8 +37696,8 @@ function setTerminalFocused(v) {
36711
37696
  cb();
36712
37697
  }
36713
37698
  if (!v) {
36714
- for (const resolve6 of resolvers) {
36715
- resolve6();
37699
+ for (const resolve7 of resolvers) {
37700
+ resolve7();
36716
37701
  }
36717
37702
  resolvers.clear();
36718
37703
  }
@@ -37116,31 +38101,31 @@ function renderNodeToOutput(node, output, {
37116
38101
  if (y < 0 && node.style.position === "absolute") {
37117
38102
  y = 0;
37118
38103
  }
37119
- const cached7 = nodeCache.get(node);
37120
- if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached7 && cached7.x === x && cached7.y === y && cached7.width === width && cached7.height === height && prevScreen) {
38104
+ const cached8 = nodeCache.get(node);
38105
+ if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached8 && cached8.x === x && cached8.y === y && cached8.width === width && cached8.height === height && prevScreen) {
37121
38106
  const fx = Math.floor(x);
37122
38107
  const fy = Math.floor(y);
37123
38108
  const fw = Math.floor(width);
37124
38109
  const fh = Math.floor(height);
37125
38110
  output.blit(prevScreen, fx, fy, fw, fh);
37126
38111
  if (node.style.position === "absolute") {
37127
- absoluteRectsCur.push(cached7);
38112
+ absoluteRectsCur.push(cached8);
37128
38113
  }
37129
38114
  blitEscapingAbsoluteDescendants(node, output, prevScreen, fx, fy, fw, fh);
37130
38115
  return;
37131
38116
  }
37132
- const positionChanged = cached7 !== void 0 && (cached7.x !== x || cached7.y !== y || cached7.width !== width || cached7.height !== height);
38117
+ const positionChanged = cached8 !== void 0 && (cached8.x !== x || cached8.y !== y || cached8.width !== width || cached8.height !== height);
37133
38118
  if (positionChanged) {
37134
38119
  layoutShifted = true;
37135
38120
  absoluteOverlayMoved ||= node.style.position === "absolute";
37136
38121
  }
37137
- if (cached7 && (node.dirty || positionChanged)) {
38122
+ if (cached8 && (node.dirty || positionChanged)) {
37138
38123
  output.clear(
37139
38124
  {
37140
- x: Math.floor(cached7.x),
37141
- y: Math.floor(cached7.y),
37142
- width: Math.floor(cached7.width),
37143
- height: Math.floor(cached7.height)
38125
+ x: Math.floor(cached8.x),
38126
+ y: Math.floor(cached8.y),
38127
+ width: Math.floor(cached8.width),
38128
+ height: Math.floor(cached8.height)
37144
38129
  },
37145
38130
  node.style.position === "absolute"
37146
38131
  );
@@ -37315,7 +38300,7 @@ function renderNodeToOutput(node, output, {
37315
38300
  const delta = contentCached.y - contentY;
37316
38301
  const regionTop = Math.floor(y + contentYoga.getComputedTop());
37317
38302
  const regionBottom = regionTop + innerHeight - 1;
37318
- if (cached7?.y === y && cached7.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
38303
+ if (cached8?.y === y && cached8.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
37319
38304
  hint = { top: regionTop, bottom: regionBottom, delta };
37320
38305
  scrollHint = hint;
37321
38306
  } else {
@@ -37615,13 +38600,13 @@ function blitEscapingAbsoluteDescendants(node, output, prevScreen, px, py, pw, p
37615
38600
  }
37616
38601
  const elem = child;
37617
38602
  if (elem.style.position === "absolute") {
37618
- const cached7 = nodeCache.get(elem);
37619
- if (cached7) {
37620
- absoluteRectsCur.push(cached7);
37621
- const cx = Math.floor(cached7.x);
37622
- const cy = Math.floor(cached7.y);
37623
- const cw = Math.floor(cached7.width);
37624
- const ch = Math.floor(cached7.height);
38603
+ const cached8 = nodeCache.get(elem);
38604
+ if (cached8) {
38605
+ absoluteRectsCur.push(cached8);
38606
+ const cx = Math.floor(cached8.x);
38607
+ const cy = Math.floor(cached8.y);
38608
+ const cw = Math.floor(cached8.width);
38609
+ const ch = Math.floor(cached8.height);
37625
38610
  if (cx < px || cy < py || cx + cw > pr || cy + ch > pb) {
37626
38611
  output.blit(prevScreen, cx, cy, cw, ch);
37627
38612
  }
@@ -37637,20 +38622,20 @@ function renderScrolledChildren(node, output, offsetX, offsetY, hasRemovedChild,
37637
38622
  const childElem = childNode;
37638
38623
  const cy = childElem.yogaNode;
37639
38624
  if (cy) {
37640
- const cached7 = nodeCache.get(childElem);
38625
+ const cached8 = nodeCache.get(childElem);
37641
38626
  let top;
37642
38627
  let height;
37643
- if (cached7?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
37644
- top = cached7.top;
37645
- height = cached7.height;
38628
+ if (cached8?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
38629
+ top = cached8.top;
38630
+ height = cached8.height;
37646
38631
  } else {
37647
38632
  top = cy.getComputedTop();
37648
38633
  height = cy.getComputedHeight();
37649
38634
  if (childElem.dirty) {
37650
- cumHeightShift += height - (cached7 ? cached7.height : 0);
38635
+ cumHeightShift += height - (cached8 ? cached8.height : 0);
37651
38636
  }
37652
- if (cached7) {
37653
- cached7.top = top;
38637
+ if (cached8) {
38638
+ cached8.top = top;
37654
38639
  }
37655
38640
  }
37656
38641
  const bottom = top + height;
@@ -38336,7 +39321,7 @@ function ErrorOverview({ error }) {
38336
39321
  let lineWidth2 = 0;
38337
39322
  if (filePath && origin?.line) {
38338
39323
  try {
38339
- const sourceCode = readFileSync27(filePath, "utf8");
39324
+ const sourceCode = readFileSync28(filePath, "utf8");
38340
39325
  excerpt = codeExcerpt(sourceCode, origin.line);
38341
39326
  if (excerpt) {
38342
39327
  for (const { line } of excerpt) {
@@ -40060,11 +41045,11 @@ var init_entry_exports = __esm({
40060
41045
  return rawStringWidth(str);
40061
41046
  }
40062
41047
  }
40063
- const cached7 = widthCache.get(str);
40064
- if (cached7 !== void 0) {
41048
+ const cached8 = widthCache.get(str);
41049
+ if (cached8 !== void 0) {
40065
41050
  widthCache.delete(str);
40066
- widthCache.set(str, cached7);
40067
- return cached7;
41051
+ widthCache.set(str, cached8);
41052
+ return cached8;
40068
41053
  }
40069
41054
  const w = rawStringWidth(str);
40070
41055
  if (widthCache.size >= WIDTH_CACHE_LIMIT) {
@@ -42169,9 +43154,9 @@ $ npm install --save-dev react-devtools-core
42169
43154
  if (char.length === 1) {
42170
43155
  const code = char.charCodeAt(0);
42171
43156
  if (code < 128) {
42172
- const cached7 = this.ascii[code];
42173
- if (cached7 !== -1) {
42174
- return cached7;
43157
+ const cached8 = this.ascii[code];
43158
+ if (cached8 !== -1) {
43159
+ return cached8;
42175
43160
  }
42176
43161
  const index2 = this.strings.length;
42177
43162
  this.strings.push(char);
@@ -42950,11 +43935,11 @@ $ npm install --save-dev react-devtools-core
42950
43935
  * and the terminal doesn't respond, the promise remains pending.
42951
43936
  */
42952
43937
  send(query) {
42953
- return new Promise((resolve6) => {
43938
+ return new Promise((resolve7) => {
42954
43939
  this.queue.push({
42955
43940
  kind: "query",
42956
43941
  match: query.match,
42957
- resolve: /* @__PURE__ */ __name((r) => resolve6(r), "resolve")
43942
+ resolve: /* @__PURE__ */ __name((r) => resolve7(r), "resolve")
42958
43943
  });
42959
43944
  this.stdout.write(query.request);
42960
43945
  });
@@ -42969,8 +43954,8 @@ $ npm install --save-dev react-devtools-core
42969
43954
  * Safe to call with no pending queries — still waits for a round-trip.
42970
43955
  */
42971
43956
  flush() {
42972
- return new Promise((resolve6) => {
42973
- this.queue.push({ kind: "sentinel", resolve: resolve6 });
43957
+ return new Promise((resolve7) => {
43958
+ this.queue.push({ kind: "sentinel", resolve: resolve7 });
42974
43959
  this.stdout.write(SENTINEL);
42975
43960
  });
42976
43961
  }
@@ -45450,8 +46435,8 @@ $ npm install --save-dev react-devtools-core
45450
46435
  }
45451
46436
  }
45452
46437
  async waitUntilExit() {
45453
- this.exitPromise ||= new Promise((resolve6, reject) => {
45454
- this.resolveExitPromise = resolve6;
46438
+ this.exitPromise ||= new Promise((resolve7, reject) => {
46439
+ this.resolveExitPromise = resolve7;
45455
46440
  this.rejectExitPromise = reject;
45456
46441
  });
45457
46442
  return this.exitPromise;
@@ -45865,7 +46850,7 @@ var init_details = __esm({
45865
46850
  });
45866
46851
 
45867
46852
  // src/lib/clipboard.ts
45868
- import { execFile, spawn as spawn5 } from "node:child_process";
46853
+ import { execFile, spawn as spawn7 } from "node:child_process";
45869
46854
  import { promisify as promisify6 } from "node:util";
45870
46855
  function isUsableClipboardText(text) {
45871
46856
  if (!text || !/[^\s]/.test(text)) {
@@ -45938,14 +46923,14 @@ function writeClipboardCommands(platform2, env2) {
45938
46923
  attempts.push({ cmd: "xsel", args: ["--clipboard", "--input"] });
45939
46924
  return attempts;
45940
46925
  }
45941
- async function writeClipboardText(text, platform2 = process.platform, start = spawn5, env2 = process.env) {
46926
+ async function writeClipboardText(text, platform2 = process.platform, start = spawn7, env2 = process.env) {
45942
46927
  const candidates = writeClipboardCommands(platform2, env2);
45943
46928
  for (const { cmd, args } of candidates) {
45944
46929
  try {
45945
- const ok = await new Promise((resolve6) => {
46930
+ const ok = await new Promise((resolve7) => {
45946
46931
  const child = start(cmd, [...args], { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
45947
- child.once("error", () => resolve6(false));
45948
- child.once("close", (code) => resolve6(code === 0));
46932
+ child.once("error", () => resolve7(false));
46933
+ child.once("close", (code) => resolve7(code === 0));
45949
46934
  child.stdin?.end(text);
45950
46935
  });
45951
46936
  if (ok) {
@@ -46004,8 +46989,8 @@ async function readOsc52Clipboard(querier, timeoutMs = 500) {
46004
46989
  if (!querier) {
46005
46990
  return null;
46006
46991
  }
46007
- const timeout = new Promise((resolve6) => {
46008
- setTimeout(() => resolve6(void 0), timeoutMs);
46992
+ const timeout = new Promise((resolve7) => {
46993
+ setTimeout(() => resolve7(void 0), timeoutMs);
46009
46994
  });
46010
46995
  const query = querier.send({
46011
46996
  request: buildOsc52ClipboardQuery(),
@@ -46035,8 +47020,8 @@ var init_osc52 = __esm({
46035
47020
 
46036
47021
  // src/lib/terminalSetup.ts
46037
47022
  import { copyFile, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
46038
- import { homedir as homedir25 } from "node:os";
46039
- import { join as join39 } from "node:path";
47023
+ import { homedir as homedir26 } from "node:os";
47024
+ import { join as join40 } from "node:path";
46040
47025
  function detectVSCodeLikeTerminal(env2 = process.env) {
46041
47026
  const askpass = env2["VSCODE_GIT_ASKPASS_MAIN"]?.toLowerCase() ?? "";
46042
47027
  if (env2["CURSOR_TRACE_ID"] || askpass.includes("cursor")) {
@@ -46090,14 +47075,14 @@ function stripJsonComments(content) {
46090
47075
  function isRemoteShellSession(env2) {
46091
47076
  return Boolean(env2["SSH_CONNECTION"] || env2["SSH_TTY"] || env2["SSH_CLIENT"]);
46092
47077
  }
46093
- function getVSCodeStyleConfigDir(appName, platform2 = process.platform, env2 = process.env, homeDir = homedir25()) {
47078
+ function getVSCodeStyleConfigDir(appName, platform2 = process.platform, env2 = process.env, homeDir = homedir26()) {
46094
47079
  if (platform2 === "darwin") {
46095
- return join39(homeDir, "Library", "Application Support", appName, "User");
47080
+ return join40(homeDir, "Library", "Application Support", appName, "User");
46096
47081
  }
46097
47082
  if (platform2 === "win32") {
46098
- return env2["APPDATA"] ? join39(env2["APPDATA"], appName, "User") : null;
47083
+ return env2["APPDATA"] ? join40(env2["APPDATA"], appName, "User") : null;
46099
47084
  }
46100
- return join39(homeDir, ".config", appName, "User");
47085
+ return join40(homeDir, ".config", appName, "User");
46101
47086
  }
46102
47087
  function isKeybinding(value) {
46103
47088
  return typeof value === "object" && value !== null;
@@ -46165,7 +47150,7 @@ async function backupFile(filePath, ops) {
46165
47150
  async function configureTerminalKeybindings(terminal2, options) {
46166
47151
  const env2 = options?.env ?? process.env;
46167
47152
  const platform2 = options?.platform ?? process.platform;
46168
- const homeDir = options?.homeDir ?? homedir25();
47153
+ const homeDir = options?.homeDir ?? homedir26();
46169
47154
  const ops = { ...DEFAULT_FILE_OPS, ...options?.fileOps ?? {} };
46170
47155
  const meta = TERMINAL_META[terminal2];
46171
47156
  if (isRemoteShellSession(env2)) {
@@ -46181,7 +47166,7 @@ async function configureTerminalKeybindings(terminal2, options) {
46181
47166
  message: `Could not determine ${meta.label} settings path on this platform.`
46182
47167
  };
46183
47168
  }
46184
- const keybindingsFile = join39(configDir2, "keybindings.json");
47169
+ const keybindingsFile = join40(configDir2, "keybindings.json");
46185
47170
  try {
46186
47171
  await ops.mkdir(configDir2, { recursive: true });
46187
47172
  let keybindings = [];
@@ -46264,7 +47249,7 @@ async function shouldPromptForTerminalSetup(options) {
46264
47249
  return false;
46265
47250
  }
46266
47251
  const platform2 = options?.platform ?? process.platform;
46267
- const homeDir = options?.homeDir ?? homedir25();
47252
+ const homeDir = options?.homeDir ?? homedir26();
46268
47253
  const ops = { ...DEFAULT_FILE_OPS, ...options?.fileOps ?? {} };
46269
47254
  const meta = TERMINAL_META[detected];
46270
47255
  const configDir2 = getVSCodeStyleConfigDir(meta.appName, platform2, env2, homeDir);
@@ -46272,7 +47257,7 @@ async function shouldPromptForTerminalSetup(options) {
46272
47257
  return false;
46273
47258
  }
46274
47259
  try {
46275
- const content = await ops.readFile(join39(configDir2, "keybindings.json"), "utf8");
47260
+ const content = await ops.readFile(join40(configDir2, "keybindings.json"), "utf8");
46276
47261
  const parsed = JSON.parse(stripJsonComments(content));
46277
47262
  if (!Array.isArray(parsed)) {
46278
47263
  return true;
@@ -46378,7 +47363,7 @@ var init_overlayStore = __esm({
46378
47363
  $overlayState = atom2(buildOverlayState());
46379
47364
  $isBlocked = computed2(
46380
47365
  $overlayState,
46381
- ({ agents, approval, clarify, confirm, mcpHub, modelPicker, pager, picker, secret, skillsHub, sudo }) => Boolean(agents || approval || clarify || confirm || mcpHub || modelPicker || pager || picker || secret || skillsHub || sudo)
47366
+ ({ agents, approval, clarify, confirm, mcpHub, pager, picker, secret, skillsHub, sudo }) => Boolean(agents || approval || clarify || confirm || mcpHub || pager || picker || secret || skillsHub || sudo)
46382
47367
  );
46383
47368
  patchOverlayState = /* @__PURE__ */ __name((next) => $overlayState.set(typeof next === "function" ? next($overlayState.get()) : { ...$overlayState.get(), ...next }), "patchOverlayState");
46384
47369
  resetFlowOverlays = /* @__PURE__ */ __name(() => $overlayState.set({
@@ -47023,8 +48008,8 @@ var init_bridges = __esm({
47023
48008
  // src/lib/memory.ts
47024
48009
  import { createWriteStream } from "node:fs";
47025
48010
  import { mkdir as mkdir4, readdir as readdir3, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
47026
- import { homedir as homedir26, tmpdir as tmpdir11 } from "node:os";
47027
- import { join as join40 } from "node:path";
48011
+ import { homedir as homedir27, tmpdir as tmpdir11 } from "node:os";
48012
+ import { join as join41 } from "node:path";
47028
48013
  import { pipeline } from "node:stream/promises";
47029
48014
  import { getHeapSnapshot, getHeapSpaceStatistics, getHeapStatistics } from "node:v8";
47030
48015
  async function captureMemoryDiagnostics(trigger) {
@@ -47098,11 +48083,11 @@ async function captureMemoryDiagnostics(trigger) {
47098
48083
  async function performHeapDump(trigger = "manual") {
47099
48084
  try {
47100
48085
  const diagnostics = await captureMemoryDiagnostics(trigger);
47101
- const dir2 = process.env.OPENJAW_HEAPDUMP_DIR?.trim() || join40(homedir26() || tmpdir11(), ".openjaw-agent", "heapdumps");
48086
+ const dir2 = process.env.OPENJAW_HEAPDUMP_DIR?.trim() || join41(homedir27() || tmpdir11(), ".openjaw-agent", "heapdumps");
47102
48087
  await mkdir4(dir2, { recursive: true });
47103
48088
  const base = `hermes-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${process.pid}-${trigger}`;
47104
- const heapPath = join40(dir2, `${base}.heapsnapshot`);
47105
- const diagPath = join40(dir2, `${base}.diagnostics.json`);
48089
+ const heapPath = join41(dir2, `${base}.heapsnapshot`);
48090
+ const diagPath = join41(dir2, `${base}.diagnostics.json`);
47106
48091
  await writeFile4(diagPath, JSON.stringify(diagnostics, null, 2), { mode: 384 });
47107
48092
  await pipeline(getHeapSnapshot(), createWriteStream(heapPath, { mode: 384 }));
47108
48093
  return { diagPath, heapPath, success: true };
@@ -47959,15 +48944,15 @@ var init_ops = __esm({
47959
48944
  }
47960
48945
  const [a, b] = parts;
47961
48946
  const history = getSpawnHistory();
47962
- const resolve6 = /* @__PURE__ */ __name((token) => {
48947
+ const resolve7 = /* @__PURE__ */ __name((token) => {
47963
48948
  const n = parseInt(token, 10);
47964
48949
  if (Number.isFinite(n) && n >= 1 && n <= history.length) {
47965
48950
  return history[n - 1] ?? null;
47966
48951
  }
47967
48952
  return null;
47968
48953
  }, "resolve");
47969
- const baseline = resolve6(a);
47970
- const candidate = resolve6(b);
48954
+ const baseline = resolve7(a);
48955
+ const candidate = resolve7(b);
47971
48956
  if (!baseline || !candidate) {
47972
48957
  return ctx.transcript.sys(`replay-diff: could not resolve indices \xB7 history has ${history.length} entries`);
47973
48958
  }
@@ -48861,8 +49846,8 @@ var init_setup = __esm({
48861
49846
 
48862
49847
  // src/app/slash/catalog.ts
48863
49848
  import { readdirSync as readdirSync6 } from "node:fs";
48864
- import { homedir as homedir27 } from "node:os";
48865
- import { isAbsolute, join as join41, resolve as resolve3, sep } from "node:path";
49849
+ import { homedir as homedir28 } from "node:os";
49850
+ import { isAbsolute, join as join42, resolve as resolve4, sep } from "node:path";
48866
49851
  function buildCommandsCatalog(extras = {}) {
48867
49852
  const canon = {};
48868
49853
  const pairs = [];
@@ -48964,7 +49949,7 @@ function buildPathCompletions(word) {
48964
49949
  prefix = expanded.slice(lastSep + 1);
48965
49950
  }
48966
49951
  }
48967
- const resolved = isAbsolute(dir2) ? dir2 : resolve3(process.cwd(), dir2);
49952
+ const resolved = isAbsolute(dir2) ? dir2 : resolve4(process.cwd(), dir2);
48968
49953
  const entries = readdirSync6(resolved, { withFileTypes: true });
48969
49954
  const items = entries.filter((e) => e.name.toLowerCase().startsWith(prefix.toLowerCase())).filter((e) => prefix || !e.name.startsWith(".")).slice(0, 50).map((e) => {
48970
49955
  const full = dir2 + e.name + (e.isDirectory() ? sep : "");
@@ -49004,7 +49989,7 @@ var init_catalog = __esm({
49004
49989
  slashifyPair = /* @__PURE__ */ __name((cmd) => [`/${cmd.name}`, cmd.help ?? ""], "slashifyPair");
49005
49990
  __name(buildCommandsCatalog, "buildCommandsCatalog");
49006
49991
  __name(buildSlashCompletions, "buildSlashCompletions");
49007
- tildeExpand = /* @__PURE__ */ __name((p) => p.startsWith("~") ? join41(homedir27(), p.slice(1).replace(/^[\\/]/, "")) : p, "tildeExpand");
49992
+ tildeExpand = /* @__PURE__ */ __name((p) => p.startsWith("~") ? join42(homedir28(), p.slice(1).replace(/^[\\/]/, "")) : p, "tildeExpand");
49008
49993
  __name(buildPathCompletions, "buildPathCompletions");
49009
49994
  }
49010
49995
  });
@@ -49169,39 +50154,39 @@ var init_planner = __esm({
49169
50154
  });
49170
50155
 
49171
50156
  // src/workflows/persistence.ts
49172
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync7, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
49173
- import { homedir as homedir28 } from "node:os";
49174
- import { basename as basename3, join as join42, resolve as resolve4 } from "node:path";
50157
+ import { existsSync as existsSync33, mkdirSync as mkdirSync17, readdirSync as readdirSync7, readFileSync as readFileSync29, writeFileSync as writeFileSync19 } from "node:fs";
50158
+ import { homedir as homedir29 } from "node:os";
50159
+ import { basename as basename3, join as join43, resolve as resolve5 } from "node:path";
49175
50160
  function ensureDir(path3) {
49176
- if (!existsSync31(path3)) mkdirSync17(path3, { recursive: true });
50161
+ if (!existsSync33(path3)) mkdirSync17(path3, { recursive: true });
49177
50162
  }
49178
50163
  function safeSegment(value) {
49179
50164
  return value.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 80) || "default";
49180
50165
  }
49181
50166
  function readJson(path3) {
49182
50167
  try {
49183
- return JSON.parse(readFileSync28(path3, "utf8"));
50168
+ return JSON.parse(readFileSync29(path3, "utf8"));
49184
50169
  } catch {
49185
50170
  return null;
49186
50171
  }
49187
50172
  }
49188
50173
  function saveWorkflowSnapshot(snapshot) {
49189
50174
  ensureDir(workflowDir());
49190
- const path3 = join42(workflowDir(), `${safeSegment(snapshot.id)}.json`);
50175
+ const path3 = join43(workflowDir(), `${safeSegment(snapshot.id)}.json`);
49191
50176
  writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
49192
50177
  `, "utf8");
49193
50178
  return path3;
49194
50179
  }
49195
50180
  function loadWorkflowSnapshot(id) {
49196
- const path3 = join42(workflowDir(), `${safeSegment(id)}.json`);
50181
+ const path3 = join43(workflowDir(), `${safeSegment(id)}.json`);
49197
50182
  return readJson(path3);
49198
50183
  }
49199
50184
  function listWorkflowSnapshots(limit = 30) {
49200
- if (!existsSync31(workflowDir())) return [];
50185
+ if (!existsSync33(workflowDir())) return [];
49201
50186
  const entries = [];
49202
50187
  for (const entry of readdirSync7(workflowDir(), { withFileTypes: true })) {
49203
50188
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
49204
- const path3 = join42(workflowDir(), entry.name);
50189
+ const path3 = join43(workflowDir(), entry.name);
49205
50190
  const snapshot = readJson(path3);
49206
50191
  if (!snapshot) continue;
49207
50192
  entries.push({
@@ -49219,10 +50204,10 @@ function listWorkflowSnapshots(limit = 30) {
49219
50204
  }
49220
50205
  function saveSpawnTreeSnapshot(input) {
49221
50206
  const sessionId = safeSegment(input.session_id ?? "default");
49222
- const dir2 = join42(spawnTreeDir(), sessionId);
50207
+ const dir2 = join43(spawnTreeDir(), sessionId);
49223
50208
  ensureDir(dir2);
49224
50209
  const stamp = new Date((input.finished_at ?? Date.now() / 1e3) * 1e3).toISOString().replace(/[:.]/g, "-");
49225
- const path3 = join42(dir2, `${stamp}-${Math.random().toString(36).slice(2, 8)}.json`);
50210
+ const path3 = join43(dir2, `${stamp}-${Math.random().toString(36).slice(2, 8)}.json`);
49226
50211
  const snapshot = {
49227
50212
  count: input.subagents?.length ?? 0,
49228
50213
  finished_at: input.finished_at,
@@ -49236,12 +50221,12 @@ function saveSpawnTreeSnapshot(input) {
49236
50221
  return path3;
49237
50222
  }
49238
50223
  function listSpawnTreeSnapshots(sessionId = "default", limit = 30) {
49239
- const dir2 = join42(spawnTreeDir(), safeSegment(sessionId));
49240
- if (!existsSync31(dir2)) return [];
50224
+ const dir2 = join43(spawnTreeDir(), safeSegment(sessionId));
50225
+ if (!existsSync33(dir2)) return [];
49241
50226
  const entries = [];
49242
50227
  for (const entry of readdirSync7(dir2, { withFileTypes: true })) {
49243
50228
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
49244
- const path3 = join42(dir2, entry.name);
50229
+ const path3 = join43(dir2, entry.name);
49245
50230
  const snapshot = readJson(path3);
49246
50231
  if (!snapshot) continue;
49247
50232
  entries.push({
@@ -49256,8 +50241,8 @@ function listSpawnTreeSnapshots(sessionId = "default", limit = 30) {
49256
50241
  return entries.sort((a, b) => (b.finished_at ?? 0) - (a.finished_at ?? 0)).slice(0, Math.max(1, limit));
49257
50242
  }
49258
50243
  function loadSpawnTreeSnapshot(path3) {
49259
- const root = resolve4(spawnTreeDir());
49260
- const resolved = resolve4(path3);
50244
+ const root = resolve5(spawnTreeDir());
50245
+ const resolved = resolve5(path3);
49261
50246
  if (!resolved.startsWith(root)) return null;
49262
50247
  const snapshot = readJson(resolved);
49263
50248
  return snapshot ? { ...snapshot, path: resolved } : null;
@@ -49266,9 +50251,9 @@ var rootDir, workflowDir, spawnTreeDir;
49266
50251
  var init_persistence = __esm({
49267
50252
  "src/workflows/persistence.ts"() {
49268
50253
  "use strict";
49269
- rootDir = /* @__PURE__ */ __name(() => join42(homedir28(), ".openjaw-agent"), "rootDir");
49270
- workflowDir = /* @__PURE__ */ __name(() => join42(rootDir(), "workflows"), "workflowDir");
49271
- spawnTreeDir = /* @__PURE__ */ __name(() => join42(rootDir(), "spawn-trees"), "spawnTreeDir");
50254
+ rootDir = /* @__PURE__ */ __name(() => join43(homedir29(), ".openjaw-agent"), "rootDir");
50255
+ workflowDir = /* @__PURE__ */ __name(() => join43(rootDir(), "workflows"), "workflowDir");
50256
+ spawnTreeDir = /* @__PURE__ */ __name(() => join43(rootDir(), "spawn-trees"), "spawnTreeDir");
49272
50257
  __name(ensureDir, "ensureDir");
49273
50258
  __name(safeSegment, "safeSegment");
49274
50259
  __name(readJson, "readJson");
@@ -49848,11 +50833,11 @@ ${highlights}` : ""
49848
50833
  });
49849
50834
 
49850
50835
  // src/rpcHandlers.ts
49851
- import { spawn as spawn6 } from "node:child_process";
50836
+ import { spawn as spawn8 } from "node:child_process";
49852
50837
  import { randomUUID as randomUUID14 } from "node:crypto";
49853
- import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync29, rmSync, statSync as statSync4, writeFileSync as writeFileSync20 } from "node:fs";
49854
- import { homedir as homedir29 } from "node:os";
49855
- import { basename as basename4, extname as extname4, join as join43 } from "node:path";
50838
+ import { existsSync as existsSync34, mkdirSync as mkdirSync18, readFileSync as readFileSync30, rmSync, statSync as statSync4, writeFileSync as writeFileSync20 } from "node:fs";
50839
+ import { homedir as homedir30 } from "node:os";
50840
+ import { basename as basename4, extname as extname4, join as join44 } from "node:path";
49856
50841
  function registerRpcHandlers(options) {
49857
50842
  const { agentConfig, agentLoop, bridgeEmitter, bridgeManager, bus, mcpManager, systemPromptFn, toolRegistry, voiceManager } = options;
49858
50843
  let currentRun = null;
@@ -50077,6 +51062,7 @@ ${helpMessage}` : field.label;
50077
51062
  const modelInfo = agentLoop.getActiveModelMetadata();
50078
51063
  const nextEffort = parsed.clear ? void 0 : modelInfo ? resolveReasoningEffortForModel(modelInfo, parsed.effort) : parsed.effort;
50079
51064
  agentLoop.updateReasoningEffort(nextEffort);
51065
+ agentConfig.llm.model_reasoning_effort = nextEffort;
50080
51066
  bus.emitEvent({
50081
51067
  payload: sessionInfoSnapshot(agentLoop, toolRegistry),
50082
51068
  session_id: agentLoop.sessionId,
@@ -50166,17 +51152,39 @@ ${helpMessage}` : field.label;
50166
51152
  return { ok: false };
50167
51153
  }
50168
51154
  });
50169
- bus.registerRpc("session.create", () => ({
50170
- info: sessionInfoSnapshot(agentLoop, toolRegistry),
50171
- session_id: agentLoop.sessionId
50172
- }));
51155
+ bus.registerRpc("session.create", () => {
51156
+ if (agentLoop.turnCount === 0 && agentLoop.history.length === 0) {
51157
+ return {
51158
+ info: sessionInfoSnapshot(agentLoop, toolRegistry),
51159
+ session_id: agentLoop.sessionId
51160
+ };
51161
+ }
51162
+ const created = agentLoop.createAndSwitchTo();
51163
+ if (!created) {
51164
+ return {
51165
+ error: "cannot create a new session while a turn is in flight",
51166
+ session_id: agentLoop.sessionId
51167
+ };
51168
+ }
51169
+ bus.emitEvent({
51170
+ payload: sessionInfoSnapshot(agentLoop, toolRegistry),
51171
+ session_id: agentLoop.sessionId,
51172
+ type: "session.info"
51173
+ });
51174
+ return {
51175
+ info: sessionInfoSnapshot(agentLoop, toolRegistry),
51176
+ session_id: agentLoop.sessionId
51177
+ };
51178
+ });
50173
51179
  bus.registerRpc("session.resume", (params) => {
50174
51180
  const id = String(params.session_id ?? agentLoop.sessionId);
50175
- const data = loadSession(id);
51181
+ const swapped = agentLoop.swapToSession(id);
51182
+ const data = swapped ?? loadSession(id);
51183
+ const visibleMessages = (data?.messages ?? []).filter((m) => !messageIsInternalSelfPrompt(m));
50176
51184
  return {
50177
51185
  info: sessionInfoSnapshot(agentLoop, toolRegistry),
50178
- message_count: data?.messages.length ?? 0,
50179
- messages: (data?.messages ?? []).map((m) => {
51186
+ message_count: visibleMessages.length,
51187
+ messages: visibleMessages.map((m) => {
50180
51188
  if (m.role === "tool_result") {
50181
51189
  return {
50182
51190
  role: "tool",
@@ -50184,13 +51192,14 @@ ${helpMessage}` : field.label;
50184
51192
  };
50185
51193
  }
50186
51194
  const content = m.content;
51195
+ const raw = typeof content === "string" ? content : Array.isArray(content) ? content.map((c) => c.type === "text" ? c.text : "[image]").join("") : "";
50187
51196
  return {
50188
51197
  role: m.role,
50189
- text: typeof content === "string" ? content : Array.isArray(content) ? content.map((c) => c.type === "text" ? c.text : "[image]").join("") : ""
51198
+ text: stripDisplayPrefix(raw, m.role)
50190
51199
  };
50191
51200
  }),
50192
- resumed: data ? id : void 0,
50193
- session_id: id
51201
+ resumed: swapped ? id : void 0,
51202
+ session_id: agentLoop.sessionId
50194
51203
  };
50195
51204
  });
50196
51205
  bus.registerRpc("session.list", () => ({
@@ -50200,6 +51209,10 @@ ${helpMessage}` : field.label;
50200
51209
  preview: s.summary,
50201
51210
  source: `${s.provider}/${s.model}`,
50202
51211
  started_at: Date.parse(s.createdAt) || Date.now(),
51212
+ // updated_at drives the sidebar's "last activity" date grouping.
51213
+ // listSessions already sorts by updatedAt desc, so emitting it
51214
+ // here saves the renderer a second parse.
51215
+ updated_at: Date.parse(s.updatedAt) || Date.now(),
50203
51216
  title: s.summary || s.id
50204
51217
  }))
50205
51218
  }));
@@ -50234,12 +51247,12 @@ ${helpMessage}` : field.label;
50234
51247
  const id = String(params.session_id ?? agentLoop.sessionId) || agentLoop.sessionId;
50235
51248
  const data = loadSession(id);
50236
51249
  const messages = data?.messages ?? agentLoop.history;
50237
- const exportDir = join43(homedir29(), ".openjaw-agent", "exports");
50238
- if (!existsSync32(exportDir)) {
51250
+ const exportDir = join44(homedir30(), ".openjaw-agent", "exports");
51251
+ if (!existsSync34(exportDir)) {
50239
51252
  mkdirSync18(exportDir, { recursive: true });
50240
51253
  }
50241
51254
  const safeId = id.replace(/[^a-zA-Z0-9_.-]/g, "_") || agentLoop.sessionId;
50242
- const file2 = join43(exportDir, `session-${safeId}.md`);
51255
+ const file2 = join44(exportDir, `session-${safeId}.md`);
50243
51256
  const lines = [
50244
51257
  `# OpenJaw Agent Session ${id}`,
50245
51258
  `Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -50269,8 +51282,8 @@ ${helpMessage}` : field.label;
50269
51282
  return { deleted: "" };
50270
51283
  }
50271
51284
  try {
50272
- const file2 = join43(homedir29(), ".openjaw-agent", "sessions", `${id}.json`);
50273
- if (existsSync32(file2)) {
51285
+ const file2 = join44(homedir30(), ".openjaw-agent", "sessions", `${id}.json`);
51286
+ if (existsSync34(file2)) {
50274
51287
  rmSync(file2);
50275
51288
  }
50276
51289
  return { deleted: id };
@@ -50343,11 +51356,11 @@ ${helpMessage}` : field.label;
50343
51356
  bus.registerRpc("shell.exec", async (params) => {
50344
51357
  const command = String(params.command ?? "");
50345
51358
  if (!command) return { code: -1, stderr: "empty command" };
50346
- return await new Promise((resolve6) => {
51359
+ return await new Promise((resolve7) => {
50347
51360
  const isWin = process.platform === "win32";
50348
51361
  const shell = isWin ? "powershell.exe" : "sh";
50349
51362
  const args = isWin ? ["-NoProfile", "-Command", command] : ["-c", command];
50350
- const child = spawn6(shell, args, { timeout: 3e4 });
51363
+ const child = spawn8(shell, args, { timeout: 3e4 });
50351
51364
  let stdout = "";
50352
51365
  let stderr = "";
50353
51366
  let truncated = false;
@@ -50370,10 +51383,10 @@ ${helpMessage}` : field.label;
50370
51383
  if (truncated) {
50371
51384
  stderr += "\n[output truncated to 1MB]";
50372
51385
  }
50373
- resolve6({ code: code ?? -1, stderr, stdout });
51386
+ resolve7({ code: code ?? -1, stderr, stdout });
50374
51387
  });
50375
51388
  child.on("error", (err) => {
50376
- resolve6({ code: -1, stderr: err.message });
51389
+ resolve7({ code: -1, stderr: err.message });
50377
51390
  });
50378
51391
  });
50379
51392
  });
@@ -50404,6 +51417,11 @@ ${helpMessage}` : field.label;
50404
51417
  const modelInfo = agentLoop.getModelMetadata(provider, model);
50405
51418
  const reasoningEffort = parsedEffort?.clear ? void 0 : parsedEffort?.effort ? modelInfo ? resolveReasoningEffortForModel(modelInfo, parsedEffort.effort) : parsedEffort.effort : modelInfo ? resolveReasoningEffortForModel(modelInfo, agentLoop.reasoningEffort) : agentLoop.reasoningEffort;
50406
51419
  agentLoop.switchModel(provider, model, reasoningEffort);
51420
+ bus.emitEvent({
51421
+ payload: sessionInfoSnapshot(agentLoop, toolRegistry),
51422
+ session_id: agentLoop.sessionId,
51423
+ type: "session.info"
51424
+ });
50407
51425
  return {
50408
51426
  info: sessionInfoSnapshot(agentLoop, toolRegistry),
50409
51427
  ok: true
@@ -50481,8 +51499,8 @@ ${helpMessage}` : field.label;
50481
51499
  if (auth.auth_type !== "oauth") {
50482
51500
  throw new Error(`${PROVIDER_LABELS[slug]} does not use OAuth (auth_type=${auth.auth_type})`);
50483
51501
  }
50484
- const clientId = resolveCopilotClientId();
50485
- if (!clientId) {
51502
+ const clientId2 = resolveCopilotClientId();
51503
+ if (!clientId2) {
50486
51504
  throw new Error(
50487
51505
  "GitHub Copilot login needs a GitHub OAuth App client ID. Set GITHUB_COPILOT_CLIENT_ID or llm.copilot_oauth_client_id in ~/.openjaw-agent/config.yaml."
50488
51506
  );
@@ -50494,7 +51512,7 @@ ${helpMessage}` : field.label;
50494
51512
  }
50495
51513
  return agentConfig.llm.copilot_enterprise_url;
50496
51514
  })();
50497
- const flow = await startCopilotDeviceFlow(clientId, enterpriseUrl);
51515
+ const flow = await startCopilotDeviceFlow(clientId2, enterpriseUrl);
50498
51516
  const flowId = randomUUID14();
50499
51517
  const controller = new AbortController();
50500
51518
  const timer = setTimeout(() => {
@@ -51015,13 +52033,13 @@ ${helpMessage}` : field.label;
51015
52033
  const firstSpace = raw.search(/\s/);
51016
52034
  const path3 = firstSpace > 0 ? raw.slice(0, firstSpace) : raw;
51017
52035
  const remainder = firstSpace > 0 ? raw.slice(firstSpace).trim() : "";
51018
- if (!existsSync32(path3)) {
52036
+ if (!existsSync34(path3)) {
51019
52037
  throw new Error(`image.attach: file not found: ${path3}`);
51020
52038
  }
51021
52039
  let buffer;
51022
52040
  let fileSize = 0;
51023
52041
  try {
51024
- buffer = readFileSync29(path3);
52042
+ buffer = readFileSync30(path3);
51025
52043
  fileSize = statSync4(path3).size;
51026
52044
  } catch (err) {
51027
52045
  throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
@@ -51067,13 +52085,13 @@ ${helpMessage}` : field.label;
51067
52085
  }
51068
52086
  });
51069
52087
  bus.registerRpc("reload.env", () => {
51070
- const envPath = join43(homedir29(), ".openjaw-agent", ".env");
51071
- if (!existsSync32(envPath)) {
52088
+ const envPath = join44(homedir30(), ".openjaw-agent", ".env");
52089
+ if (!existsSync34(envPath)) {
51072
52090
  return { updated: 0 };
51073
52091
  }
51074
52092
  let updated = 0;
51075
52093
  try {
51076
- const raw = readFileSync29(envPath, "utf-8");
52094
+ const raw = readFileSync30(envPath, "utf-8");
51077
52095
  for (const line of raw.split(/\r?\n/)) {
51078
52096
  const trimmed = line.trim();
51079
52097
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -51252,7 +52270,7 @@ ${helpMessage}` : field.label;
51252
52270
  }
51253
52271
  };
51254
52272
  }
51255
- var PROVIDERS2, PROVIDER_LABELS, PROVIDER_AUTH, isProviderAuthenticated, buildProviderOption, fetchLiveModels, parseReasoningEffortInput, skillDescription, listRegistrySkills, registrySkillsByCategory, formatSkillExecutionResult, reasoningEffortsForModel, resolveReasoningEffortForModel, BRIDGE_SOURCES, isBridgeSource, inferBridgeSource, bridgeLabels, bridgeEventLabels, stripBridgeGlyph, firstLogLine, bridgeUser, formatBridgeText, runProcess, contentToText, sessionMessageToMarkdown, sessionInfoSnapshot, MCP_TOOL_PREFIX, MAX_TOTAL_TOOLS, syncMcpTools;
52273
+ var PROVIDERS2, PROVIDER_LABELS, PROVIDER_AUTH, isProviderAuthenticated, buildProviderOption, fetchLiveModels, parseReasoningEffortInput, skillDescription, listRegistrySkills, registrySkillsByCategory, formatSkillExecutionResult, reasoningEffortsForModel, resolveReasoningEffortForModel, BRIDGE_SOURCES, isBridgeSource, inferBridgeSource, bridgeLabels, bridgeEventLabels, stripBridgeGlyph, firstLogLine, bridgeUser, formatBridgeText, runProcess, contentToText, sessionMessageToMarkdown, DISPLAY_PREFIX_PATTERNS, stripDisplayPrefix, INTERNAL_SELF_PROMPT_PATTERNS, isInternalSelfPrompt, messageIsInternalSelfPrompt, sessionInfoSnapshot, MCP_TOOL_PREFIX, MAX_TOTAL_TOOLS, syncMcpTools;
51256
52274
  var init_rpcHandlers = __esm({
51257
52275
  "src/rpcHandlers.ts"() {
51258
52276
  "use strict";
@@ -51462,8 +52480,8 @@ var init_rpcHandlers = __esm({
51462
52480
  ${raw}`;
51463
52481
  return `${header}: ${raw}`;
51464
52482
  }, "formatBridgeText");
51465
- runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve6) => {
51466
- const child = spawn6(command, args, { timeout, windowsHide: true });
52483
+ runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve7) => {
52484
+ const child = spawn8(command, args, { timeout, windowsHide: true });
51467
52485
  let stdout = "";
51468
52486
  let stderr = "";
51469
52487
  let truncated = false;
@@ -51486,9 +52504,9 @@ ${raw}`;
51486
52504
  if (truncated) {
51487
52505
  stderr += "\n[output truncated to 1MB]";
51488
52506
  }
51489
- resolve6({ code: code ?? -1, stderr, stdout });
52507
+ resolve7({ code: code ?? -1, stderr, stdout });
51490
52508
  });
51491
- child.on("error", (err) => resolve6({ code: -1, stderr: err.message, stdout }));
52509
+ child.on("error", (err) => resolve7({ code: -1, stderr: err.message, stdout }));
51492
52510
  }), "runProcess");
51493
52511
  contentToText = /* @__PURE__ */ __name((content) => {
51494
52512
  if (typeof content === "string") return content;
@@ -51512,6 +52530,27 @@ ${raw}`;
51512
52530
  }
51513
52531
  return [];
51514
52532
  }, "sessionMessageToMarkdown");
52533
+ DISPLAY_PREFIX_PATTERNS = [
52534
+ /^\[USER TASK\]:\s*/
52535
+ ];
52536
+ stripDisplayPrefix = /* @__PURE__ */ __name((text, role) => {
52537
+ if (role !== "user") return text;
52538
+ let out = text;
52539
+ for (const pattern of DISPLAY_PREFIX_PATTERNS) {
52540
+ out = out.replace(pattern, "");
52541
+ }
52542
+ return out;
52543
+ }, "stripDisplayPrefix");
52544
+ INTERNAL_SELF_PROMPT_PATTERNS = [
52545
+ /^\[System:\s/
52546
+ ];
52547
+ isInternalSelfPrompt = /* @__PURE__ */ __name((rawText) => INTERNAL_SELF_PROMPT_PATTERNS.some((p) => p.test(rawText)), "isInternalSelfPrompt");
52548
+ messageIsInternalSelfPrompt = /* @__PURE__ */ __name((m) => {
52549
+ if (m.role !== "user") return false;
52550
+ const content = m.content;
52551
+ const raw = typeof content === "string" ? content : Array.isArray(content) ? content.map((c) => c.type === "text" ? c.text : "").join("") : "";
52552
+ return isInternalSelfPrompt(raw);
52553
+ }, "messageIsInternalSelfPrompt");
51515
52554
  sessionInfoSnapshot = /* @__PURE__ */ __name((agentLoop, toolRegistry) => ({
51516
52555
  cwd: process.cwd(),
51517
52556
  model: agentLoop.model,
@@ -51646,14 +52685,14 @@ var init_memoryMonitor = __esm({
51646
52685
  });
51647
52686
 
51648
52687
  // src/lib/openExternalUrl.ts
51649
- import { spawn as spawn7 } from "node:child_process";
52688
+ import { spawn as spawn9 } from "node:child_process";
51650
52689
  import { platform } from "node:os";
51651
52690
  function openExternalUrl(rawUrl, dependencies = {}) {
51652
52691
  const url = parseSafeUrl(rawUrl);
51653
52692
  if (!url) {
51654
52693
  return false;
51655
52694
  }
51656
- const spawnFn = dependencies.spawn ?? spawn7;
52695
+ const spawnFn = dependencies.spawn ?? spawn9;
51657
52696
  const platformId = dependencies.platform?.() ?? platform();
51658
52697
  const command = openCommand(platformId);
51659
52698
  if (!command) {
@@ -52146,9 +53185,9 @@ var init_useVirtualHistory = __esm({
52146
53185
  viewportHeight
52147
53186
  }) => itemCount > 0 && viewportHeight > 0 && !sticky && !liveTailActive, "shouldSetVirtualClamp");
52148
53187
  ensureVirtualItemHeight = /* @__PURE__ */ __name((heights, key, index, estimate, estimateHeight) => {
52149
- const cached7 = heights.get(key);
52150
- if (cached7 !== void 0) {
52151
- return Math.max(1, Math.floor(cached7));
53188
+ const cached8 = heights.get(key);
53189
+ if (cached8 !== void 0) {
53190
+ return Math.max(1, Math.floor(cached8));
52152
53191
  }
52153
53192
  const seeded = Math.max(1, Math.floor(estimateHeight?.(index, key) ?? estimate));
52154
53193
  heights.set(key, seeded);
@@ -53586,7 +54625,7 @@ function createGatewayEventHandler(ctx) {
53586
54625
  setTimeout(async () => {
53587
54626
  let sid = getUiState().sid;
53588
54627
  for (let i = 0; !sid && i < 40; i += 1) {
53589
- await new Promise((resolve6) => setTimeout(resolve6, 100));
54628
+ await new Promise((resolve7) => setTimeout(resolve7, 100));
53590
54629
  sid = getUiState().sid;
53591
54630
  }
53592
54631
  if (!sid) {
@@ -54333,21 +55372,21 @@ var init_useCompletion = __esm({
54333
55372
  });
54334
55373
 
54335
55374
  // src/lib/history.ts
54336
- import { appendFileSync as appendFileSync4, existsSync as existsSync33, mkdirSync as mkdirSync19, readFileSync as readFileSync30 } from "node:fs";
54337
- import { homedir as homedir30 } from "node:os";
54338
- import { join as join44 } from "node:path";
55375
+ import { appendFileSync as appendFileSync4, existsSync as existsSync35, mkdirSync as mkdirSync19, readFileSync as readFileSync31 } from "node:fs";
55376
+ import { homedir as homedir31 } from "node:os";
55377
+ import { join as join45 } from "node:path";
54339
55378
  function load() {
54340
55379
  if (cache3) {
54341
55380
  return cache3;
54342
55381
  }
54343
55382
  try {
54344
- if (!existsSync33(file)) {
55383
+ if (!existsSync35(file)) {
54345
55384
  cache3 = [];
54346
55385
  return cache3;
54347
55386
  }
54348
55387
  const entries = [];
54349
55388
  let current = [];
54350
- for (const line of readFileSync30(file, "utf8").split("\n")) {
55389
+ for (const line of readFileSync31(file, "utf8").split("\n")) {
54351
55390
  if (line.startsWith("+")) {
54352
55391
  current.push(line.slice(1));
54353
55392
  } else if (current.length) {
@@ -54378,7 +55417,7 @@ function append(line) {
54378
55417
  items.splice(0, items.length - MAX);
54379
55418
  }
54380
55419
  try {
54381
- if (!existsSync33(dir)) {
55420
+ if (!existsSync35(dir)) {
54382
55421
  mkdirSync19(dir, { recursive: true });
54383
55422
  }
54384
55423
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
@@ -54395,8 +55434,8 @@ var init_history = __esm({
54395
55434
  "src/lib/history.ts"() {
54396
55435
  "use strict";
54397
55436
  MAX = 1e3;
54398
- dir = process.env.OPENJAW_HOME ?? join44(homedir30(), ".openjaw-agent");
54399
- file = join44(dir, ".openjaw-agent_history");
55437
+ dir = process.env.OPENJAW_HOME ?? join45(homedir31(), ".openjaw-agent");
55438
+ file = join45(dir, ".openjaw-agent_history");
54400
55439
  cache3 = null;
54401
55440
  __name(load, "load");
54402
55441
  __name(append, "append");
@@ -54489,8 +55528,8 @@ var init_useQueue = __esm({
54489
55528
  });
54490
55529
 
54491
55530
  // src/lib/editor.ts
54492
- import { accessSync, constants } from "node:fs";
54493
- import { delimiter, join as join45 } from "node:path";
55531
+ import { accessSync as accessSync2, constants as constants2 } from "node:fs";
55532
+ import { delimiter, join as join46 } from "node:path";
54494
55533
  var FALLBACKS, isExecutable, resolveEditor;
54495
55534
  var init_editor = __esm({
54496
55535
  "src/lib/editor.ts"() {
@@ -54498,7 +55537,7 @@ var init_editor = __esm({
54498
55537
  FALLBACKS = ["editor", "nano", "pico", "vi", "emacs"];
54499
55538
  isExecutable = /* @__PURE__ */ __name((path3) => {
54500
55539
  try {
54501
- accessSync(path3, constants.X_OK);
55540
+ accessSync2(path3, constants2.X_OK);
54502
55541
  return true;
54503
55542
  } catch {
54504
55543
  return false;
@@ -54513,7 +55552,7 @@ var init_editor = __esm({
54513
55552
  return ["notepad.exe"];
54514
55553
  }
54515
55554
  const dirs = (env2.PATH ?? "").split(delimiter).filter(Boolean);
54516
- const found = FALLBACKS.flatMap((name) => dirs.map((d) => join45(d, name))).find(isExecutable);
55555
+ const found = FALLBACKS.flatMap((name) => dirs.map((d) => join46(d, name))).find(isExecutable);
54517
55556
  return [found ?? "vi"];
54518
55557
  }, "resolveEditor");
54519
55558
  }
@@ -54521,9 +55560,9 @@ var init_editor = __esm({
54521
55560
 
54522
55561
  // src/app/useComposerState.ts
54523
55562
  import { spawnSync } from "node:child_process";
54524
- import { mkdtempSync, readFileSync as readFileSync31, rmSync as rmSync2, writeFileSync as writeFileSync21 } from "node:fs";
55563
+ import { mkdtempSync, readFileSync as readFileSync32, rmSync as rmSync2, writeFileSync as writeFileSync21 } from "node:fs";
54525
55564
  import { tmpdir as tmpdir12 } from "node:os";
54526
- import { join as join46 } from "node:path";
55565
+ import { join as join47 } from "node:path";
54527
55566
  import { useStore } from "@nanostores/react";
54528
55567
  import { useCallback as useCallback7, useMemo as useMemo6, useState as useState12 } from "react";
54529
55568
  function insertAtCursor(value, cursor, text) {
@@ -54680,8 +55719,8 @@ function useComposerState({
54680
55719
  [handleResolvedPaste, onClipboardPaste, querier]
54681
55720
  );
54682
55721
  const openEditor = useCallback7(async () => {
54683
- const dir2 = mkdtempSync(join46(tmpdir12(), "hermes-"));
54684
- const file2 = join46(dir2, "prompt.md");
55722
+ const dir2 = mkdtempSync(join47(tmpdir12(), "hermes-"));
55723
+ const file2 = join47(dir2, "prompt.md");
54685
55724
  const [cmd, ...args] = resolveEditor();
54686
55725
  writeFileSync21(file2, [...inputBuf, input].join("\n"));
54687
55726
  let exitCode = null;
@@ -54692,7 +55731,7 @@ function useComposerState({
54692
55731
  if (exitCode !== 0) {
54693
55732
  return;
54694
55733
  }
54695
- const text = readFileSync31(file2, "utf8").trimEnd();
55734
+ const text = readFileSync32(file2, "utf8").trimEnd();
54696
55735
  if (!text) {
54697
55736
  return;
54698
55737
  }
@@ -56774,8 +57813,8 @@ __export(perfPane_exports, {
56774
57813
  logFrameEvent: () => logFrameEvent
56775
57814
  });
56776
57815
  import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync20 } from "node:fs";
56777
- import { homedir as homedir31 } from "node:os";
56778
- import { dirname as dirname7, join as join47 } from "node:path";
57816
+ import { homedir as homedir32 } from "node:os";
57817
+ import { dirname as dirname9, join as join48 } from "node:path";
56779
57818
  import { Profiler } from "react";
56780
57819
  import { jsx as jsx17 } from "react/jsx-runtime";
56781
57820
  function PerfPane({ children, id }) {
@@ -56791,13 +57830,13 @@ var init_perfPane = __esm({
56791
57830
  init_entry_exports();
56792
57831
  ENABLED = /^(?:1|true|yes|on)$/i.test((process.env.OPENJAW_DEV_PERF ?? "").trim());
56793
57832
  THRESHOLD_MS = Number(process.env.OPENJAW_DEV_PERF_MS ?? "2") || 0;
56794
- LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() || join47(homedir31(), ".openjaw-agent", "perf.log");
57833
+ LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() || join48(homedir32(), ".openjaw-agent", "perf.log");
56795
57834
  logReady = false;
56796
57835
  writeRow = /* @__PURE__ */ __name((row) => {
56797
57836
  if (!logReady) {
56798
57837
  logReady = true;
56799
57838
  try {
56800
- mkdirSync20(dirname7(LOG_PATH2), { recursive: true });
57839
+ mkdirSync20(dirname9(LOG_PATH2), { recursive: true });
56801
57840
  } catch {
56802
57841
  }
56803
57842
  }
@@ -62055,7 +63094,7 @@ var init_mathUnicode = __esm({
62055
63094
 
62056
63095
  // src/lib/syntax.ts
62057
63096
  function highlightLine(line, lang, t) {
62058
- const spec = resolve5(lang);
63097
+ const spec = resolve6(lang);
62059
63098
  if (!spec) {
62060
63099
  return [["", line]];
62061
63100
  }
@@ -62087,7 +63126,7 @@ function highlightLine(line, lang, t) {
62087
63126
  }
62088
63127
  return tokens;
62089
63128
  }
62090
- var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve5, isHighlightable, TOKEN_RE;
63129
+ var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve6, isHighlightable, TOKEN_RE;
62091
63130
  var init_syntax = __esm({
62092
63131
  "src/lib/syntax.ts"() {
62093
63132
  "use strict";
@@ -62141,8 +63180,8 @@ var init_syntax = __esm({
62141
63180
  yml: "yaml",
62142
63181
  zsh: "sh"
62143
63182
  };
62144
- resolve5 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
62145
- isHighlightable = /* @__PURE__ */ __name((lang) => resolve5(lang) !== null, "isHighlightable");
63183
+ resolve6 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
63184
+ isHighlightable = /* @__PURE__ */ __name((lang) => resolve6(lang) !== null, "isHighlightable");
62146
63185
  TOKEN_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|\b\d+(?:\.\d+)?\b|[A-Za-z_$][\w$]*/g;
62147
63186
  __name(highlightLine, "highlightLine");
62148
63187
  }
@@ -62242,9 +63281,9 @@ function MdImpl({ compact, t, text }) {
62242
63281
  const nodes = useMemo14(() => {
62243
63282
  const bucket = cacheBucket(t);
62244
63283
  const cacheKey = `${compact ? "1" : "0"}|${text}`;
62245
- const cached7 = cacheGet(bucket, cacheKey);
62246
- if (cached7) {
62247
- return cached7;
63284
+ const cached8 = cacheGet(bucket, cacheKey);
63285
+ if (cached8) {
63286
+ return cached8;
62248
63287
  }
62249
63288
  const lines = ensureEmojiPresentation(text).split("\n");
62250
63289
  const nodes2 = [];
@@ -64477,12 +65516,18 @@ var init_entry = __esm({
64477
65516
  // src/main.ts
64478
65517
  async function main() {
64479
65518
  const args = process.argv.slice(2);
65519
+ if (args[0] === "app") {
65520
+ const { launchDesktop: launchDesktop2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
65521
+ await launchDesktop2(args.slice(1));
65522
+ return;
65523
+ }
64480
65524
  if (args.includes("--help") || args.includes("-h")) {
64481
65525
  console.log(`
64482
65526
  OpenJaw Agent \u2014 Autonomous desktop AI assistant
64483
65527
 
64484
65528
  Usage:
64485
65529
  openjaw-agent Start new session (Ink UI)
65530
+ openjaw-agent app Start the Electron desktop UI (optional)
64486
65531
  openjaw-agent --legacy-ui Start with the previous Ink UI (pre-rewrite)
64487
65532
  openjaw-agent --telegram Ink UI + Telegram bridge (hybrid)
64488
65533
  openjaw-agent --telegram --headless Telegram only (no desktop UI)