@charzhu/openjaw-agent 0.2.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/config.yaml +15 -0
- package/dist/main.js +2162 -548
- package/dist/main.js.map +4 -4
- package/package.json +1 -1
- package/prompts/REASONING.md +12 -3
- package/skills/create-docx.md +2 -0
- package/skills/create-pdf-report.md +2 -0
- package/skills/create-pptx.md +2 -0
- package/skills/daily-briefing.md +2 -0
- package/skills/data-analysis.md +2 -0
- package/skills/deep-research.md +4 -1
- package/skills/web-research.md +2 -1
package/dist/main.js
CHANGED
|
@@ -137,7 +137,11 @@ function loadAgentConfig() {
|
|
|
137
137
|
feishu: parsed?.feishu ?? void 0,
|
|
138
138
|
wechat: parsed?.wechat ?? void 0,
|
|
139
139
|
features: {
|
|
140
|
-
skill_auto_suggest: parsed?.features?.skill_auto_suggest ?? true
|
|
140
|
+
skill_auto_suggest: parsed?.features?.skill_auto_suggest ?? true,
|
|
141
|
+
dynamic_workflows: {
|
|
142
|
+
...DEFAULT_DYNAMIC_WORKFLOWS,
|
|
143
|
+
...parsed?.features?.dynamic_workflows ?? {}
|
|
144
|
+
}
|
|
141
145
|
}
|
|
142
146
|
};
|
|
143
147
|
if (config.llm.provider === "anthropic") {
|
|
@@ -199,12 +203,20 @@ function updateBridgeConfig(name, values) {
|
|
|
199
203
|
);
|
|
200
204
|
return next;
|
|
201
205
|
}
|
|
202
|
-
var DEFAULT_COPILOT_OAUTH_CLIENT_ID, DEFAULT_CONFIG, configWriteChain;
|
|
206
|
+
var DEFAULT_COPILOT_OAUTH_CLIENT_ID, DEFAULT_DYNAMIC_WORKFLOWS, DEFAULT_CONFIG, configWriteChain;
|
|
203
207
|
var init_config = __esm({
|
|
204
208
|
"src/config.ts"() {
|
|
205
209
|
"use strict";
|
|
206
210
|
init_packageRoot();
|
|
207
211
|
DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
212
|
+
DEFAULT_DYNAMIC_WORKFLOWS = {
|
|
213
|
+
enabled: true,
|
|
214
|
+
planner_mode: "adaptive",
|
|
215
|
+
hard_max_workers: 1024,
|
|
216
|
+
hard_max_concurrent_workers: 128,
|
|
217
|
+
worker_timeout_ms: 18e4,
|
|
218
|
+
persist_history: true
|
|
219
|
+
};
|
|
208
220
|
DEFAULT_CONFIG = {
|
|
209
221
|
llm: {
|
|
210
222
|
provider: "anthropic",
|
|
@@ -856,7 +868,7 @@ Start-Sleep -Milliseconds 50
|
|
|
856
868
|
}
|
|
857
869
|
}
|
|
858
870
|
async function wait(duration) {
|
|
859
|
-
await new Promise((
|
|
871
|
+
await new Promise((resolve6) => setTimeout(resolve6, duration * 1e3));
|
|
860
872
|
return { output: `Waited ${duration} seconds` };
|
|
861
873
|
}
|
|
862
874
|
function getDisplayDimensions() {
|
|
@@ -2393,9 +2405,6 @@ var init_copilot = __esm({
|
|
|
2393
2405
|
}
|
|
2394
2406
|
async chat(options) {
|
|
2395
2407
|
const modelInfo = await this.resolveModelInfo(this.config.model);
|
|
2396
|
-
if (hasImageContent(options.messages) && modelInfo?.supportsVision === false) {
|
|
2397
|
-
throw new Error(`model ${this.config.model} does not support image input; switch to a vision-capable model before submitting an image`);
|
|
2398
|
-
}
|
|
2399
2408
|
if (this.shouldRouteToResponses(modelInfo)) {
|
|
2400
2409
|
return this.chatResponses(options, modelInfo);
|
|
2401
2410
|
}
|
|
@@ -2554,7 +2563,7 @@ var init_copilot = __esm({
|
|
|
2554
2563
|
handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
|
|
2555
2564
|
});
|
|
2556
2565
|
const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
|
|
2557
|
-
return new Promise((
|
|
2566
|
+
return new Promise((resolve6, reject) => {
|
|
2558
2567
|
const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
|
|
2559
2568
|
let settled = false;
|
|
2560
2569
|
const timeout = setTimeout(() => {
|
|
@@ -2568,7 +2577,7 @@ var init_copilot = __esm({
|
|
|
2568
2577
|
settled = true;
|
|
2569
2578
|
clearTimeout(timeout);
|
|
2570
2579
|
ws.close();
|
|
2571
|
-
|
|
2580
|
+
resolve6(value);
|
|
2572
2581
|
}, "finish");
|
|
2573
2582
|
const fail = /* @__PURE__ */ __name((error) => {
|
|
2574
2583
|
if (settled) return;
|
|
@@ -4008,7 +4017,7 @@ function categoryForTool(toolName) {
|
|
|
4008
4017
|
if (toolName.startsWith("word_") || toolName.startsWith("excel_") || toolName.startsWith("powerpoint_") || toolName.startsWith("office_")) return "office";
|
|
4009
4018
|
if (toolName.startsWith("memory_") || toolName === "todo_write") return "memory";
|
|
4010
4019
|
if (toolName.startsWith("file_") || toolName.startsWith("image_") || toolName === "grep" || toolName === "glob") return "files";
|
|
4011
|
-
if (toolName.startsWith("system_") || toolName.startsWith("clipboard_") || ["code_execute", "web_fetch", "web_search", "notify", "sleep", "ask_user", "config"].includes(toolName)) return "system";
|
|
4020
|
+
if (toolName.startsWith("system_") || toolName.startsWith("clipboard_") || ["code_execute", "web_fetch", "web_search", "web_extract", "notify", "sleep", "ask_user", "config"].includes(toolName)) return "system";
|
|
4012
4021
|
return "mcp";
|
|
4013
4022
|
}
|
|
4014
4023
|
var DEFAULT_OPENAI_MAX_TOOLS, MCP_AUTO_GROW_HARD_CAP, BUILTIN_HEADROOM, FOUNDATION_TOOL_NAMES, PROFILE_CATEGORIES, CATEGORY_KEYWORDS;
|
|
@@ -4034,9 +4043,9 @@ var init_tool_exposure = __esm({
|
|
|
4034
4043
|
CATEGORY_KEYWORDS = [
|
|
4035
4044
|
{ category: "email", patterns: [/\b(email|mail|outlook|inbox|calendar|schedule|meeting|invite|today|tomorrow)\b/i] },
|
|
4036
4045
|
{ category: "teams", patterns: [/\b(teams|chat|channel|message|dm|meeting|standup|today|mention)\b/i] },
|
|
4037
|
-
{ category: "browser", patterns: [/\b(browser|page|website|web|navigate|click|screenshot|search online)\b/i] },
|
|
4046
|
+
{ category: "browser", patterns: [/\b(browser|page|website|web|navigate|click|screenshot|snapshot|console|image|search online)\b/i] },
|
|
4038
4047
|
{ category: "files", patterns: [/\b(file|folder|directory|read|write|edit|grep|glob|find in repo|codebase)\b/i] },
|
|
4039
|
-
{ category: "system", patterns: [/\b(shell|command|terminal|run|execute|clipboard|notify|sleep|web search|fetch url)\b/i] },
|
|
4048
|
+
{ 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] },
|
|
4040
4049
|
{ category: "office", patterns: [/\b(word|excel|powerpoint|spreadsheet|document|presentation|slide)\b/i] },
|
|
4041
4050
|
{ category: "wechat", patterns: [/\b(wechat|weixin)\b/i] },
|
|
4042
4051
|
{ category: "memory", patterns: [/\b(memory|remember|recall|todo|preference)\b/i] }
|
|
@@ -5094,9 +5103,7 @@ ${summary}
|
|
|
5094
5103
|
if (imageData) {
|
|
5095
5104
|
const contentBlocks = [
|
|
5096
5105
|
{ type: "image", source: { type: "base64", media_type: imageData.mimeType, data: imageData.base64 } },
|
|
5097
|
-
{ type: "text", text:
|
|
5098
|
-
|
|
5099
|
-
[ATTACHED IMAGE]: The image is attached as visual input in this message. Inspect it directly; do not search the filesystem for an uploaded image path unless the user explicitly asks for a file.` }
|
|
5106
|
+
{ type: "text", text: taskText }
|
|
5100
5107
|
];
|
|
5101
5108
|
this.conversationHistory.push({ role: "user", content: contentBlocks });
|
|
5102
5109
|
} else {
|
|
@@ -5477,18 +5484,18 @@ ${summary}
|
|
|
5477
5484
|
content: parsed.question,
|
|
5478
5485
|
choices: parsed.choices ?? void 0
|
|
5479
5486
|
};
|
|
5480
|
-
const userResponse = await new Promise((
|
|
5487
|
+
const userResponse = await new Promise((resolve6) => {
|
|
5481
5488
|
if (this._pendingAskUserResponse !== null) {
|
|
5482
5489
|
const buffered = this._pendingAskUserResponse;
|
|
5483
5490
|
this._pendingAskUserResponse = null;
|
|
5484
|
-
|
|
5491
|
+
resolve6(buffered);
|
|
5485
5492
|
return;
|
|
5486
5493
|
}
|
|
5487
|
-
this._askUserResolver =
|
|
5494
|
+
this._askUserResolver = resolve6;
|
|
5488
5495
|
setTimeout(() => {
|
|
5489
|
-
if (this._askUserResolver ===
|
|
5496
|
+
if (this._askUserResolver === resolve6) {
|
|
5490
5497
|
this._askUserResolver = null;
|
|
5491
|
-
|
|
5498
|
+
resolve6("[No response from user \u2014 timed out after 5 minutes]");
|
|
5492
5499
|
}
|
|
5493
5500
|
}, 5 * 60 * 1e3);
|
|
5494
5501
|
});
|
|
@@ -5838,18 +5845,135 @@ var init_logger = __esm({
|
|
|
5838
5845
|
}
|
|
5839
5846
|
});
|
|
5840
5847
|
|
|
5848
|
+
// ../openjaw-mcp/dist/tools/url-safety.js
|
|
5849
|
+
import { isIP } from "node:net";
|
|
5850
|
+
function hasTokenLikeSecret(value) {
|
|
5851
|
+
const decoded = safeDecode(value);
|
|
5852
|
+
return TOKEN_PATTERNS.some((pattern) => pattern.test(value) || pattern.test(decoded));
|
|
5853
|
+
}
|
|
5854
|
+
function safeDecode(value) {
|
|
5855
|
+
try {
|
|
5856
|
+
return decodeURIComponent(value);
|
|
5857
|
+
} catch {
|
|
5858
|
+
return value;
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5861
|
+
function normalizeHostname(hostname) {
|
|
5862
|
+
return hostname.replace(/^\[|\]$/g, "").replace(/\.+$/g, "").toLowerCase();
|
|
5863
|
+
}
|
|
5864
|
+
function isPrivateIPv4(host) {
|
|
5865
|
+
const parts = host.split(".").map((part) => Number(part));
|
|
5866
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255))
|
|
5867
|
+
return false;
|
|
5868
|
+
const [a, b] = parts;
|
|
5869
|
+
return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 127 || a === 169 && b === 254 || a === 0;
|
|
5870
|
+
}
|
|
5871
|
+
function ipv4FromMappedIPv6(host) {
|
|
5872
|
+
const lower = host.toLowerCase();
|
|
5873
|
+
const dotted = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
|
|
5874
|
+
if (dotted)
|
|
5875
|
+
return dotted[1];
|
|
5876
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(lower);
|
|
5877
|
+
if (!hex)
|
|
5878
|
+
return void 0;
|
|
5879
|
+
const high = Number.parseInt(hex[1], 16);
|
|
5880
|
+
const low = Number.parseInt(hex[2], 16);
|
|
5881
|
+
if (!Number.isFinite(high) || !Number.isFinite(low))
|
|
5882
|
+
return void 0;
|
|
5883
|
+
return [high >> 8, high & 255, low >> 8, low & 255].join(".");
|
|
5884
|
+
}
|
|
5885
|
+
function isPrivateIPv6(host) {
|
|
5886
|
+
const lower = host.toLowerCase();
|
|
5887
|
+
const mapped = ipv4FromMappedIPv6(lower);
|
|
5888
|
+
if (mapped)
|
|
5889
|
+
return isPrivateIPv4(mapped);
|
|
5890
|
+
return lower === "::1" || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80:");
|
|
5891
|
+
}
|
|
5892
|
+
function isMetadataUrl(input) {
|
|
5893
|
+
const url = typeof input === "string" ? new URL(input) : input;
|
|
5894
|
+
const host = normalizeHostname(url.hostname);
|
|
5895
|
+
const mapped = ipv4FromMappedIPv6(host);
|
|
5896
|
+
return METADATA_HOSTS.has(host) || METADATA_IPS.has(host) || (mapped ? METADATA_IPS.has(mapped) : false);
|
|
5897
|
+
}
|
|
5898
|
+
function isPrivateHost(hostname) {
|
|
5899
|
+
const host = normalizeHostname(hostname);
|
|
5900
|
+
if (host === "localhost" || host.endsWith(".localhost"))
|
|
5901
|
+
return true;
|
|
5902
|
+
const ipKind = isIP(host);
|
|
5903
|
+
if (ipKind === 4)
|
|
5904
|
+
return isPrivateIPv4(host);
|
|
5905
|
+
if (ipKind === 6)
|
|
5906
|
+
return isPrivateIPv6(host);
|
|
5907
|
+
return false;
|
|
5908
|
+
}
|
|
5909
|
+
function validateHttpUrl(input, options = {}) {
|
|
5910
|
+
let url;
|
|
5911
|
+
try {
|
|
5912
|
+
url = new URL(input);
|
|
5913
|
+
} catch {
|
|
5914
|
+
return { ok: false, error: "Invalid URL" };
|
|
5915
|
+
}
|
|
5916
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
5917
|
+
return { ok: false, error: "Only http:// and https:// URLs are supported" };
|
|
5918
|
+
}
|
|
5919
|
+
if (url.username || url.password) {
|
|
5920
|
+
return { ok: false, error: "Blocked: URL contains embedded credentials" };
|
|
5921
|
+
}
|
|
5922
|
+
if (hasTokenLikeSecret(url.href)) {
|
|
5923
|
+
return { ok: false, error: "Blocked: URL contains what appears to be an API key, token, password, or secret" };
|
|
5924
|
+
}
|
|
5925
|
+
if (isMetadataUrl(url)) {
|
|
5926
|
+
return { ok: false, error: "Blocked: URL targets a cloud metadata endpoint" };
|
|
5927
|
+
}
|
|
5928
|
+
if (!options.allowPrivate && isPrivateHost(url.hostname)) {
|
|
5929
|
+
return { ok: false, error: "Blocked: URL targets a private, loopback, or internal network address" };
|
|
5930
|
+
}
|
|
5931
|
+
return { ok: true, url };
|
|
5932
|
+
}
|
|
5933
|
+
var TOKEN_PATTERNS, METADATA_HOSTS, METADATA_IPS;
|
|
5934
|
+
var init_url_safety = __esm({
|
|
5935
|
+
"../openjaw-mcp/dist/tools/url-safety.js"() {
|
|
5936
|
+
"use strict";
|
|
5937
|
+
TOKEN_PATTERNS = [
|
|
5938
|
+
/\b(?:sk|ghp|github_pat|gho|ghu|ghs|glpat|xox[baprs]|ya29|AIza)[A-Za-z0-9_\-]{12,}\b/i,
|
|
5939
|
+
/(?:api[_-]?key|access[_-]?token|auth[_-]?token|bearer|secret|password|passwd|pwd|token)=([^&\s]{8,})/i
|
|
5940
|
+
];
|
|
5941
|
+
METADATA_HOSTS = /* @__PURE__ */ new Set([
|
|
5942
|
+
"metadata.google.internal",
|
|
5943
|
+
"metadata.azure.internal"
|
|
5944
|
+
]);
|
|
5945
|
+
METADATA_IPS = /* @__PURE__ */ new Set([
|
|
5946
|
+
"169.254.169.254",
|
|
5947
|
+
"100.100.100.200"
|
|
5948
|
+
]);
|
|
5949
|
+
__name(hasTokenLikeSecret, "hasTokenLikeSecret");
|
|
5950
|
+
__name(safeDecode, "safeDecode");
|
|
5951
|
+
__name(normalizeHostname, "normalizeHostname");
|
|
5952
|
+
__name(isPrivateIPv4, "isPrivateIPv4");
|
|
5953
|
+
__name(ipv4FromMappedIPv6, "ipv4FromMappedIPv6");
|
|
5954
|
+
__name(isPrivateIPv6, "isPrivateIPv6");
|
|
5955
|
+
__name(isMetadataUrl, "isMetadataUrl");
|
|
5956
|
+
__name(isPrivateHost, "isPrivateHost");
|
|
5957
|
+
__name(validateHttpUrl, "validateHttpUrl");
|
|
5958
|
+
}
|
|
5959
|
+
});
|
|
5960
|
+
|
|
5841
5961
|
// ../openjaw-mcp/dist/channels/browser.js
|
|
5842
5962
|
import * as chromeLauncher from "chrome-launcher";
|
|
5843
5963
|
import CDP from "chrome-remote-interface";
|
|
5844
5964
|
function escapeForJs(str) {
|
|
5845
5965
|
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\0/g, "\\0").replace(/</g, "\\x3c").replace(/>/g, "\\x3e");
|
|
5846
5966
|
}
|
|
5847
|
-
var BrowserChannel;
|
|
5967
|
+
var MAX_CONSOLE_MESSAGES, MAX_JS_ERRORS, MAX_CONSOLE_TEXT, BrowserChannel;
|
|
5848
5968
|
var init_browser = __esm({
|
|
5849
5969
|
"../openjaw-mcp/dist/channels/browser.js"() {
|
|
5850
5970
|
"use strict";
|
|
5851
5971
|
init_logger();
|
|
5972
|
+
init_url_safety();
|
|
5852
5973
|
__name(escapeForJs, "escapeForJs");
|
|
5974
|
+
MAX_CONSOLE_MESSAGES = 200;
|
|
5975
|
+
MAX_JS_ERRORS = 100;
|
|
5976
|
+
MAX_CONSOLE_TEXT = 4e3;
|
|
5853
5977
|
BrowserChannel = class {
|
|
5854
5978
|
static {
|
|
5855
5979
|
__name(this, "BrowserChannel");
|
|
@@ -5859,6 +5983,10 @@ var init_browser = __esm({
|
|
|
5859
5983
|
client = null;
|
|
5860
5984
|
page = null;
|
|
5861
5985
|
launchPromise = null;
|
|
5986
|
+
currentSnapshotId = null;
|
|
5987
|
+
currentRefs = /* @__PURE__ */ new Map();
|
|
5988
|
+
consoleBuffer = [];
|
|
5989
|
+
jsErrorBuffer = [];
|
|
5862
5990
|
constructor(config) {
|
|
5863
5991
|
this.config = config;
|
|
5864
5992
|
}
|
|
@@ -5905,6 +6033,8 @@ var init_browser = __esm({
|
|
|
5905
6033
|
this.client = null;
|
|
5906
6034
|
this.page = null;
|
|
5907
6035
|
this.launchPromise = null;
|
|
6036
|
+
this.consoleBuffer = [];
|
|
6037
|
+
this.jsErrorBuffer = [];
|
|
5908
6038
|
logger_default.info("Browser connection state reset");
|
|
5909
6039
|
}
|
|
5910
6040
|
/**
|
|
@@ -5957,33 +6087,99 @@ var init_browser = __esm({
|
|
|
5957
6087
|
DOM.enable(),
|
|
5958
6088
|
Network.enable()
|
|
5959
6089
|
]);
|
|
6090
|
+
this.attachRuntimeEventBuffers(Runtime);
|
|
6091
|
+
await this.attachRequestSafetyInterception(this.client.Fetch);
|
|
5960
6092
|
this.page = { Runtime, Page, DOM, Input };
|
|
5961
6093
|
logger_default.info("Browser launched", { port: this.chrome.port });
|
|
5962
6094
|
}
|
|
6095
|
+
async attachRequestSafetyInterception(Fetch) {
|
|
6096
|
+
const fetchDomain = Fetch;
|
|
6097
|
+
if (!fetchDomain?.enable || !fetchDomain.requestPaused || !fetchDomain.failRequest || !fetchDomain.continueRequest) {
|
|
6098
|
+
logger_default.debug("Browser Fetch domain unavailable; navigation safety falls back to pre/final URL checks");
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6101
|
+
fetchDomain.requestPaused((event) => {
|
|
6102
|
+
void (async () => {
|
|
6103
|
+
try {
|
|
6104
|
+
const url = new URL(event.request.url);
|
|
6105
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
6106
|
+
const safety = validateHttpUrl(url.href, { allowPrivate: true });
|
|
6107
|
+
if (!safety.ok) {
|
|
6108
|
+
await fetchDomain.failRequest({ requestId: event.requestId, errorReason: "BlockedByClient" });
|
|
6109
|
+
return;
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
await fetchDomain.continueRequest({ requestId: event.requestId });
|
|
6113
|
+
} catch {
|
|
6114
|
+
await fetchDomain.continueRequest({ requestId: event.requestId }).catch(() => void 0);
|
|
6115
|
+
}
|
|
6116
|
+
})();
|
|
6117
|
+
});
|
|
6118
|
+
await fetchDomain.enable({ patterns: [{ urlPattern: "*", requestStage: "Request" }] });
|
|
6119
|
+
}
|
|
6120
|
+
attachRuntimeEventBuffers(Runtime) {
|
|
6121
|
+
const runtime = Runtime;
|
|
6122
|
+
runtime.consoleAPICalled?.((event) => {
|
|
6123
|
+
const text = (event.args ?? []).map((arg) => String(arg.value ?? arg.description ?? "")).join(" ").slice(0, MAX_CONSOLE_TEXT);
|
|
6124
|
+
this.consoleBuffer.push({
|
|
6125
|
+
type: event.type ?? "log",
|
|
6126
|
+
text,
|
|
6127
|
+
timestamp: event.timestamp ?? Date.now()
|
|
6128
|
+
});
|
|
6129
|
+
if (this.consoleBuffer.length > MAX_CONSOLE_MESSAGES) {
|
|
6130
|
+
this.consoleBuffer.splice(0, this.consoleBuffer.length - MAX_CONSOLE_MESSAGES);
|
|
6131
|
+
}
|
|
6132
|
+
});
|
|
6133
|
+
runtime.exceptionThrown?.((event) => {
|
|
6134
|
+
const details = event.exceptionDetails ?? {};
|
|
6135
|
+
this.jsErrorBuffer.push({
|
|
6136
|
+
message: String(details.exception?.description ?? details.exception?.value ?? details.text ?? "JavaScript exception").slice(0, MAX_CONSOLE_TEXT),
|
|
6137
|
+
url: details.url,
|
|
6138
|
+
line: details.lineNumber,
|
|
6139
|
+
column: details.columnNumber,
|
|
6140
|
+
timestamp: event.timestamp ?? Date.now()
|
|
6141
|
+
});
|
|
6142
|
+
if (this.jsErrorBuffer.length > MAX_JS_ERRORS) {
|
|
6143
|
+
this.jsErrorBuffer.splice(0, this.jsErrorBuffer.length - MAX_JS_ERRORS);
|
|
6144
|
+
}
|
|
6145
|
+
});
|
|
6146
|
+
}
|
|
5963
6147
|
async navigate(options) {
|
|
5964
6148
|
await this.ensureBrowser();
|
|
5965
6149
|
const { Page, Runtime } = this.page;
|
|
5966
|
-
|
|
6150
|
+
const initialSafety = validateHttpUrl(options.url, { allowPrivate: true });
|
|
6151
|
+
if (!initialSafety.ok) {
|
|
6152
|
+
return { url: options.url, title: "", snapshot: "", error: initialSafety.error };
|
|
6153
|
+
}
|
|
6154
|
+
await Page.navigate({ url: initialSafety.url.href });
|
|
5967
6155
|
if (options.waitFor === "load") {
|
|
5968
6156
|
await Page.loadEventFired();
|
|
5969
6157
|
} else if (options.waitFor === "domcontentloaded") {
|
|
5970
6158
|
await Page.domContentEventFired();
|
|
5971
6159
|
} else if (options.waitFor === "networkidle") {
|
|
5972
6160
|
await Page.loadEventFired();
|
|
5973
|
-
await new Promise((
|
|
6161
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
5974
6162
|
}
|
|
5975
6163
|
const result = await Runtime.evaluate({
|
|
5976
6164
|
expression: "document.title"
|
|
5977
6165
|
});
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
6166
|
+
const url = await this.getCurrentUrl() ?? initialSafety.url.href;
|
|
6167
|
+
const finalSafety = validateHttpUrl(url, { allowPrivate: true });
|
|
6168
|
+
if (!finalSafety.ok) {
|
|
6169
|
+
return { url, title: result.result.value, snapshot: "", error: `Blocked final URL: ${finalSafety.error}` };
|
|
6170
|
+
}
|
|
6171
|
+
const title = result.result.value;
|
|
6172
|
+
const snapshot = await this.snapshot({ full: false });
|
|
6173
|
+
return { ...snapshot, url, title };
|
|
5982
6174
|
}
|
|
5983
6175
|
async click(options) {
|
|
5984
6176
|
await this.ensureBrowser();
|
|
5985
6177
|
const { Runtime } = this.page;
|
|
5986
|
-
const
|
|
6178
|
+
const resolvedSelector = options.ref ? this.resolveRef(options.ref, options.snapshotId) : options.selector;
|
|
6179
|
+
if (options.ref && !resolvedSelector) {
|
|
6180
|
+
return { success: false, element: `stale or unknown ref ${options.ref}; call browser_snapshot again` };
|
|
6181
|
+
}
|
|
6182
|
+
const findExpr = resolvedSelector ? `document.querySelector('${escapeForJs(resolvedSelector)}')` : options.text ? `(() => {
|
|
5987
6183
|
const isVis = (e) => {
|
|
5988
6184
|
if (!e.offsetParent && e.tagName !== 'BODY' && e.tagName !== 'HTML') return false;
|
|
5989
6185
|
const s = getComputedStyle(e);
|
|
@@ -6107,9 +6303,13 @@ var init_browser = __esm({
|
|
|
6107
6303
|
async type(options) {
|
|
6108
6304
|
await this.ensureBrowser();
|
|
6109
6305
|
const { Runtime } = this.page;
|
|
6306
|
+
const resolvedSelector = options.ref ? this.resolveRef(options.ref, options.snapshotId) : options.selector;
|
|
6307
|
+
if (!resolvedSelector) {
|
|
6308
|
+
return { success: false, error: options.ref ? `stale or unknown ref ${options.ref}; call browser_snapshot again` : "No selector or ref provided" };
|
|
6309
|
+
}
|
|
6110
6310
|
const script = `
|
|
6111
6311
|
(() => {
|
|
6112
|
-
const el = document.querySelector('${escapeForJs(
|
|
6312
|
+
const el = document.querySelector('${escapeForJs(resolvedSelector)}');
|
|
6113
6313
|
if (el) {
|
|
6114
6314
|
${options.clear ? "el.value = '';" : ""}
|
|
6115
6315
|
el.value = '${escapeForJs(options.text)}';
|
|
@@ -6191,6 +6391,141 @@ var init_browser = __esm({
|
|
|
6191
6391
|
const result = await Runtime.evaluate({ expression: script, returnByValue: true });
|
|
6192
6392
|
return { content: result.result.value };
|
|
6193
6393
|
}
|
|
6394
|
+
async snapshot(options) {
|
|
6395
|
+
await this.ensureBrowser();
|
|
6396
|
+
const { Runtime } = this.page;
|
|
6397
|
+
const scope = options?.selector ? `'${escapeForJs(options.selector)}'` : `'body'`;
|
|
6398
|
+
const maxElements = Math.min(Math.max(options?.maxElements ?? 80, 1), 200);
|
|
6399
|
+
const textLimit = options?.full ? 8e3 : 1200;
|
|
6400
|
+
const script = `(() => {
|
|
6401
|
+
const root = document.querySelector(${scope});
|
|
6402
|
+
const cssEscape = (value) => globalThis.CSS?.escape
|
|
6403
|
+
? globalThis.CSS.escape(String(value))
|
|
6404
|
+
: String(value).replace(/[^a-zA-Z0-9_-]/g, ch => '\\\\' + ch.charCodeAt(0).toString(16) + ' ');
|
|
6405
|
+
const selectorFor = (el) => {
|
|
6406
|
+
if (el.id) return '#' + cssEscape(el.id);
|
|
6407
|
+
const parts = [];
|
|
6408
|
+
let cur = el;
|
|
6409
|
+
while (cur && cur.nodeType === 1 && cur !== document.body) {
|
|
6410
|
+
let part = cur.tagName.toLowerCase();
|
|
6411
|
+
const parent = cur.parentElement;
|
|
6412
|
+
if (!parent) break;
|
|
6413
|
+
const same = Array.from(parent.children).filter(child => child.tagName === cur.tagName);
|
|
6414
|
+
if (same.length > 1) part += ':nth-of-type(' + (same.indexOf(cur) + 1) + ')';
|
|
6415
|
+
parts.unshift(part);
|
|
6416
|
+
cur = parent;
|
|
6417
|
+
}
|
|
6418
|
+
return parts.length ? parts.join(' > ') : 'body';
|
|
6419
|
+
};
|
|
6420
|
+
const isVisible = (el) => {
|
|
6421
|
+
const rect = el.getBoundingClientRect();
|
|
6422
|
+
const style = getComputedStyle(el);
|
|
6423
|
+
return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
6424
|
+
};
|
|
6425
|
+
const disabled = (el) => Boolean(el.disabled || el.getAttribute('aria-disabled') === 'true');
|
|
6426
|
+
if (!root) return JSON.stringify({ url: location.href, title: document.title, elements: [], text: '' });
|
|
6427
|
+
const nodes = Array.from(root.querySelectorAll('button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="textbox"], [onclick], [tabindex]'));
|
|
6428
|
+
const elements = [];
|
|
6429
|
+
for (const el of nodes) {
|
|
6430
|
+
if (elements.length >= ${maxElements}) break;
|
|
6431
|
+
const tag = el.tagName.toLowerCase();
|
|
6432
|
+
const type = el.getAttribute('type') || '';
|
|
6433
|
+
const role = el.getAttribute('role') || (type ? tag + '[' + type + ']' : tag);
|
|
6434
|
+
const text = (el.innerText || el.textContent || '').replace(/s+/g, ' ').trim().slice(0, 120);
|
|
6435
|
+
const label = (el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('placeholder') || text || el.getAttribute('value') || '').replace(/s+/g, ' ').trim().slice(0, 120);
|
|
6436
|
+
elements.push({
|
|
6437
|
+
ref: '@e' + (elements.length + 1),
|
|
6438
|
+
selector: selectorFor(el),
|
|
6439
|
+
role,
|
|
6440
|
+
tag,
|
|
6441
|
+
label,
|
|
6442
|
+
text,
|
|
6443
|
+
visible: isVisible(el),
|
|
6444
|
+
disabled: disabled(el),
|
|
6445
|
+
});
|
|
6446
|
+
}
|
|
6447
|
+
const pageText = (root.innerText || root.textContent || '').replace(/s+/g, ' ').trim().slice(0, ${textLimit});
|
|
6448
|
+
return JSON.stringify({ url: location.href, title: document.title, elements, text: pageText });
|
|
6449
|
+
})()`;
|
|
6450
|
+
const result = await Runtime.evaluate({ expression: script, returnByValue: true });
|
|
6451
|
+
const parsed = JSON.parse(result.result.value || "{}");
|
|
6452
|
+
const snapshotId = `snap_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
6453
|
+
const elements = parsed.elements ?? [];
|
|
6454
|
+
const refs = {};
|
|
6455
|
+
this.currentRefs.clear();
|
|
6456
|
+
for (const element of elements) {
|
|
6457
|
+
refs[element.ref] = element.selector;
|
|
6458
|
+
this.currentRefs.set(element.ref, element.selector);
|
|
6459
|
+
}
|
|
6460
|
+
this.currentSnapshotId = snapshotId;
|
|
6461
|
+
const lines = [
|
|
6462
|
+
`URL: ${parsed.url ?? ""}`,
|
|
6463
|
+
`Title: ${parsed.title ?? ""}`,
|
|
6464
|
+
`Snapshot: ${snapshotId}`,
|
|
6465
|
+
"",
|
|
6466
|
+
`Interactive elements (${elements.length}):`,
|
|
6467
|
+
...elements.map((element) => `${element.ref} <${element.role}>${element.visible ? "" : " [hidden]"}${element.disabled ? " [disabled]" : ""} ${JSON.stringify(element.label || element.text || element.selector)}`),
|
|
6468
|
+
"",
|
|
6469
|
+
"Visible text:",
|
|
6470
|
+
parsed.text ?? ""
|
|
6471
|
+
];
|
|
6472
|
+
return {
|
|
6473
|
+
url: parsed.url ?? "",
|
|
6474
|
+
title: parsed.title ?? "",
|
|
6475
|
+
snapshot: lines.join("\n").trim(),
|
|
6476
|
+
snapshot_id: snapshotId,
|
|
6477
|
+
refs,
|
|
6478
|
+
elements,
|
|
6479
|
+
text: parsed.text ?? "",
|
|
6480
|
+
element_count: elements.length
|
|
6481
|
+
};
|
|
6482
|
+
}
|
|
6483
|
+
resolveRef(ref, snapshotId) {
|
|
6484
|
+
if (snapshotId && this.currentSnapshotId && snapshotId !== this.currentSnapshotId)
|
|
6485
|
+
return void 0;
|
|
6486
|
+
return this.currentRefs.get(ref);
|
|
6487
|
+
}
|
|
6488
|
+
async back() {
|
|
6489
|
+
await this.ensureBrowser();
|
|
6490
|
+
const { Page, Runtime } = this.page;
|
|
6491
|
+
const history = await Page.getNavigationHistory();
|
|
6492
|
+
if (history.currentIndex > 0) {
|
|
6493
|
+
await Page.navigateToHistoryEntry({ entryId: history.entries[history.currentIndex - 1].id });
|
|
6494
|
+
await Page.loadEventFired().catch(() => void 0);
|
|
6495
|
+
}
|
|
6496
|
+
const title = await Runtime.evaluate({ expression: "document.title", returnByValue: true });
|
|
6497
|
+
return { url: await this.getCurrentUrl(), title: title.result.value };
|
|
6498
|
+
}
|
|
6499
|
+
async press(key) {
|
|
6500
|
+
await this.sendKey(key);
|
|
6501
|
+
return { success: true, key };
|
|
6502
|
+
}
|
|
6503
|
+
async getImages() {
|
|
6504
|
+
await this.ensureBrowser();
|
|
6505
|
+
const { Runtime } = this.page;
|
|
6506
|
+
const script = `JSON.stringify(Array.from(document.images).slice(0, 100).map(img => {
|
|
6507
|
+
const rect = img.getBoundingClientRect();
|
|
6508
|
+
return { src: img.currentSrc || img.src, alt: img.alt || '', width: img.naturalWidth || rect.width || 0, height: img.naturalHeight || rect.height || 0, visible: rect.width > 0 && rect.height > 0 };
|
|
6509
|
+
}).filter(img => img.src && !img.src.startsWith('data:')))`;
|
|
6510
|
+
const result = await Runtime.evaluate({ expression: script, returnByValue: true });
|
|
6511
|
+
const images = JSON.parse(result.result.value || "[]");
|
|
6512
|
+
return { images, count: images.length };
|
|
6513
|
+
}
|
|
6514
|
+
async consoleMessages(clear2 = false) {
|
|
6515
|
+
const consoleMessages = [...this.consoleBuffer];
|
|
6516
|
+
const jsErrors = [...this.jsErrorBuffer];
|
|
6517
|
+
if (clear2) {
|
|
6518
|
+
this.consoleBuffer = [];
|
|
6519
|
+
this.jsErrorBuffer = [];
|
|
6520
|
+
}
|
|
6521
|
+
return {
|
|
6522
|
+
console_messages: consoleMessages,
|
|
6523
|
+
js_errors: jsErrors,
|
|
6524
|
+
total_messages: consoleMessages.length,
|
|
6525
|
+
total_errors: jsErrors.length,
|
|
6526
|
+
note: clear2 ? "Returned and cleared buffered console messages." : "Returned buffered console messages."
|
|
6527
|
+
};
|
|
6528
|
+
}
|
|
6194
6529
|
async evaluate(script) {
|
|
6195
6530
|
await this.ensureBrowser();
|
|
6196
6531
|
const { Runtime } = this.page;
|
|
@@ -6594,7 +6929,7 @@ var init_browser = __esm({
|
|
|
6594
6929
|
if (exists) {
|
|
6595
6930
|
return true;
|
|
6596
6931
|
}
|
|
6597
|
-
await new Promise((
|
|
6932
|
+
await new Promise((resolve6) => setTimeout(resolve6, 200));
|
|
6598
6933
|
}
|
|
6599
6934
|
return false;
|
|
6600
6935
|
}
|
|
@@ -6625,18 +6960,48 @@ function createBrowseTools(config, sharedBrowser) {
|
|
|
6625
6960
|
required: ["url"]
|
|
6626
6961
|
},
|
|
6627
6962
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
6963
|
+
const safety = validateHttpUrl(input.url, { allowPrivate: true });
|
|
6964
|
+
if (!safety.ok)
|
|
6965
|
+
return { error: safety.error, url: input.url };
|
|
6628
6966
|
return await browser.navigate({
|
|
6629
6967
|
url: input.url,
|
|
6630
6968
|
waitFor: input.wait_for ?? "load"
|
|
6631
6969
|
});
|
|
6632
6970
|
}, "execute")
|
|
6633
6971
|
},
|
|
6972
|
+
{
|
|
6973
|
+
name: "browser_snapshot",
|
|
6974
|
+
description: "Get a compact snapshot of the current page with stable refs like @e1 for browser_click and browser_type. Use after navigation or after interactions that changed the page.",
|
|
6975
|
+
parameters: {
|
|
6976
|
+
type: "object",
|
|
6977
|
+
properties: {
|
|
6978
|
+
full: { type: "boolean", description: "Include more visible page text (default false)", default: false },
|
|
6979
|
+
selector: { type: "string", description: "Optional CSS selector to scope the snapshot" },
|
|
6980
|
+
max_elements: { type: "number", description: "Maximum interactive elements to include (default 80, max 200)" }
|
|
6981
|
+
}
|
|
6982
|
+
},
|
|
6983
|
+
execute: /* @__PURE__ */ __name(async (input) => {
|
|
6984
|
+
return await browser.snapshot({
|
|
6985
|
+
full: input.full === true,
|
|
6986
|
+
selector: input.selector,
|
|
6987
|
+
maxElements: input.max_elements
|
|
6988
|
+
});
|
|
6989
|
+
}, "execute")
|
|
6990
|
+
},
|
|
6634
6991
|
{
|
|
6635
6992
|
name: "browser_click",
|
|
6636
|
-
description: "Click an element on the page",
|
|
6993
|
+
description: "Click an element on the page. Prefer ref from browser_snapshot (e.g. @e5). Legacy selector/text targeting is still supported.",
|
|
6637
6994
|
parameters: {
|
|
6638
6995
|
type: "object",
|
|
6639
6996
|
properties: {
|
|
6997
|
+
ref: {
|
|
6998
|
+
type: "string",
|
|
6999
|
+
description: "Element ref from browser_snapshot, e.g. @e5"
|
|
7000
|
+
},
|
|
7001
|
+
snapshot_id: {
|
|
7002
|
+
type: "string",
|
|
7003
|
+
description: "Optional snapshot_id associated with the ref. If stale, refresh with browser_snapshot."
|
|
7004
|
+
},
|
|
6640
7005
|
selector: {
|
|
6641
7006
|
type: "string",
|
|
6642
7007
|
description: "CSS selector of element to click"
|
|
@@ -6649,6 +7014,8 @@ function createBrowseTools(config, sharedBrowser) {
|
|
|
6649
7014
|
},
|
|
6650
7015
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
6651
7016
|
return await browser.click({
|
|
7017
|
+
ref: input.ref,
|
|
7018
|
+
snapshotId: input.snapshot_id,
|
|
6652
7019
|
selector: input.selector,
|
|
6653
7020
|
text: input.text
|
|
6654
7021
|
});
|
|
@@ -6656,10 +7023,18 @@ function createBrowseTools(config, sharedBrowser) {
|
|
|
6656
7023
|
},
|
|
6657
7024
|
{
|
|
6658
7025
|
name: "browser_type",
|
|
6659
|
-
description: "Type text into an input element",
|
|
7026
|
+
description: "Type text into an input element. Prefer ref from browser_snapshot (e.g. @e3). Legacy selector targeting is still supported.",
|
|
6660
7027
|
parameters: {
|
|
6661
7028
|
type: "object",
|
|
6662
7029
|
properties: {
|
|
7030
|
+
ref: {
|
|
7031
|
+
type: "string",
|
|
7032
|
+
description: "Element ref from browser_snapshot, e.g. @e3"
|
|
7033
|
+
},
|
|
7034
|
+
snapshot_id: {
|
|
7035
|
+
type: "string",
|
|
7036
|
+
description: "Optional snapshot_id associated with the ref. If stale, refresh with browser_snapshot."
|
|
7037
|
+
},
|
|
6663
7038
|
selector: {
|
|
6664
7039
|
type: "string",
|
|
6665
7040
|
description: "CSS selector of input element"
|
|
@@ -6679,10 +7054,12 @@ function createBrowseTools(config, sharedBrowser) {
|
|
|
6679
7054
|
default: false
|
|
6680
7055
|
}
|
|
6681
7056
|
},
|
|
6682
|
-
required: ["
|
|
7057
|
+
required: ["text"]
|
|
6683
7058
|
},
|
|
6684
7059
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
6685
7060
|
return await browser.type({
|
|
7061
|
+
ref: input.ref,
|
|
7062
|
+
snapshotId: input.snapshot_id,
|
|
6686
7063
|
selector: input.selector,
|
|
6687
7064
|
text: input.text,
|
|
6688
7065
|
clear: input.clear ?? true,
|
|
@@ -6690,6 +7067,24 @@ function createBrowseTools(config, sharedBrowser) {
|
|
|
6690
7067
|
});
|
|
6691
7068
|
}, "execute")
|
|
6692
7069
|
},
|
|
7070
|
+
{
|
|
7071
|
+
name: "browser_back",
|
|
7072
|
+
description: "Navigate back to the previous page in browser history.",
|
|
7073
|
+
parameters: { type: "object", properties: {} },
|
|
7074
|
+
execute: /* @__PURE__ */ __name(async () => await browser.back(), "execute")
|
|
7075
|
+
},
|
|
7076
|
+
{
|
|
7077
|
+
name: "browser_press",
|
|
7078
|
+
description: "Press a keyboard key in the browser, e.g. Enter, Tab, Escape, ArrowDown.",
|
|
7079
|
+
parameters: {
|
|
7080
|
+
type: "object",
|
|
7081
|
+
properties: {
|
|
7082
|
+
key: { type: "string", description: "Key to press, e.g. Enter, Tab, Escape, ArrowDown" }
|
|
7083
|
+
},
|
|
7084
|
+
required: ["key"]
|
|
7085
|
+
},
|
|
7086
|
+
execute: /* @__PURE__ */ __name(async (input) => await browser.press(input.key), "execute")
|
|
7087
|
+
},
|
|
6693
7088
|
{
|
|
6694
7089
|
name: "browser_extract",
|
|
6695
7090
|
description: "Extract text content from the page",
|
|
@@ -6734,6 +7129,51 @@ function createBrowseTools(config, sharedBrowser) {
|
|
|
6734
7129
|
return await browser.evaluate(input.script);
|
|
6735
7130
|
}, "execute")
|
|
6736
7131
|
},
|
|
7132
|
+
{
|
|
7133
|
+
name: "browser_console",
|
|
7134
|
+
description: "Read browser console logs and JavaScript errors. This tool is read-only; use browser_evaluate for JavaScript execution.",
|
|
7135
|
+
parameters: {
|
|
7136
|
+
type: "object",
|
|
7137
|
+
properties: {
|
|
7138
|
+
clear: { type: "boolean", description: "Clear buffered logs after reading, if buffering is active", default: false }
|
|
7139
|
+
}
|
|
7140
|
+
},
|
|
7141
|
+
execute: /* @__PURE__ */ __name(async (input) => await browser.consoleMessages(input.clear === true), "execute")
|
|
7142
|
+
},
|
|
7143
|
+
{
|
|
7144
|
+
name: "browser_get_images",
|
|
7145
|
+
description: "List images on the current page with URLs, alt text, dimensions, and visibility. Use before visual analysis or downloading images.",
|
|
7146
|
+
parameters: { type: "object", properties: {} },
|
|
7147
|
+
execute: /* @__PURE__ */ __name(async () => await browser.getImages(), "execute")
|
|
7148
|
+
},
|
|
7149
|
+
{
|
|
7150
|
+
name: "browser_vision",
|
|
7151
|
+
description: "Take a browser screenshot for visual inspection. Returns a screenshot path plus page metadata. Use for visual layouts, CAPTCHAs, screenshots, or image-heavy pages.",
|
|
7152
|
+
parameters: {
|
|
7153
|
+
type: "object",
|
|
7154
|
+
properties: {
|
|
7155
|
+
question: { type: "string", description: "What you want to inspect visually" },
|
|
7156
|
+
annotate: { type: "boolean", description: "Reserved for future ref overlays; currently returns a normal screenshot", default: false }
|
|
7157
|
+
}
|
|
7158
|
+
},
|
|
7159
|
+
execute: /* @__PURE__ */ __name(async (input) => {
|
|
7160
|
+
const { join: join48 } = await import("node:path");
|
|
7161
|
+
const { tmpdir: tmpdir13 } = await import("node:os");
|
|
7162
|
+
const { randomUUID: randomUUID15 } = await import("node:crypto");
|
|
7163
|
+
const screenshotPath = join48(tmpdir13(), `openjaw-browser-${randomUUID15().slice(0, 8)}.png`);
|
|
7164
|
+
const screenshot = await browser.screenshot({ fullPage: false, path: screenshotPath });
|
|
7165
|
+
const snapshot = await browser.snapshot({ full: false });
|
|
7166
|
+
return {
|
|
7167
|
+
message: "Browser screenshot captured for visual inspection",
|
|
7168
|
+
question: input.question,
|
|
7169
|
+
snapshot_id: snapshot.snapshot_id,
|
|
7170
|
+
url: snapshot.url,
|
|
7171
|
+
title: snapshot.title,
|
|
7172
|
+
screenshotPath: screenshot.path ?? screenshotPath,
|
|
7173
|
+
imagePayload: "not_attached: screenshot path returned to avoid sending images to non-vision providers"
|
|
7174
|
+
};
|
|
7175
|
+
}, "execute")
|
|
7176
|
+
},
|
|
6737
7177
|
{
|
|
6738
7178
|
name: "browser_wait",
|
|
6739
7179
|
description: "Wait for an element to appear, text to change, or element to hide. Use before interacting with dynamic/slow-loading pages.",
|
|
@@ -6819,6 +7259,7 @@ var init_browse = __esm({
|
|
|
6819
7259
|
"../openjaw-mcp/dist/tools/browse.js"() {
|
|
6820
7260
|
"use strict";
|
|
6821
7261
|
init_browser();
|
|
7262
|
+
init_url_safety();
|
|
6822
7263
|
__name(createBrowseTools, "createBrowseTools");
|
|
6823
7264
|
}
|
|
6824
7265
|
});
|
|
@@ -7414,7 +7855,7 @@ var init_outlook_desktop = __esm({
|
|
|
7414
7855
|
}
|
|
7415
7856
|
}
|
|
7416
7857
|
sleep(ms) {
|
|
7417
|
-
return new Promise((
|
|
7858
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
7418
7859
|
}
|
|
7419
7860
|
};
|
|
7420
7861
|
}
|
|
@@ -8012,7 +8453,7 @@ var init_outlook_web = __esm({
|
|
|
8012
8453
|
await this.browser.typeChars(text);
|
|
8013
8454
|
}
|
|
8014
8455
|
sleep(ms) {
|
|
8015
|
-
return new Promise((
|
|
8456
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
8016
8457
|
}
|
|
8017
8458
|
};
|
|
8018
8459
|
}
|
|
@@ -8246,8 +8687,8 @@ var init_token_pool = __esm({
|
|
|
8246
8687
|
if (!existsSync8(legacyDir))
|
|
8247
8688
|
return;
|
|
8248
8689
|
try {
|
|
8249
|
-
const { readdirSync:
|
|
8250
|
-
const files =
|
|
8690
|
+
const { readdirSync: readdirSync8 } = __require("node:fs");
|
|
8691
|
+
const files = readdirSync8(legacyDir);
|
|
8251
8692
|
for (const file2 of files) {
|
|
8252
8693
|
if (!file2.endsWith(".json"))
|
|
8253
8694
|
continue;
|
|
@@ -8427,7 +8868,7 @@ var init_cdp_token_extractor = __esm({
|
|
|
8427
8868
|
}
|
|
8428
8869
|
logger_default.info("CDP: reloading tab for token refresh", { url: targetTab.url, audience });
|
|
8429
8870
|
await this.reloadTab(targetTab);
|
|
8430
|
-
await new Promise((
|
|
8871
|
+
await new Promise((resolve6) => setTimeout(resolve6, PAGE_RELOAD_WAIT_MS));
|
|
8431
8872
|
if (!targetTab.webSocketDebuggerUrl)
|
|
8432
8873
|
return [];
|
|
8433
8874
|
const freshPages = await this.listPages();
|
|
@@ -8544,19 +8985,19 @@ var init_cdp_token_extractor = __esm({
|
|
|
8544
8985
|
* Evaluate a JS expression in a tab and return the string result.
|
|
8545
8986
|
*/
|
|
8546
8987
|
async evaluateInTab(wsUrl, expression) {
|
|
8547
|
-
return new Promise((
|
|
8988
|
+
return new Promise((resolve6) => {
|
|
8548
8989
|
let ws;
|
|
8549
8990
|
try {
|
|
8550
8991
|
ws = new WebSocket(wsUrl);
|
|
8551
8992
|
} catch (err) {
|
|
8552
8993
|
logger_default.warn("CDP: WebSocket constructor failed", { wsUrl: wsUrl.substring(0, 60), error: String(err) });
|
|
8553
|
-
|
|
8994
|
+
resolve6(null);
|
|
8554
8995
|
return;
|
|
8555
8996
|
}
|
|
8556
8997
|
const timer = setTimeout(() => {
|
|
8557
8998
|
logger_default.warn("CDP: evaluateInTab timeout", { wsUrl: wsUrl.substring(0, 60) });
|
|
8558
8999
|
ws.close();
|
|
8559
|
-
|
|
9000
|
+
resolve6(null);
|
|
8560
9001
|
}, CDP_TIMEOUT_MS);
|
|
8561
9002
|
ws.on("open", () => {
|
|
8562
9003
|
ws.send(JSON.stringify({
|
|
@@ -8570,18 +9011,18 @@ var init_cdp_token_extractor = __esm({
|
|
|
8570
9011
|
if (resp.id === 1) {
|
|
8571
9012
|
clearTimeout(timer);
|
|
8572
9013
|
ws.close();
|
|
8573
|
-
|
|
9014
|
+
resolve6(resp.result?.result?.value ?? null);
|
|
8574
9015
|
}
|
|
8575
9016
|
});
|
|
8576
9017
|
ws.on("error", (err) => {
|
|
8577
9018
|
logger_default.warn("CDP: evaluateInTab WS error", { error: String(err), wsUrl: wsUrl.substring(0, 60) });
|
|
8578
9019
|
clearTimeout(timer);
|
|
8579
|
-
|
|
9020
|
+
resolve6(null);
|
|
8580
9021
|
});
|
|
8581
9022
|
});
|
|
8582
9023
|
}
|
|
8583
9024
|
async extractFromTab(wsUrl) {
|
|
8584
|
-
return new Promise((
|
|
9025
|
+
return new Promise((resolve6, reject) => {
|
|
8585
9026
|
const ws = new WebSocket(wsUrl);
|
|
8586
9027
|
const timer = setTimeout(() => {
|
|
8587
9028
|
ws.close();
|
|
@@ -8601,14 +9042,14 @@ var init_cdp_token_extractor = __esm({
|
|
|
8601
9042
|
ws.close();
|
|
8602
9043
|
const value = resp.result?.result?.value;
|
|
8603
9044
|
if (!value) {
|
|
8604
|
-
|
|
9045
|
+
resolve6([]);
|
|
8605
9046
|
return;
|
|
8606
9047
|
}
|
|
8607
9048
|
try {
|
|
8608
9049
|
const tokens = JSON.parse(value);
|
|
8609
|
-
|
|
9050
|
+
resolve6(tokens);
|
|
8610
9051
|
} catch {
|
|
8611
|
-
|
|
9052
|
+
resolve6([]);
|
|
8612
9053
|
}
|
|
8613
9054
|
}
|
|
8614
9055
|
});
|
|
@@ -8624,11 +9065,11 @@ var init_cdp_token_extractor = __esm({
|
|
|
8624
9065
|
async reloadTab(page) {
|
|
8625
9066
|
if (!page.webSocketDebuggerUrl)
|
|
8626
9067
|
return;
|
|
8627
|
-
return new Promise((
|
|
9068
|
+
return new Promise((resolve6, reject) => {
|
|
8628
9069
|
const ws = new WebSocket(page.webSocketDebuggerUrl);
|
|
8629
9070
|
const timer = setTimeout(() => {
|
|
8630
9071
|
ws.close();
|
|
8631
|
-
|
|
9072
|
+
resolve6();
|
|
8632
9073
|
}, 1e4);
|
|
8633
9074
|
ws.on("open", () => {
|
|
8634
9075
|
ws.send(JSON.stringify({
|
|
@@ -8642,7 +9083,7 @@ var init_cdp_token_extractor = __esm({
|
|
|
8642
9083
|
if (resp.id === 1) {
|
|
8643
9084
|
clearTimeout(timer);
|
|
8644
9085
|
ws.close();
|
|
8645
|
-
|
|
9086
|
+
resolve6();
|
|
8646
9087
|
}
|
|
8647
9088
|
});
|
|
8648
9089
|
ws.on("error", (err) => {
|
|
@@ -10303,13 +10744,13 @@ function createMemoryTools(config) {
|
|
|
10303
10744
|
const todos = input.todos;
|
|
10304
10745
|
try {
|
|
10305
10746
|
const { appendFile: appendFile2, mkdir: mkdir5 } = await import("node:fs/promises");
|
|
10306
|
-
const { existsSync:
|
|
10307
|
-
const { join:
|
|
10308
|
-
const { homedir:
|
|
10309
|
-
const memoryDir =
|
|
10310
|
-
if (!
|
|
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))
|
|
10311
10752
|
await mkdir5(memoryDir, { recursive: true });
|
|
10312
|
-
const todoPath =
|
|
10753
|
+
const todoPath = join48(memoryDir, "TODOS.md");
|
|
10313
10754
|
const { writeFile: writeFile5 } = await import("node:fs/promises");
|
|
10314
10755
|
await writeFile5(todoPath, `# Session Todos
|
|
10315
10756
|
|
|
@@ -11630,7 +12071,7 @@ var init_teams_desktop = __esm({
|
|
|
11630
12071
|
}
|
|
11631
12072
|
}
|
|
11632
12073
|
sleep(ms) {
|
|
11633
|
-
return new Promise((
|
|
12074
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
11634
12075
|
}
|
|
11635
12076
|
/**
|
|
11636
12077
|
* Get the current Teams window state
|
|
@@ -12497,7 +12938,7 @@ var init_teams_web = __esm({
|
|
|
12497
12938
|
}
|
|
12498
12939
|
}
|
|
12499
12940
|
sleep(ms) {
|
|
12500
|
-
return new Promise((
|
|
12941
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
12501
12942
|
}
|
|
12502
12943
|
};
|
|
12503
12944
|
}
|
|
@@ -13895,7 +14336,7 @@ var init_teams_chat_monitor = __esm({
|
|
|
13895
14336
|
return this.sentMessages.has(normalized);
|
|
13896
14337
|
}
|
|
13897
14338
|
sleep(ms) {
|
|
13898
|
-
return new Promise((
|
|
14339
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
13899
14340
|
}
|
|
13900
14341
|
/**
|
|
13901
14342
|
* Attempt to reconnect the browser after detecting a disconnection.
|
|
@@ -14682,7 +15123,7 @@ ${lines.join("\n")}`;
|
|
|
14682
15123
|
globalThis.__teamsSeenMessages.set(chatName, seenIds);
|
|
14683
15124
|
}
|
|
14684
15125
|
if (syncMode) {
|
|
14685
|
-
return new Promise((
|
|
15126
|
+
return new Promise((resolve6) => {
|
|
14686
15127
|
const timer2 = setInterval(async () => {
|
|
14687
15128
|
try {
|
|
14688
15129
|
const current = await channel.readCurrentChatMessages();
|
|
@@ -14691,7 +15132,7 @@ ${lines.join("\n")}`;
|
|
|
14691
15132
|
if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
|
|
14692
15133
|
clearInterval(timer2);
|
|
14693
15134
|
seenIds.add(msgId);
|
|
14694
|
-
|
|
15135
|
+
resolve6({
|
|
14695
15136
|
success: true,
|
|
14696
15137
|
channel: channelType,
|
|
14697
15138
|
sync: true,
|
|
@@ -14768,7 +15209,7 @@ ${lines.join("\n")}`;
|
|
|
14768
15209
|
globalThis.__teamsSeenMessages.set(chatName, seenIds);
|
|
14769
15210
|
}
|
|
14770
15211
|
if (syncMode) {
|
|
14771
|
-
return new Promise((
|
|
15212
|
+
return new Promise((resolve6) => {
|
|
14772
15213
|
const timer2 = setInterval(async () => {
|
|
14773
15214
|
try {
|
|
14774
15215
|
const current = await channel.readCurrentChatMessages();
|
|
@@ -14777,7 +15218,7 @@ ${lines.join("\n")}`;
|
|
|
14777
15218
|
if (!seenIds.has(msgId) && msg.sender !== currentUserName) {
|
|
14778
15219
|
clearInterval(timer2);
|
|
14779
15220
|
seenIds.add(msgId);
|
|
14780
|
-
|
|
15221
|
+
resolve6({
|
|
14781
15222
|
success: true,
|
|
14782
15223
|
channel: channelType,
|
|
14783
15224
|
sync: true,
|
|
@@ -15135,7 +15576,7 @@ ${lines.join("\n")}`;
|
|
|
15135
15576
|
return allTools;
|
|
15136
15577
|
}
|
|
15137
15578
|
function sleep2(ms) {
|
|
15138
|
-
return new Promise((
|
|
15579
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
15139
15580
|
}
|
|
15140
15581
|
var MAX_STORED_ENTRIES, ENTRY_TTL_MS;
|
|
15141
15582
|
var init_chat = __esm({
|
|
@@ -15750,6 +16191,175 @@ function urlMatchesDomain(url, domain) {
|
|
|
15750
16191
|
return false;
|
|
15751
16192
|
}
|
|
15752
16193
|
}
|
|
16194
|
+
function normalizeSearchResults(results, limit) {
|
|
16195
|
+
return results.slice(0, limit).map((result, index) => ({
|
|
16196
|
+
...result,
|
|
16197
|
+
snippet: result.snippet ?? "",
|
|
16198
|
+
description: result.snippet ?? "",
|
|
16199
|
+
position: index + 1
|
|
16200
|
+
}));
|
|
16201
|
+
}
|
|
16202
|
+
function clampNumber(value, fallback, min, max) {
|
|
16203
|
+
const numberValue = typeof value === "number" ? value : Number(value);
|
|
16204
|
+
if (!Number.isFinite(numberValue))
|
|
16205
|
+
return fallback;
|
|
16206
|
+
return Math.min(Math.max(Math.trunc(numberValue), min), max);
|
|
16207
|
+
}
|
|
16208
|
+
function extractHtmlTitle(html) {
|
|
16209
|
+
const match = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
16210
|
+
if (!match)
|
|
16211
|
+
return void 0;
|
|
16212
|
+
return DECODE_ENTITIES(match[1].replace(/\s+/g, " ").trim()) || void 0;
|
|
16213
|
+
}
|
|
16214
|
+
function cleanHtmlForMarkdown(html) {
|
|
16215
|
+
return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, "").replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, "").replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, "").replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, "").replace(/<header[^>]*>[\s\S]*?<\/header>/gi, "");
|
|
16216
|
+
}
|
|
16217
|
+
function htmlToText(html) {
|
|
16218
|
+
return DECODE_ENTITIES(cleanHtmlForMarkdown(html).replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/h[1-6]>/gi, "\n\n").replace(/<[^>]+>/g, " ").replace(/[ \t]+/g, " ").replace(/\n\s+/g, "\n").replace(/\n{3,}/g, "\n\n").trim());
|
|
16219
|
+
}
|
|
16220
|
+
function isPdfLike(url, contentType) {
|
|
16221
|
+
if (/application\/pdf/i.test(contentType))
|
|
16222
|
+
return true;
|
|
16223
|
+
try {
|
|
16224
|
+
return new URL(url).pathname.toLowerCase().endsWith(".pdf");
|
|
16225
|
+
} catch {
|
|
16226
|
+
return /\.pdf(?:$|[?#])/i.test(url);
|
|
16227
|
+
}
|
|
16228
|
+
}
|
|
16229
|
+
async function htmlToMarkdown(html) {
|
|
16230
|
+
try {
|
|
16231
|
+
const TurndownModule = await import("turndown");
|
|
16232
|
+
const TurndownService = TurndownModule.default || TurndownModule;
|
|
16233
|
+
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
|
16234
|
+
return turndown.turndown(cleanHtmlForMarkdown(html));
|
|
16235
|
+
} catch {
|
|
16236
|
+
return cleanHtmlForMarkdown(html).replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
16237
|
+
}
|
|
16238
|
+
}
|
|
16239
|
+
function paginateContent(text, startIndex, maxLength) {
|
|
16240
|
+
const safeStart = clampNumber(startIndex, 0, 0, Math.max(0, text.length));
|
|
16241
|
+
const safeMax = clampNumber(maxLength, 8e3, 1, 1e5);
|
|
16242
|
+
const content = text.slice(safeStart, safeStart + safeMax);
|
|
16243
|
+
const nextStartIndex = safeStart + content.length < text.length ? safeStart + content.length : void 0;
|
|
16244
|
+
return {
|
|
16245
|
+
content,
|
|
16246
|
+
contentLength: text.length,
|
|
16247
|
+
truncated: nextStartIndex !== void 0,
|
|
16248
|
+
startIndex: safeStart,
|
|
16249
|
+
...nextStartIndex !== void 0 && { nextStartIndex }
|
|
16250
|
+
};
|
|
16251
|
+
}
|
|
16252
|
+
async function fetchWithValidatedRedirects(startUrl, options) {
|
|
16253
|
+
let current = startUrl;
|
|
16254
|
+
const maxRedirects = options.maxRedirects ?? 10;
|
|
16255
|
+
for (let redirects = 0; redirects <= maxRedirects; redirects++) {
|
|
16256
|
+
const response = await fetch(current.href, {
|
|
16257
|
+
signal: options.signal,
|
|
16258
|
+
headers: options.headers,
|
|
16259
|
+
redirect: "manual"
|
|
16260
|
+
});
|
|
16261
|
+
if (response.status < 300 || response.status >= 400) {
|
|
16262
|
+
return { response, finalUrl: current.href };
|
|
16263
|
+
}
|
|
16264
|
+
const location = response.headers.get("location");
|
|
16265
|
+
if (!location) {
|
|
16266
|
+
return { response, finalUrl: current.href };
|
|
16267
|
+
}
|
|
16268
|
+
const nextUrl = new URL(location, current.href);
|
|
16269
|
+
const nextSafety = validateHttpUrl(nextUrl.href, { allowPrivate: options.allowPrivate });
|
|
16270
|
+
if (!nextSafety.ok) {
|
|
16271
|
+
return { finalUrl: nextUrl.href, error: `Blocked redirect: ${nextSafety.error}` };
|
|
16272
|
+
}
|
|
16273
|
+
current = nextSafety.url;
|
|
16274
|
+
}
|
|
16275
|
+
return { finalUrl: current.href, error: `Too many redirects (>${maxRedirects})` };
|
|
16276
|
+
}
|
|
16277
|
+
async function extractUrlContent(inputUrl, options) {
|
|
16278
|
+
const initial = validateHttpUrl(inputUrl, { allowPrivate: options.allowPrivate });
|
|
16279
|
+
if (!initial.ok) {
|
|
16280
|
+
return {
|
|
16281
|
+
url: inputUrl,
|
|
16282
|
+
finalUrl: inputUrl,
|
|
16283
|
+
contentType: "",
|
|
16284
|
+
content: "",
|
|
16285
|
+
contentLength: 0,
|
|
16286
|
+
truncated: false,
|
|
16287
|
+
startIndex: options.startIndex,
|
|
16288
|
+
error: initial.error
|
|
16289
|
+
};
|
|
16290
|
+
}
|
|
16291
|
+
const controller = new AbortController();
|
|
16292
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 2e4);
|
|
16293
|
+
try {
|
|
16294
|
+
const fetched = await fetchWithValidatedRedirects(initial.url, {
|
|
16295
|
+
signal: controller.signal,
|
|
16296
|
+
headers: { "User-Agent": "OpenJaw-Agent/1.0" },
|
|
16297
|
+
allowPrivate: options.allowPrivate
|
|
16298
|
+
});
|
|
16299
|
+
const finalUrl = fetched.finalUrl;
|
|
16300
|
+
if (fetched.error || !fetched.response) {
|
|
16301
|
+
return {
|
|
16302
|
+
url: inputUrl,
|
|
16303
|
+
finalUrl,
|
|
16304
|
+
contentType: "",
|
|
16305
|
+
content: "",
|
|
16306
|
+
contentLength: 0,
|
|
16307
|
+
truncated: false,
|
|
16308
|
+
startIndex: options.startIndex,
|
|
16309
|
+
error: fetched.error ?? "Fetch failed before response"
|
|
16310
|
+
};
|
|
16311
|
+
}
|
|
16312
|
+
const response = fetched.response;
|
|
16313
|
+
if (!response.ok) {
|
|
16314
|
+
return {
|
|
16315
|
+
url: inputUrl,
|
|
16316
|
+
finalUrl,
|
|
16317
|
+
contentType: response.headers.get("content-type") || "",
|
|
16318
|
+
content: "",
|
|
16319
|
+
contentLength: 0,
|
|
16320
|
+
truncated: false,
|
|
16321
|
+
startIndex: options.startIndex,
|
|
16322
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
16323
|
+
};
|
|
16324
|
+
}
|
|
16325
|
+
const contentType = response.headers.get("content-type") || "";
|
|
16326
|
+
if (isPdfLike(finalUrl, contentType)) {
|
|
16327
|
+
return {
|
|
16328
|
+
url: inputUrl,
|
|
16329
|
+
finalUrl,
|
|
16330
|
+
contentType,
|
|
16331
|
+
content: "",
|
|
16332
|
+
contentLength: 0,
|
|
16333
|
+
truncated: false,
|
|
16334
|
+
startIndex: options.startIndex,
|
|
16335
|
+
error: "PDF extraction is not yet supported"
|
|
16336
|
+
};
|
|
16337
|
+
}
|
|
16338
|
+
const rawText = await response.text();
|
|
16339
|
+
const title = contentType.includes("html") ? extractHtmlTitle(rawText) : void 0;
|
|
16340
|
+
const text = contentType.includes("html") ? options.format === "text" ? htmlToText(rawText) : await htmlToMarkdown(rawText) : rawText;
|
|
16341
|
+
return {
|
|
16342
|
+
url: inputUrl,
|
|
16343
|
+
finalUrl,
|
|
16344
|
+
title,
|
|
16345
|
+
contentType,
|
|
16346
|
+
...paginateContent(text, options.startIndex, options.maxLength)
|
|
16347
|
+
};
|
|
16348
|
+
} catch (err) {
|
|
16349
|
+
return {
|
|
16350
|
+
url: inputUrl,
|
|
16351
|
+
finalUrl: inputUrl,
|
|
16352
|
+
contentType: "",
|
|
16353
|
+
content: "",
|
|
16354
|
+
contentLength: 0,
|
|
16355
|
+
truncated: false,
|
|
16356
|
+
startIndex: options.startIndex,
|
|
16357
|
+
error: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16358
|
+
};
|
|
16359
|
+
} finally {
|
|
16360
|
+
clearTimeout(timeout);
|
|
16361
|
+
}
|
|
16362
|
+
}
|
|
15753
16363
|
function createShellTools(_config, hooks) {
|
|
15754
16364
|
return [
|
|
15755
16365
|
{
|
|
@@ -15790,11 +16400,11 @@ function createShellTools(_config, hooks) {
|
|
|
15790
16400
|
const shell = input.shell ?? true;
|
|
15791
16401
|
if (input.background) {
|
|
15792
16402
|
const { tmpdir: tmpdir13 } = await import("node:os");
|
|
15793
|
-
const { join:
|
|
15794
|
-
const { randomUUID:
|
|
16403
|
+
const { join: join48 } = await import("node:path");
|
|
16404
|
+
const { randomUUID: randomUUID15 } = await import("node:crypto");
|
|
15795
16405
|
const { createWriteStream: createWriteStream2 } = await import("node:fs");
|
|
15796
|
-
const taskId =
|
|
15797
|
-
const outputPath =
|
|
16406
|
+
const taskId = randomUUID15().slice(0, 8);
|
|
16407
|
+
const outputPath = join48(tmpdir13(), `oj-bg-${taskId}.log`);
|
|
15798
16408
|
const detached = spawn(command, [], {
|
|
15799
16409
|
shell,
|
|
15800
16410
|
cwd,
|
|
@@ -15814,7 +16424,7 @@ function createShellTools(_config, hooks) {
|
|
|
15814
16424
|
message: `Command started in background (PID: ${detached.pid}). Output: ${outputPath}`
|
|
15815
16425
|
};
|
|
15816
16426
|
}
|
|
15817
|
-
return new Promise((
|
|
16427
|
+
return new Promise((resolve6) => {
|
|
15818
16428
|
const proc = spawn(command, [], {
|
|
15819
16429
|
shell,
|
|
15820
16430
|
cwd,
|
|
@@ -15832,7 +16442,7 @@ function createShellTools(_config, hooks) {
|
|
|
15832
16442
|
proc.on("close", (code) => {
|
|
15833
16443
|
const stdoutResult = truncateOutput(stdout.trim());
|
|
15834
16444
|
const stderrResult = truncateOutput(stderr.trim());
|
|
15835
|
-
|
|
16445
|
+
resolve6({
|
|
15836
16446
|
command,
|
|
15837
16447
|
exitCode: code,
|
|
15838
16448
|
stdout: stdoutResult.text,
|
|
@@ -15843,7 +16453,7 @@ function createShellTools(_config, hooks) {
|
|
|
15843
16453
|
});
|
|
15844
16454
|
});
|
|
15845
16455
|
proc.on("error", (error) => {
|
|
15846
|
-
|
|
16456
|
+
resolve6({
|
|
15847
16457
|
command,
|
|
15848
16458
|
exitCode: -1,
|
|
15849
16459
|
stdout: "",
|
|
@@ -15877,10 +16487,10 @@ function createShellTools(_config, hooks) {
|
|
|
15877
16487
|
},
|
|
15878
16488
|
requiresConfirmation: false,
|
|
15879
16489
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
15880
|
-
const { writeFileSync:
|
|
15881
|
-
const { join:
|
|
16490
|
+
const { writeFileSync: writeFileSync23, unlinkSync: unlinkSync9 } = await import("node:fs");
|
|
16491
|
+
const { join: join48 } = await import("node:path");
|
|
15882
16492
|
const { tmpdir: tmpdir13 } = await import("node:os");
|
|
15883
|
-
const { randomUUID:
|
|
16493
|
+
const { randomUUID: randomUUID15 } = await import("node:crypto");
|
|
15884
16494
|
const { execFile: execFile3 } = await import("node:child_process");
|
|
15885
16495
|
const code = input.code;
|
|
15886
16496
|
const language = input.language;
|
|
@@ -15897,14 +16507,14 @@ function createShellTools(_config, hooks) {
|
|
|
15897
16507
|
const interpreter = interpreterMap[language];
|
|
15898
16508
|
if (!interpreter)
|
|
15899
16509
|
return { error: `Unsupported language: ${language}` };
|
|
15900
|
-
const tmpFile =
|
|
16510
|
+
const tmpFile = join48(tmpdir13(), `oj-code-${randomUUID15().slice(0, 8)}${ext}`);
|
|
15901
16511
|
try {
|
|
15902
|
-
|
|
16512
|
+
writeFileSync23(tmpFile, code, "utf-8");
|
|
15903
16513
|
const startTime = Date.now();
|
|
15904
|
-
const result = await new Promise((
|
|
16514
|
+
const result = await new Promise((resolve6) => {
|
|
15905
16515
|
execFile3(interpreter.cmd, [...interpreter.args, tmpFile], { timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout2, stderr2) => {
|
|
15906
16516
|
const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
|
|
15907
|
-
|
|
16517
|
+
resolve6({ exitCode, stdout: stdout2 ?? "", stderr: stderr2 ?? "" });
|
|
15908
16518
|
});
|
|
15909
16519
|
});
|
|
15910
16520
|
const executionTimeMs = Date.now() - startTime;
|
|
@@ -15997,7 +16607,7 @@ function createShellTools(_config, hooks) {
|
|
|
15997
16607
|
required: ["title", "message"]
|
|
15998
16608
|
},
|
|
15999
16609
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
16000
|
-
return new Promise((
|
|
16610
|
+
return new Promise((resolve6) => {
|
|
16001
16611
|
notifier.notify({
|
|
16002
16612
|
title: input.title,
|
|
16003
16613
|
message: input.message,
|
|
@@ -16005,9 +16615,9 @@ function createShellTools(_config, hooks) {
|
|
|
16005
16615
|
sound: true
|
|
16006
16616
|
}, (err) => {
|
|
16007
16617
|
if (err) {
|
|
16008
|
-
|
|
16618
|
+
resolve6({ error: err.message });
|
|
16009
16619
|
} else {
|
|
16010
|
-
|
|
16620
|
+
resolve6({ success: true });
|
|
16011
16621
|
}
|
|
16012
16622
|
});
|
|
16013
16623
|
});
|
|
@@ -16016,58 +16626,86 @@ function createShellTools(_config, hooks) {
|
|
|
16016
16626
|
// ─── Web Fetch tool (headless URL content extraction) ───
|
|
16017
16627
|
{
|
|
16018
16628
|
name: "web_fetch",
|
|
16019
|
-
description: "Fetch content from a URL
|
|
16629
|
+
description: "Fetch raw content from a URL. Use for APIs, JSON, plain text, and small pages. For articles/docs/pages that need cleanup, prefer web_extract.",
|
|
16020
16630
|
parameters: {
|
|
16021
16631
|
type: "object",
|
|
16022
16632
|
properties: {
|
|
16023
16633
|
url: { type: "string", description: "The URL to fetch" },
|
|
16024
16634
|
max_length: { type: "number", description: "Maximum characters to return (default: 5000)" },
|
|
16025
|
-
start_index: { type: "number", description: "Start index for content pagination (default: 0). Use to continue reading truncated content." }
|
|
16635
|
+
start_index: { type: "number", description: "Start index for content pagination (default: 0). Use to continue reading truncated content." },
|
|
16636
|
+
allow_private: { type: "boolean", description: "Allow private, loopback, or internal network URLs. Default false." }
|
|
16026
16637
|
},
|
|
16027
16638
|
required: ["url"]
|
|
16028
16639
|
},
|
|
16029
16640
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
16030
16641
|
const url = input.url;
|
|
16031
|
-
const maxLength = input.max_length
|
|
16032
|
-
const startIndex = input.start_index
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
const turndown = new TurndownService({
|
|
16051
|
-
headingStyle: "atx",
|
|
16052
|
-
codeBlockStyle: "fenced"
|
|
16053
|
-
});
|
|
16054
|
-
const cleaned = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, "").replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, "");
|
|
16055
|
-
text = turndown.turndown(cleaned);
|
|
16056
|
-
} catch {
|
|
16057
|
-
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
16058
|
-
}
|
|
16059
|
-
}
|
|
16060
|
-
if (startIndex > 0) {
|
|
16061
|
-
text = text.slice(startIndex);
|
|
16062
|
-
}
|
|
16063
|
-
if (text.length > maxLength) {
|
|
16064
|
-
text = text.slice(0, maxLength);
|
|
16065
|
-
return { url, contentType, length: text.length, content: text, truncated: true, nextIndex: startIndex + maxLength, hint: `Content truncated. Use start_index: ${startIndex + maxLength} to continue reading.` };
|
|
16642
|
+
const maxLength = clampNumber(input.max_length, 5e3, 1, 1e5);
|
|
16643
|
+
const startIndex = clampNumber(input.start_index, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
16644
|
+
const allowPrivate = input.allow_private === true;
|
|
16645
|
+
const extracted = await extractUrlContent(url, { maxLength, startIndex, allowPrivate, timeoutMs: 15e3, format: "markdown" });
|
|
16646
|
+
if (extracted.error)
|
|
16647
|
+
return { error: extracted.error, url: extracted.url, finalUrl: extracted.finalUrl };
|
|
16648
|
+
return {
|
|
16649
|
+
url: extracted.url,
|
|
16650
|
+
finalUrl: extracted.finalUrl,
|
|
16651
|
+
contentType: extracted.contentType,
|
|
16652
|
+
title: extracted.title,
|
|
16653
|
+
length: extracted.content.length,
|
|
16654
|
+
contentLength: extracted.contentLength,
|
|
16655
|
+
content: extracted.content,
|
|
16656
|
+
truncated: extracted.truncated,
|
|
16657
|
+
...extracted.nextStartIndex !== void 0 && {
|
|
16658
|
+
nextIndex: extracted.nextStartIndex,
|
|
16659
|
+
nextStartIndex: extracted.nextStartIndex,
|
|
16660
|
+
hint: `Content truncated. Use start_index: ${extracted.nextStartIndex} to continue reading.`
|
|
16066
16661
|
}
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16662
|
+
};
|
|
16663
|
+
}, "execute")
|
|
16664
|
+
},
|
|
16665
|
+
// ─── Web Extract tool (selected-source reading) ───
|
|
16666
|
+
{
|
|
16667
|
+
name: "web_extract",
|
|
16668
|
+
description: "Extract readable content from selected web page URLs. Use after web_search when you need to read source pages, articles, docs, or papers. Returns markdown/text with metadata and pagination. For raw APIs or plain JSON, web_fetch is lighter.",
|
|
16669
|
+
parameters: {
|
|
16670
|
+
type: "object",
|
|
16671
|
+
properties: {
|
|
16672
|
+
urls: { type: "array", items: { type: "string" }, description: "List of URLs to extract content from (max 5)" },
|
|
16673
|
+
format: { type: "string", enum: ["markdown", "text"], description: "Output format. HTML is converted to markdown by default.", default: "markdown" },
|
|
16674
|
+
max_length: { type: "number", description: "Maximum characters per URL to return (default: 8000, max: 100000)" },
|
|
16675
|
+
start_index: { type: "number", description: "Start index for content pagination (default: 0). Applies to each URL." },
|
|
16676
|
+
allow_private: { type: "boolean", description: "Allow private, loopback, or internal network URLs. Default false." },
|
|
16677
|
+
use_llm_processing: { type: "boolean", description: "Reserved for future LLM summarization. Currently deterministic extraction only." }
|
|
16678
|
+
},
|
|
16679
|
+
required: ["urls"]
|
|
16680
|
+
},
|
|
16681
|
+
execute: /* @__PURE__ */ __name(async (input) => {
|
|
16682
|
+
const rawUrls = Array.isArray(input.urls) ? input.urls.filter((url) => typeof url === "string") : [];
|
|
16683
|
+
const urls = rawUrls.slice(0, 5);
|
|
16684
|
+
if (urls.length === 0)
|
|
16685
|
+
return { error: "web_extract requires at least one URL", results: [] };
|
|
16686
|
+
const maxLength = clampNumber(input.max_length, 8e3, 1, 1e5);
|
|
16687
|
+
const startIndex = clampNumber(input.start_index, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
16688
|
+
const allowPrivate = input.allow_private === true;
|
|
16689
|
+
const format2 = input.format === "text" ? "text" : "markdown";
|
|
16690
|
+
const results = await Promise.all(urls.map((url) => extractUrlContent(url, { maxLength, startIndex, allowPrivate, format: format2 })));
|
|
16691
|
+
return {
|
|
16692
|
+
results: results.map((result) => ({
|
|
16693
|
+
url: result.url,
|
|
16694
|
+
finalUrl: result.finalUrl,
|
|
16695
|
+
title: result.title,
|
|
16696
|
+
contentType: result.contentType,
|
|
16697
|
+
format: format2,
|
|
16698
|
+
content: result.content,
|
|
16699
|
+
contentLength: result.contentLength,
|
|
16700
|
+
truncated: result.truncated,
|
|
16701
|
+
startIndex: result.startIndex,
|
|
16702
|
+
...result.nextStartIndex !== void 0 && { nextStartIndex: result.nextStartIndex },
|
|
16703
|
+
...result.error && { error: result.error }
|
|
16704
|
+
})),
|
|
16705
|
+
count: results.length,
|
|
16706
|
+
...rawUrls.length > urls.length && { warning: `Only the first ${urls.length} URLs were extracted (max 5 per call).` },
|
|
16707
|
+
...input.use_llm_processing === true && { llmProcessing: "unavailable: deterministic extraction only in this build" }
|
|
16708
|
+
};
|
|
16071
16709
|
}, "execute")
|
|
16072
16710
|
},
|
|
16073
16711
|
// ─── Sleep tool ───
|
|
@@ -16083,7 +16721,7 @@ function createShellTools(_config, hooks) {
|
|
|
16083
16721
|
},
|
|
16084
16722
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
16085
16723
|
const seconds = Math.min(Math.max(0.1, input.seconds), 60);
|
|
16086
|
-
await new Promise((
|
|
16724
|
+
await new Promise((resolve6) => setTimeout(resolve6, seconds * 1e3));
|
|
16087
16725
|
return { waited: seconds, message: `Waited ${seconds} seconds` };
|
|
16088
16726
|
}, "execute")
|
|
16089
16727
|
},
|
|
@@ -16162,12 +16800,14 @@ function createShellTools(_config, hooks) {
|
|
|
16162
16800
|
signal: controller.signal
|
|
16163
16801
|
});
|
|
16164
16802
|
if (native && Array.isArray(native.results) && native.results.length > 0) {
|
|
16803
|
+
const results = normalizeSearchResults(native.results, maxResults);
|
|
16165
16804
|
return {
|
|
16166
16805
|
query,
|
|
16167
|
-
resultCount:
|
|
16168
|
-
results
|
|
16806
|
+
resultCount: results.length,
|
|
16807
|
+
results,
|
|
16169
16808
|
summary: native.summary,
|
|
16170
16809
|
provider: native.provider,
|
|
16810
|
+
backend: native.provider,
|
|
16171
16811
|
durationSeconds: native.durationSeconds
|
|
16172
16812
|
};
|
|
16173
16813
|
}
|
|
@@ -16218,11 +16858,13 @@ function createShellTools(_config, hooks) {
|
|
|
16218
16858
|
results = results.filter((r) => !blockedDomains.some((d) => urlMatchesDomain(r.url, d)));
|
|
16219
16859
|
}
|
|
16220
16860
|
if (results.length > 0) {
|
|
16861
|
+
const normalizedResults = normalizeSearchResults(results, maxResults);
|
|
16221
16862
|
return {
|
|
16222
16863
|
query,
|
|
16223
|
-
resultCount:
|
|
16224
|
-
results,
|
|
16864
|
+
resultCount: normalizedResults.length,
|
|
16865
|
+
results: normalizedResults,
|
|
16225
16866
|
provider: `duckduckgo-${ep.kind}`,
|
|
16867
|
+
backend: `duckduckgo-${ep.kind}`,
|
|
16226
16868
|
...nativeError ? { nativeSearchError: nativeError } : {}
|
|
16227
16869
|
};
|
|
16228
16870
|
}
|
|
@@ -16287,12 +16929,23 @@ var init_shell = __esm({
|
|
|
16287
16929
|
"../openjaw-mcp/dist/tools/shell.js"() {
|
|
16288
16930
|
"use strict";
|
|
16289
16931
|
init_web_search_types();
|
|
16932
|
+
init_url_safety();
|
|
16290
16933
|
__name(truncateOutput, "truncateOutput");
|
|
16291
16934
|
DECODE_ENTITIES = /* @__PURE__ */ __name((s) => s.replace(/&/g, "&").replace(/ /g, " ").replace(/'/g, "'").replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">"), "DECODE_ENTITIES");
|
|
16292
16935
|
__name(parseLiteResults, "parseLiteResults");
|
|
16293
16936
|
__name(parseHtmlResults, "parseHtmlResults");
|
|
16294
16937
|
__name(isAnomalyPage, "isAnomalyPage");
|
|
16295
16938
|
__name(urlMatchesDomain, "urlMatchesDomain");
|
|
16939
|
+
__name(normalizeSearchResults, "normalizeSearchResults");
|
|
16940
|
+
__name(clampNumber, "clampNumber");
|
|
16941
|
+
__name(extractHtmlTitle, "extractHtmlTitle");
|
|
16942
|
+
__name(cleanHtmlForMarkdown, "cleanHtmlForMarkdown");
|
|
16943
|
+
__name(htmlToText, "htmlToText");
|
|
16944
|
+
__name(isPdfLike, "isPdfLike");
|
|
16945
|
+
__name(htmlToMarkdown, "htmlToMarkdown");
|
|
16946
|
+
__name(paginateContent, "paginateContent");
|
|
16947
|
+
__name(fetchWithValidatedRedirects, "fetchWithValidatedRedirects");
|
|
16948
|
+
__name(extractUrlContent, "extractUrlContent");
|
|
16296
16949
|
__name(createShellTools, "createShellTools");
|
|
16297
16950
|
}
|
|
16298
16951
|
});
|
|
@@ -17180,7 +17833,7 @@ var init_office_desktop = __esm({
|
|
|
17180
17833
|
return Array.isArray(parsed) ? parsed : [parsed];
|
|
17181
17834
|
}
|
|
17182
17835
|
sleep(ms) {
|
|
17183
|
-
return new Promise((
|
|
17836
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
17184
17837
|
}
|
|
17185
17838
|
};
|
|
17186
17839
|
}
|
|
@@ -18861,7 +19514,7 @@ public class Win32Send {
|
|
|
18861
19514
|
}
|
|
18862
19515
|
}
|
|
18863
19516
|
sleep(ms) {
|
|
18864
|
-
return new Promise((
|
|
19517
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
18865
19518
|
}
|
|
18866
19519
|
};
|
|
18867
19520
|
}
|
|
@@ -18926,7 +19579,7 @@ function getActiveMonitors() {
|
|
|
18926
19579
|
return result;
|
|
18927
19580
|
}
|
|
18928
19581
|
function sleep3(ms) {
|
|
18929
|
-
return new Promise((
|
|
19582
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
18930
19583
|
}
|
|
18931
19584
|
async function fileHash(filePath) {
|
|
18932
19585
|
const data = fs.readFileSync(filePath);
|
|
@@ -19667,6 +20320,7 @@ var init_categories = __esm({
|
|
|
19667
20320
|
["clipboard_", "system"],
|
|
19668
20321
|
["web_fetch", "system"],
|
|
19669
20322
|
["web_search", "system"],
|
|
20323
|
+
["web_extract", "system"],
|
|
19670
20324
|
["notify", "system"],
|
|
19671
20325
|
["sleep", "system"],
|
|
19672
20326
|
["ask_user", "system"],
|
|
@@ -20248,6 +20902,8 @@ function parseSkillFile(content, filename) {
|
|
|
20248
20902
|
whenToUse: meta.whenToUse || meta.when_to_use,
|
|
20249
20903
|
tools: Array.isArray(meta.tools) ? meta.tools : meta.tools ? parseYamlArray(meta.tools) : void 0,
|
|
20250
20904
|
model: meta.model,
|
|
20905
|
+
execution: meta.execution === "fork" ? "fork" : meta.execution === "inline" ? "inline" : void 0,
|
|
20906
|
+
timeoutMs: typeof meta.timeoutMs === "number" ? meta.timeoutMs : typeof meta.timeout_ms === "number" ? meta.timeout_ms : void 0,
|
|
20251
20907
|
version: meta.version,
|
|
20252
20908
|
author: meta.author,
|
|
20253
20909
|
platforms: Array.isArray(meta.platforms) ? meta.platforms : void 0,
|
|
@@ -20396,11 +21052,11 @@ function loadFlatSkillsFromDir(dir2, source, priority, out) {
|
|
|
20396
21052
|
function loadPackagedSkillsFromDir(dir2, out) {
|
|
20397
21053
|
for (const entry of safeReadDir(dir2)) {
|
|
20398
21054
|
if (!entry.isDirectory()) continue;
|
|
20399
|
-
const
|
|
20400
|
-
const entrypoint = findPackageEntrypoint(
|
|
21055
|
+
const rootDir2 = join22(dir2, entry.name);
|
|
21056
|
+
const entrypoint = findPackageEntrypoint(rootDir2);
|
|
20401
21057
|
if (!entrypoint) continue;
|
|
20402
|
-
const filePath = join22(
|
|
20403
|
-
const skill = parseSkillAtPath(filePath,
|
|
21058
|
+
const filePath = join22(rootDir2, entrypoint);
|
|
21059
|
+
const skill = parseSkillAtPath(filePath, rootDir2, entrypoint, "user", `${entry.name}.md`);
|
|
20404
21060
|
if (skill) putSkill(out, skill, 2);
|
|
20405
21061
|
}
|
|
20406
21062
|
}
|
|
@@ -20413,7 +21069,7 @@ function findPackageEntrypoint(dir2) {
|
|
|
20413
21069
|
const markdown = entries.filter((entry) => isMarkdown(entry.name));
|
|
20414
21070
|
return markdown.length === 1 ? markdown[0].name : null;
|
|
20415
21071
|
}
|
|
20416
|
-
function parseSkillAtPath(filePath,
|
|
21072
|
+
function parseSkillAtPath(filePath, rootDir2, entrypoint, source, fallbackFilename) {
|
|
20417
21073
|
try {
|
|
20418
21074
|
const content = readFileSync14(filePath, "utf-8").trim();
|
|
20419
21075
|
if (!content) return null;
|
|
@@ -20424,7 +21080,7 @@ function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilenam
|
|
|
20424
21080
|
name,
|
|
20425
21081
|
meta: { ...parsed.meta, name },
|
|
20426
21082
|
filePath,
|
|
20427
|
-
rootDir,
|
|
21083
|
+
rootDir: rootDir2,
|
|
20428
21084
|
entrypoint,
|
|
20429
21085
|
source,
|
|
20430
21086
|
hasFrontmatter: parsed.hasFrontmatter
|
|
@@ -20527,8 +21183,10 @@ function getSkillsSection() {
|
|
|
20527
21183
|
const content = `# Available Skills
|
|
20528
21184
|
|
|
20529
21185
|
Use the \`invoke_skill\` tool to execute any skill listed below.
|
|
20530
|
-
|
|
20531
|
-
|
|
21186
|
+
Skills are for specialized, multi-step workflows that materially benefit from the skill's process.
|
|
21187
|
+
Do NOT invoke a skill for simple one-off questions, quick factual/current-info lookups, basic summaries, or tasks a visible built-in/MCP tool can answer in 1-2 calls.
|
|
21188
|
+
For news/latest/current events, use \`web_search\` directly. For selected URLs, use \`web_extract\` directly.
|
|
21189
|
+
When a skill truly matches a complex workflow, invoke it. Most skills load inline into the current turn so you can continue with visible tools. Only skills marked for fork execution run as isolated sub-agents and may take longer than ordinary tools.
|
|
20532
21190
|
|
|
20533
21191
|
To create new skills or improve existing ones, use \`invoke_skill("skill-creator")\`.
|
|
20534
21192
|
New skills must be saved to \`~/.openjaw-agent/skills/\` (user skills directory).
|
|
@@ -21079,12 +21737,12 @@ var init_telegram = __esm({
|
|
|
21079
21737
|
Options: ${chunk.choices.join(" | ")}` : "";
|
|
21080
21738
|
await this.bot.sendMessage(chatId, `\u2753 ${question}${choicesText}`);
|
|
21081
21739
|
updateStatus("\u2753 Waiting for your response...");
|
|
21082
|
-
const userReply = await new Promise((
|
|
21083
|
-
this._pendingReplyResolver =
|
|
21740
|
+
const userReply = await new Promise((resolve6) => {
|
|
21741
|
+
this._pendingReplyResolver = resolve6;
|
|
21084
21742
|
setTimeout(() => {
|
|
21085
|
-
if (this._pendingReplyResolver ===
|
|
21743
|
+
if (this._pendingReplyResolver === resolve6) {
|
|
21086
21744
|
this._pendingReplyResolver = null;
|
|
21087
|
-
|
|
21745
|
+
resolve6("[No response \u2014 timed out]");
|
|
21088
21746
|
}
|
|
21089
21747
|
}, 5 * 60 * 1e3);
|
|
21090
21748
|
});
|
|
@@ -21682,7 +22340,7 @@ async function promptConsent(servers) {
|
|
|
21682
22340
|
input: process.stdin,
|
|
21683
22341
|
output: process.stderr
|
|
21684
22342
|
});
|
|
21685
|
-
const ask = /* @__PURE__ */ __name((question) => new Promise((
|
|
22343
|
+
const ask = /* @__PURE__ */ __name((question) => new Promise((resolve6) => rl.question(question, resolve6)), "ask");
|
|
21686
22344
|
const w = process.stderr.columns || 80;
|
|
21687
22345
|
const inner = w - 4;
|
|
21688
22346
|
const hLine = "\u2500".repeat(inner);
|
|
@@ -23737,7 +24395,7 @@ try {
|
|
|
23737
24395
|
}
|
|
23738
24396
|
`;
|
|
23739
24397
|
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
23740
|
-
return new Promise((
|
|
24398
|
+
return new Promise((resolve6) => {
|
|
23741
24399
|
const proc = spawn3("powershell.exe", ["-NoProfile", "-STA", "-EncodedCommand", encoded], {
|
|
23742
24400
|
stdio: ["pipe", "pipe", "pipe"],
|
|
23743
24401
|
windowsHide: true
|
|
@@ -23752,22 +24410,22 @@ try {
|
|
|
23752
24410
|
});
|
|
23753
24411
|
const timer = setTimeout(() => {
|
|
23754
24412
|
if (!proc.killed) proc.kill();
|
|
23755
|
-
|
|
24413
|
+
resolve6(null);
|
|
23756
24414
|
}, (timeoutSeconds + 5) * 1e3);
|
|
23757
24415
|
proc.on("exit", () => {
|
|
23758
24416
|
clearTimeout(timer);
|
|
23759
24417
|
const output = stdout.replace(/#< CLIXML[\s\S]*/m, "").trim();
|
|
23760
24418
|
if (output === "NO_SPEECH" || output.startsWith("ERROR") || !output) {
|
|
23761
|
-
|
|
24419
|
+
resolve6(null);
|
|
23762
24420
|
return;
|
|
23763
24421
|
}
|
|
23764
24422
|
const parts = output.split("|");
|
|
23765
24423
|
const text = parts[0]?.trim();
|
|
23766
24424
|
const confidence = parseFloat(parts[1] || "0");
|
|
23767
24425
|
if (text) {
|
|
23768
|
-
|
|
24426
|
+
resolve6({ text, confidence: isNaN(confidence) ? 0.5 : confidence });
|
|
23769
24427
|
} else {
|
|
23770
|
-
|
|
24428
|
+
resolve6(null);
|
|
23771
24429
|
}
|
|
23772
24430
|
try {
|
|
23773
24431
|
if (existsSync21(resultFile)) unlinkSync5(resultFile);
|
|
@@ -24104,6 +24762,7 @@ var init_PromptInput = __esm({
|
|
|
24104
24762
|
{ name: "/repl", description: "\u{1F527} Start interactive code REPL" },
|
|
24105
24763
|
{ name: "/voice", description: "\u{1F50A} Toggle voice output (TTS)" },
|
|
24106
24764
|
{ name: "/fork", description: "\u{1F500} Spawn background sub-agent" },
|
|
24765
|
+
{ name: "/workflow", description: "\u{1F9ED} Run advisory dynamic workflow" },
|
|
24107
24766
|
{ name: "/tasks", description: "List background tasks" },
|
|
24108
24767
|
{ name: "/clear", description: "Clear conversation history" },
|
|
24109
24768
|
{ name: "/compact", description: "Summarize old messages to free context" },
|
|
@@ -24136,6 +24795,12 @@ var init_PromptInput = __esm({
|
|
|
24136
24795
|
{ name: "pause", description: "Pause a task: /schedule pause <id>" },
|
|
24137
24796
|
{ name: "resume", description: "Resume a task: /schedule resume <id>" }
|
|
24138
24797
|
],
|
|
24798
|
+
"/workflow": [
|
|
24799
|
+
{ name: "status", description: "Open live worker status" },
|
|
24800
|
+
{ name: "list", description: "List recent workflows" },
|
|
24801
|
+
{ name: "show <id>", description: "Show workflow summary" },
|
|
24802
|
+
{ name: "cancel <id>", description: "Cancel a run or worker" }
|
|
24803
|
+
],
|
|
24139
24804
|
"/repl": [
|
|
24140
24805
|
{ name: "python", description: "Python interactive shell" },
|
|
24141
24806
|
{ name: "node", description: "Node.js interactive shell" },
|
|
@@ -25339,8 +26004,8 @@ function isSensitivePath(resolved) {
|
|
|
25339
26004
|
if (re.test(normalized)) return true;
|
|
25340
26005
|
}
|
|
25341
26006
|
for (const re of SENSITIVE_FILE_PATTERNS) {
|
|
25342
|
-
const
|
|
25343
|
-
if (re.test(
|
|
26007
|
+
const basename5 = path2.basename(resolved);
|
|
26008
|
+
if (re.test(basename5)) return true;
|
|
25344
26009
|
}
|
|
25345
26010
|
return false;
|
|
25346
26011
|
}
|
|
@@ -25458,34 +26123,34 @@ ${output}
|
|
|
25458
26123
|
}
|
|
25459
26124
|
}
|
|
25460
26125
|
function expandUrl(ref, warnings) {
|
|
25461
|
-
return new Promise((
|
|
26126
|
+
return new Promise((resolve6) => {
|
|
25462
26127
|
const url = ref.target;
|
|
25463
26128
|
const mod = url.startsWith("https") ? https : http;
|
|
25464
26129
|
const req = mod.get(url, { timeout: 15e3 }, (res) => {
|
|
25465
26130
|
if (res.statusCode && (res.statusCode >= 300 && res.statusCode < 400) && res.headers.location) {
|
|
25466
26131
|
const redirectMod = res.headers.location.startsWith("https") ? https : http;
|
|
25467
26132
|
const req2 = redirectMod.get(res.headers.location, { timeout: 15e3 }, (res2) => {
|
|
25468
|
-
collectResponse(res2, ref, warnings,
|
|
26133
|
+
collectResponse(res2, ref, warnings, resolve6);
|
|
25469
26134
|
});
|
|
25470
26135
|
req2.on("error", (e) => {
|
|
25471
26136
|
warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
|
|
25472
|
-
|
|
26137
|
+
resolve6(null);
|
|
25473
26138
|
});
|
|
25474
26139
|
return;
|
|
25475
26140
|
}
|
|
25476
|
-
collectResponse(res, ref, warnings,
|
|
26141
|
+
collectResponse(res, ref, warnings, resolve6);
|
|
25477
26142
|
});
|
|
25478
26143
|
req.on("error", (e) => {
|
|
25479
26144
|
warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
|
|
25480
|
-
|
|
26145
|
+
resolve6(null);
|
|
25481
26146
|
});
|
|
25482
26147
|
});
|
|
25483
26148
|
}
|
|
25484
|
-
function collectResponse(res, ref, warnings,
|
|
26149
|
+
function collectResponse(res, ref, warnings, resolve6) {
|
|
25485
26150
|
if (res.statusCode && res.statusCode >= 400) {
|
|
25486
26151
|
warnings.push(`\u26A0\uFE0F @url returned HTTP ${res.statusCode}: ${ref.target}`);
|
|
25487
26152
|
res.resume();
|
|
25488
|
-
|
|
26153
|
+
resolve6(null);
|
|
25489
26154
|
return;
|
|
25490
26155
|
}
|
|
25491
26156
|
const chunks = [];
|
|
@@ -25497,14 +26162,14 @@ function collectResponse(res, ref, warnings, resolve5) {
|
|
|
25497
26162
|
text = text.slice(0, 5e4) + "\n\u2026 (truncated)";
|
|
25498
26163
|
}
|
|
25499
26164
|
const tokens = estimateTokens2(text);
|
|
25500
|
-
|
|
26165
|
+
resolve6(`\u{1F310} @url:${ref.target} (${tokens} tokens)
|
|
25501
26166
|
\`\`\`
|
|
25502
26167
|
${text}
|
|
25503
26168
|
\`\`\``);
|
|
25504
26169
|
});
|
|
25505
26170
|
res.on("error", (e) => {
|
|
25506
26171
|
warnings.push(`\u26A0\uFE0F @url fetch error: ${e.message}`);
|
|
25507
|
-
|
|
26172
|
+
resolve6(null);
|
|
25508
26173
|
});
|
|
25509
26174
|
}
|
|
25510
26175
|
function stripHtml(html) {
|
|
@@ -26005,13 +26670,13 @@ Type /resume <id> to resume.` });
|
|
|
26005
26670
|
if (input === "/export") {
|
|
26006
26671
|
try {
|
|
26007
26672
|
const { writeFile: writeFile5, mkdir: mkdir5 } = await import("node:fs/promises");
|
|
26008
|
-
const { join:
|
|
26009
|
-
const { homedir:
|
|
26010
|
-
const { existsSync:
|
|
26011
|
-
const exportDir =
|
|
26012
|
-
if (!
|
|
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 });
|
|
26013
26678
|
const filename = `session-${agentLoop.sessionId}.md`;
|
|
26014
|
-
const filepath =
|
|
26679
|
+
const filepath = join48(exportDir, filename);
|
|
26015
26680
|
const lines = [
|
|
26016
26681
|
`# OpenJaw Agent Session ${agentLoop.sessionId}`,
|
|
26017
26682
|
`Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
@@ -26230,9 +26895,9 @@ ${list}` });
|
|
|
26230
26895
|
const cmd = lang === "python" ? "python" : lang === "node" ? "node" : "pwsh";
|
|
26231
26896
|
const args = lang === "python" ? ["-i", "-u"] : lang === "node" ? ["-i"] : ["-NoProfile", "-NoLogo"];
|
|
26232
26897
|
try {
|
|
26233
|
-
const { spawn:
|
|
26898
|
+
const { spawn: spawn8 } = await import("node:child_process");
|
|
26234
26899
|
if (replRef.current) replRef.current.kill();
|
|
26235
|
-
const proc =
|
|
26900
|
+
const proc = spawn8(cmd, args, {
|
|
26236
26901
|
stdio: ["pipe", "pipe", "pipe"],
|
|
26237
26902
|
windowsHide: true
|
|
26238
26903
|
});
|
|
@@ -26272,7 +26937,7 @@ ${list}` });
|
|
|
26272
26937
|
if (replRef.current && replLangRef.current && !input.startsWith("/")) {
|
|
26273
26938
|
const proc = replRef.current;
|
|
26274
26939
|
proc.stdin?.write(input + "\n");
|
|
26275
|
-
await new Promise((
|
|
26940
|
+
await new Promise((resolve6) => setTimeout(resolve6, 800));
|
|
26276
26941
|
const flush = proc._ojFlush;
|
|
26277
26942
|
const output = flush ? flush() : "";
|
|
26278
26943
|
if (output.trim()) {
|
|
@@ -26586,14 +27251,14 @@ Options: ${chunk.choices.join(" | ")}` : chunk.content;
|
|
|
26586
27251
|
waitingForAskUserRef.current = true;
|
|
26587
27252
|
setIsRunning(false);
|
|
26588
27253
|
setWaitingForAskUser(true);
|
|
26589
|
-
await new Promise((
|
|
27254
|
+
await new Promise((resolve6) => {
|
|
26590
27255
|
const checkInterval = setInterval(() => {
|
|
26591
27256
|
if (!waitingForAskUserRef.current || !agentLoop.isWaitingForAskUser) {
|
|
26592
27257
|
clearInterval(checkInterval);
|
|
26593
27258
|
waitingForAskUserRef.current = false;
|
|
26594
27259
|
setWaitingForAskUser(false);
|
|
26595
27260
|
setIsRunning(true);
|
|
26596
|
-
|
|
27261
|
+
resolve6();
|
|
26597
27262
|
}
|
|
26598
27263
|
}, 200);
|
|
26599
27264
|
});
|
|
@@ -26869,10 +27534,38 @@ __export(skill_tool_exports, {
|
|
|
26869
27534
|
createSkillTool: () => createSkillTool
|
|
26870
27535
|
});
|
|
26871
27536
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
|
|
27537
|
+
function resolveSkillTimeoutMs() {
|
|
27538
|
+
const raw = process.env["OPENJAW_SKILL_TIMEOUT_MS"];
|
|
27539
|
+
const parsed = raw ? Number(raw) : NaN;
|
|
27540
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
27541
|
+
return Math.min(Math.max(Math.floor(parsed), 1e4), 6e5);
|
|
27542
|
+
}
|
|
27543
|
+
return DEFAULT_SKILL_TIMEOUT_MS;
|
|
27544
|
+
}
|
|
27545
|
+
function resolveForkTimeoutMs(skill) {
|
|
27546
|
+
const configured = skill.meta.timeoutMs;
|
|
27547
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
|
|
27548
|
+
return Math.min(Math.max(Math.floor(configured), 1e4), 6e5);
|
|
27549
|
+
}
|
|
27550
|
+
return resolveSkillTimeoutMs();
|
|
27551
|
+
}
|
|
27552
|
+
async function withTimeout(promise, ms) {
|
|
27553
|
+
let timeoutId;
|
|
27554
|
+
try {
|
|
27555
|
+
return await Promise.race([
|
|
27556
|
+
promise,
|
|
27557
|
+
new Promise((resolve6) => {
|
|
27558
|
+
timeoutId = setTimeout(() => resolve6(SKILL_TIMEOUT), ms);
|
|
27559
|
+
})
|
|
27560
|
+
]);
|
|
27561
|
+
} finally {
|
|
27562
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
27563
|
+
}
|
|
27564
|
+
}
|
|
26872
27565
|
function createSkillTool(config, toolRegistry, systemPromptFn) {
|
|
26873
27566
|
return {
|
|
26874
27567
|
name: "invoke_skill",
|
|
26875
|
-
description: "Execute a skill from the available skills list. Skills
|
|
27568
|
+
description: "Execute a skill from the available skills list. Skills are for specialized multi-step workflows. Do not use for simple one-off lookups, quick current-news/factual questions, or tasks a direct built-in tool can answer in 1-2 calls.",
|
|
26876
27569
|
parameters: {
|
|
26877
27570
|
type: "object",
|
|
26878
27571
|
properties: {
|
|
@@ -26883,6 +27576,11 @@ function createSkillTool(config, toolRegistry, systemPromptFn) {
|
|
|
26883
27576
|
args: {
|
|
26884
27577
|
type: "string",
|
|
26885
27578
|
description: "Optional arguments or context for the skill"
|
|
27579
|
+
},
|
|
27580
|
+
mode: {
|
|
27581
|
+
type: "string",
|
|
27582
|
+
enum: ["inline", "fork"],
|
|
27583
|
+
description: "Execution mode. Default inline loads instructions into this turn; fork runs an isolated sub-agent for skills that explicitly support long-running execution."
|
|
26886
27584
|
}
|
|
26887
27585
|
},
|
|
26888
27586
|
required: ["skill"]
|
|
@@ -26890,6 +27588,7 @@ function createSkillTool(config, toolRegistry, systemPromptFn) {
|
|
|
26890
27588
|
execute: /* @__PURE__ */ __name(async (input) => {
|
|
26891
27589
|
const skillName = input.skill.trim().replace(/^\//, "");
|
|
26892
27590
|
const args = input.args || "";
|
|
27591
|
+
const requestedMode = input.mode === "fork" ? "fork" : input.mode === "inline" ? "inline" : void 0;
|
|
26893
27592
|
const skill = findSkill(skillName);
|
|
26894
27593
|
if (!skill) {
|
|
26895
27594
|
clearSkillRegistry();
|
|
@@ -26897,17 +27596,39 @@ function createSkillTool(config, toolRegistry, systemPromptFn) {
|
|
|
26897
27596
|
if (!retrySkill) {
|
|
26898
27597
|
return { success: false, error: `Skill "${skillName}" not found. Check /skills for available skills.` };
|
|
26899
27598
|
}
|
|
26900
|
-
return await executeSkill(retrySkill
|
|
27599
|
+
return await executeSkill(retrySkill, args, requestedMode, config, toolRegistry, systemPromptFn);
|
|
26901
27600
|
}
|
|
26902
|
-
return await executeSkill(skill
|
|
27601
|
+
return await executeSkill(skill, args, requestedMode, config, toolRegistry, systemPromptFn);
|
|
26903
27602
|
}, "execute")
|
|
26904
27603
|
};
|
|
26905
27604
|
}
|
|
26906
|
-
async function executeSkill(
|
|
27605
|
+
async function executeSkill(skill, args, requestedMode, config, toolRegistry, systemPromptFn) {
|
|
27606
|
+
const skillName = skill.name;
|
|
26907
27607
|
const body = loadSkillPrompt(skillName);
|
|
26908
27608
|
if (!body) {
|
|
26909
27609
|
return { success: false, error: `Could not load skill content for "${skillName}"` };
|
|
26910
27610
|
}
|
|
27611
|
+
const mode = requestedMode ?? skill.meta.execution ?? "inline";
|
|
27612
|
+
if (mode === "fork") {
|
|
27613
|
+
return await executeForkedSkill(skill, body, args, config, toolRegistry, systemPromptFn);
|
|
27614
|
+
}
|
|
27615
|
+
return loadInlineSkill(skill, body, args);
|
|
27616
|
+
}
|
|
27617
|
+
function loadInlineSkill(skill, body, args) {
|
|
27618
|
+
const skillBlock = args ? `${body}
|
|
27619
|
+
|
|
27620
|
+
# User instructions for this skill
|
|
27621
|
+
${args}` : body;
|
|
27622
|
+
return {
|
|
27623
|
+
success: true,
|
|
27624
|
+
skill: skill.name,
|
|
27625
|
+
mode: "inline",
|
|
27626
|
+
message: "Skill instructions loaded inline. Continue the task in the current conversation using these instructions and visible tools.",
|
|
27627
|
+
instructions: skillBlock
|
|
27628
|
+
};
|
|
27629
|
+
}
|
|
27630
|
+
async function executeForkedSkill(skill, body, args, config, toolRegistry, systemPromptFn) {
|
|
27631
|
+
const skillName = skill.name;
|
|
26911
27632
|
const baseSections = await systemPromptFn();
|
|
26912
27633
|
const staticSections = baseSections.slice(0, 4).filter(Boolean);
|
|
26913
27634
|
const skillPrompt = [...staticSections, body].join("\n\n");
|
|
@@ -26917,7 +27638,34 @@ async function executeSkill(skillName, args, config, toolRegistry, systemPromptF
|
|
|
26917
27638
|
let lastAnswer = "";
|
|
26918
27639
|
let thinking = "";
|
|
26919
27640
|
const allChunks = [];
|
|
26920
|
-
|
|
27641
|
+
const timeoutMs = resolveForkTimeoutMs(skill);
|
|
27642
|
+
const deadline = Date.now() + timeoutMs;
|
|
27643
|
+
const iterator = forkLoop.run(userMessage, skillPrompt)[Symbol.asyncIterator]();
|
|
27644
|
+
while (true) {
|
|
27645
|
+
const remaining = deadline - Date.now();
|
|
27646
|
+
if (remaining <= 0) {
|
|
27647
|
+
forkLoop.abort();
|
|
27648
|
+
return {
|
|
27649
|
+
success: false,
|
|
27650
|
+
skill: skillName,
|
|
27651
|
+
timeoutMs,
|
|
27652
|
+
error: `Skill "${skillName}" exceeded ${Math.round(timeoutMs / 1e3)}s. Use direct tools for simple requests, or rerun with narrower instructions.`,
|
|
27653
|
+
partial: lastAnswer || thinking || void 0
|
|
27654
|
+
};
|
|
27655
|
+
}
|
|
27656
|
+
const next = await withTimeout(iterator.next(), remaining);
|
|
27657
|
+
if (next === SKILL_TIMEOUT) {
|
|
27658
|
+
forkLoop.abort();
|
|
27659
|
+
return {
|
|
27660
|
+
success: false,
|
|
27661
|
+
skill: skillName,
|
|
27662
|
+
timeoutMs,
|
|
27663
|
+
error: `Skill "${skillName}" exceeded ${Math.round(timeoutMs / 1e3)}s. Use direct tools for simple requests, or rerun with narrower instructions.`,
|
|
27664
|
+
partial: lastAnswer || thinking || void 0
|
|
27665
|
+
};
|
|
27666
|
+
}
|
|
27667
|
+
if (next.done) break;
|
|
27668
|
+
const chunk = next.value;
|
|
26921
27669
|
if (chunk.type === "answer" && chunk.content) lastAnswer = chunk.content;
|
|
26922
27670
|
if (chunk.type === "thinking" && chunk.content) thinking += chunk.content;
|
|
26923
27671
|
allChunks.push({ type: chunk.type, content: chunk.content });
|
|
@@ -26925,14 +27673,14 @@ async function executeSkill(skillName, args, config, toolRegistry, systemPromptF
|
|
|
26925
27673
|
const result = lastAnswer || thinking || "Skill completed (no output)";
|
|
26926
27674
|
const compressed = result.length > 2e3 ? result.slice(0, 2e3) + `
|
|
26927
27675
|
...(${result.length} chars total, truncated)` : result;
|
|
26928
|
-
const
|
|
26929
|
-
if (
|
|
27676
|
+
const currentSkill = findSkill(skillName);
|
|
27677
|
+
if (currentSkill?.filePath && currentSkill.source === "user") {
|
|
26930
27678
|
const lessons = extractLessons(allChunks);
|
|
26931
27679
|
if (lessons) {
|
|
26932
|
-
appendLessonsLearned(
|
|
27680
|
+
appendLessonsLearned(currentSkill.filePath, lessons);
|
|
26933
27681
|
}
|
|
26934
27682
|
}
|
|
26935
|
-
return { success: true, skill: skillName, result: compressed };
|
|
27683
|
+
return { success: true, skill: skillName, mode: "fork", result: compressed };
|
|
26936
27684
|
} catch (err) {
|
|
26937
27685
|
return { success: false, skill: skillName, error: err instanceof Error ? err.message : String(err) };
|
|
26938
27686
|
}
|
|
@@ -26973,13 +27721,21 @@ function appendLessonsLearned(skillPath, lessons) {
|
|
|
26973
27721
|
} catch {
|
|
26974
27722
|
}
|
|
26975
27723
|
}
|
|
27724
|
+
var DEFAULT_SKILL_TIMEOUT_MS, SKILL_TIMEOUT;
|
|
26976
27725
|
var init_skill_tool = __esm({
|
|
26977
27726
|
"src/tools/skill-tool.ts"() {
|
|
26978
27727
|
"use strict";
|
|
26979
27728
|
init_agent_loop();
|
|
26980
27729
|
init_registry2();
|
|
27730
|
+
DEFAULT_SKILL_TIMEOUT_MS = 12e4;
|
|
27731
|
+
SKILL_TIMEOUT = /* @__PURE__ */ Symbol("skill-timeout");
|
|
27732
|
+
__name(resolveSkillTimeoutMs, "resolveSkillTimeoutMs");
|
|
27733
|
+
__name(resolveForkTimeoutMs, "resolveForkTimeoutMs");
|
|
27734
|
+
__name(withTimeout, "withTimeout");
|
|
26981
27735
|
__name(createSkillTool, "createSkillTool");
|
|
26982
27736
|
__name(executeSkill, "executeSkill");
|
|
27737
|
+
__name(loadInlineSkill, "loadInlineSkill");
|
|
27738
|
+
__name(executeForkedSkill, "executeForkedSkill");
|
|
26983
27739
|
__name(extractLessons, "extractLessons");
|
|
26984
27740
|
__name(appendLessonsLearned, "appendLessonsLearned");
|
|
26985
27741
|
}
|
|
@@ -27409,12 +28165,12 @@ var init_teams = __esm({
|
|
|
27409
28165
|
Options: ${chunk.choices.join(" | ")}` : "";
|
|
27410
28166
|
await this.sendToSelfChat(`\u2753 ${question}${choicesText}`);
|
|
27411
28167
|
if (!answerSent) await updateStatus("\u{1F916} OpenJaw Agent \u2014 \u2753 Waiting for your response...");
|
|
27412
|
-
const userReply = await new Promise((
|
|
27413
|
-
this._pendingReplyResolver =
|
|
28168
|
+
const userReply = await new Promise((resolve6) => {
|
|
28169
|
+
this._pendingReplyResolver = resolve6;
|
|
27414
28170
|
setTimeout(() => {
|
|
27415
|
-
if (this._pendingReplyResolver ===
|
|
28171
|
+
if (this._pendingReplyResolver === resolve6) {
|
|
27416
28172
|
this._pendingReplyResolver = null;
|
|
27417
|
-
|
|
28173
|
+
resolve6("[No response \u2014 timed out]");
|
|
27418
28174
|
}
|
|
27419
28175
|
}, 5 * 60 * 1e3);
|
|
27420
28176
|
});
|
|
@@ -27832,13 +28588,13 @@ var init_feishu = __esm({
|
|
|
27832
28588
|
if (stat2.isFile() && stat2.size < 30 * 1024 * 1024) {
|
|
27833
28589
|
const fileType = this.getFeishuFileType(fileName);
|
|
27834
28590
|
this.emit({ type: "system", content: `\u{1F4E4} Uploading to Feishu: ${fileName} (${(stat2.size / 1024).toFixed(0)}KB, type=${fileType})` });
|
|
27835
|
-
const
|
|
28591
|
+
const withTimeout2 = /* @__PURE__ */ __name((promise, ms, label) => Promise.race([
|
|
27836
28592
|
promise,
|
|
27837
28593
|
new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1e3}s`)), ms))
|
|
27838
28594
|
]), "withTimeout");
|
|
27839
28595
|
const { createReadStream: crs } = await import("node:fs");
|
|
27840
28596
|
const t0 = Date.now();
|
|
27841
|
-
const uploadResp = await
|
|
28597
|
+
const uploadResp = await withTimeout2(
|
|
27842
28598
|
this.client.im.file.create({
|
|
27843
28599
|
data: {
|
|
27844
28600
|
file_type: fileType,
|
|
@@ -27856,7 +28612,7 @@ var init_feishu = __esm({
|
|
|
27856
28612
|
this.emit({ type: "system", content: `\u{1F4E4} Upload done in ${(uploadMs / 1e3).toFixed(1)}s: code=${respCode}, file_key=${fileKey ? "\u2713" : "\u2717"}` });
|
|
27857
28613
|
if (fileKey) {
|
|
27858
28614
|
const t1 = Date.now();
|
|
27859
|
-
const sendResp = await
|
|
28615
|
+
const sendResp = await withTimeout2(
|
|
27860
28616
|
this.client.im.message.create({
|
|
27861
28617
|
params: { receive_id_type: "chat_id" },
|
|
27862
28618
|
data: {
|
|
@@ -27898,12 +28654,12 @@ ${err.stack?.split("\n").slice(0, 3).join("\n")}` : String(err);
|
|
|
27898
28654
|
}
|
|
27899
28655
|
/** Wait for the next user message (used by ask_user flow) */
|
|
27900
28656
|
waitForUserReply(_chatId) {
|
|
27901
|
-
return new Promise((
|
|
27902
|
-
this._pendingReplyResolver =
|
|
28657
|
+
return new Promise((resolve6) => {
|
|
28658
|
+
this._pendingReplyResolver = resolve6;
|
|
27903
28659
|
setTimeout(() => {
|
|
27904
|
-
if (this._pendingReplyResolver ===
|
|
28660
|
+
if (this._pendingReplyResolver === resolve6) {
|
|
27905
28661
|
this._pendingReplyResolver = null;
|
|
27906
|
-
|
|
28662
|
+
resolve6("[No response \u2014 timed out]");
|
|
27907
28663
|
}
|
|
27908
28664
|
}, 5 * 60 * 1e3);
|
|
27909
28665
|
});
|
|
@@ -28194,9 +28950,9 @@ var init_wechat2 = __esm({
|
|
|
28194
28950
|
try {
|
|
28195
28951
|
const qrTerminal = await import("qrcode-terminal");
|
|
28196
28952
|
const mod = qrTerminal.default || qrTerminal;
|
|
28197
|
-
const qrAscii = await new Promise((
|
|
28953
|
+
const qrAscii = await new Promise((resolve6, reject) => {
|
|
28198
28954
|
try {
|
|
28199
|
-
mod.generate(qrUrl, { small: true }, (out) =>
|
|
28955
|
+
mod.generate(qrUrl, { small: true }, (out) => resolve6(out));
|
|
28200
28956
|
} catch (e) {
|
|
28201
28957
|
reject(e);
|
|
28202
28958
|
}
|
|
@@ -28462,12 +29218,12 @@ Scan URL manually: ${qrUrl}` });
|
|
|
28462
29218
|
const choicesText = chunk.choices?.length ? `
|
|
28463
29219
|
\u9009\u9879: ${chunk.choices.join(" | ")}` : "";
|
|
28464
29220
|
await this.sendText(userId, `\u2753 ${question}${choicesText}`, contextToken);
|
|
28465
|
-
const userReply = await new Promise((
|
|
28466
|
-
this._pendingReplyResolver =
|
|
29221
|
+
const userReply = await new Promise((resolve6) => {
|
|
29222
|
+
this._pendingReplyResolver = resolve6;
|
|
28467
29223
|
setTimeout(() => {
|
|
28468
|
-
if (this._pendingReplyResolver ===
|
|
29224
|
+
if (this._pendingReplyResolver === resolve6) {
|
|
28469
29225
|
this._pendingReplyResolver = null;
|
|
28470
|
-
|
|
29226
|
+
resolve6("[No response \u2014 timed out]");
|
|
28471
29227
|
}
|
|
28472
29228
|
}, 5 * 60 * 1e3);
|
|
28473
29229
|
});
|
|
@@ -29968,8 +30724,8 @@ function createPromptCollector(bus, getSessionId) {
|
|
|
29968
30724
|
}, "emit");
|
|
29969
30725
|
const register = /* @__PURE__ */ __name((kind) => {
|
|
29970
30726
|
const requestId = randomUUID12();
|
|
29971
|
-
const promise = new Promise((
|
|
29972
|
-
pending.set(requestId, { kind, resolve:
|
|
30727
|
+
const promise = new Promise((resolve6) => {
|
|
30728
|
+
pending.set(requestId, { kind, resolve: resolve6 });
|
|
29973
30729
|
});
|
|
29974
30730
|
return { promise, requestId };
|
|
29975
30731
|
}, "register");
|
|
@@ -30949,11 +31705,11 @@ function detectTerminal() {
|
|
|
30949
31705
|
}
|
|
30950
31706
|
return process.env.TERM ?? null;
|
|
30951
31707
|
}
|
|
30952
|
-
function supportsOsc52Clipboard(
|
|
30953
|
-
return OSC52_CAPABLE_TERMINALS.includes(
|
|
31708
|
+
function supportsOsc52Clipboard(terminal2 = env.terminal) {
|
|
31709
|
+
return OSC52_CAPABLE_TERMINALS.includes(terminal2 ?? "");
|
|
30954
31710
|
}
|
|
30955
31711
|
function execFileNoThrow(file2, args, options = {}) {
|
|
30956
|
-
return new Promise((
|
|
31712
|
+
return new Promise((resolve6) => {
|
|
30957
31713
|
const child = spawn4(file2, args, {
|
|
30958
31714
|
cwd: options.useCwd ? process.cwd() : void 0,
|
|
30959
31715
|
env: options.env,
|
|
@@ -30976,13 +31732,13 @@ function execFileNoThrow(file2, args, options = {}) {
|
|
|
30976
31732
|
if (timer) {
|
|
30977
31733
|
clearTimeout(timer);
|
|
30978
31734
|
}
|
|
30979
|
-
|
|
31735
|
+
resolve6({ stdout, stderr, code: 1, error: String(error) });
|
|
30980
31736
|
});
|
|
30981
31737
|
child.on("close", (code) => {
|
|
30982
31738
|
if (timer) {
|
|
30983
31739
|
clearTimeout(timer);
|
|
30984
31740
|
}
|
|
30985
|
-
|
|
31741
|
+
resolve6({ stdout, stderr, code: timedOut ? 124 : code ?? 0 });
|
|
30986
31742
|
});
|
|
30987
31743
|
if (options.input) {
|
|
30988
31744
|
child.stdin?.write(options.input);
|
|
@@ -31014,7 +31770,7 @@ function shouldEmitClipboardSequence(env2 = process.env) {
|
|
|
31014
31770
|
}
|
|
31015
31771
|
return !!env2["SSH_CONNECTION"] || !env2["TMUX"] && !env2["STY"];
|
|
31016
31772
|
}
|
|
31017
|
-
function shouldUseNativeClipboard(env2 = process.env,
|
|
31773
|
+
function shouldUseNativeClipboard(env2 = process.env, terminal2 = env.terminal) {
|
|
31018
31774
|
if (env2.SSH_CONNECTION) {
|
|
31019
31775
|
return false;
|
|
31020
31776
|
}
|
|
@@ -31024,7 +31780,7 @@ function shouldUseNativeClipboard(env2 = process.env, terminal = env.terminal) {
|
|
|
31024
31780
|
if (!shouldEmitClipboardSequence(env2)) {
|
|
31025
31781
|
return true;
|
|
31026
31782
|
}
|
|
31027
|
-
return !supportsOsc52Clipboard(
|
|
31783
|
+
return !supportsOsc52Clipboard(terminal2);
|
|
31028
31784
|
}
|
|
31029
31785
|
function tmuxPassthrough(payload) {
|
|
31030
31786
|
return `${ESC}Ptmux;${payload.replaceAll(ESC, ESC + ESC)}${ST}`;
|
|
@@ -32460,7 +33216,7 @@ function needsAltScreenResizeScrollbackClear(env2 = process.env) {
|
|
|
32460
33216
|
function supportsExtendedKeys() {
|
|
32461
33217
|
return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? "");
|
|
32462
33218
|
}
|
|
32463
|
-
function writeDiffToTerminal(
|
|
33219
|
+
function writeDiffToTerminal(terminal2, diff2, skipSyncMarkers = false, onDrain) {
|
|
32464
33220
|
if (diff2.length === 0) {
|
|
32465
33221
|
return { bytes: 0, backpressure: false };
|
|
32466
33222
|
}
|
|
@@ -32505,7 +33261,7 @@ function writeDiffToTerminal(terminal, diff2, skipSyncMarkers = false, onDrain)
|
|
|
32505
33261
|
if (useSync) {
|
|
32506
33262
|
buffer += ESU;
|
|
32507
33263
|
}
|
|
32508
|
-
const wrote = onDrain ?
|
|
33264
|
+
const wrote = onDrain ? terminal2.stdout.write(buffer, () => onDrain()) : terminal2.stdout.write(buffer);
|
|
32509
33265
|
return { bytes: Buffer.byteLength(buffer, "utf8"), backpressure: !wrote };
|
|
32510
33266
|
}
|
|
32511
33267
|
function logForDebugging(_message, _options = {}) {
|
|
@@ -35955,8 +36711,8 @@ function setTerminalFocused(v) {
|
|
|
35955
36711
|
cb();
|
|
35956
36712
|
}
|
|
35957
36713
|
if (!v) {
|
|
35958
|
-
for (const
|
|
35959
|
-
|
|
36714
|
+
for (const resolve6 of resolvers) {
|
|
36715
|
+
resolve6();
|
|
35960
36716
|
}
|
|
35961
36717
|
resolvers.clear();
|
|
35962
36718
|
}
|
|
@@ -42194,11 +42950,11 @@ $ npm install --save-dev react-devtools-core
|
|
|
42194
42950
|
* and the terminal doesn't respond, the promise remains pending.
|
|
42195
42951
|
*/
|
|
42196
42952
|
send(query) {
|
|
42197
|
-
return new Promise((
|
|
42953
|
+
return new Promise((resolve6) => {
|
|
42198
42954
|
this.queue.push({
|
|
42199
42955
|
kind: "query",
|
|
42200
42956
|
match: query.match,
|
|
42201
|
-
resolve: /* @__PURE__ */ __name((r) =>
|
|
42957
|
+
resolve: /* @__PURE__ */ __name((r) => resolve6(r), "resolve")
|
|
42202
42958
|
});
|
|
42203
42959
|
this.stdout.write(query.request);
|
|
42204
42960
|
});
|
|
@@ -42213,8 +42969,8 @@ $ npm install --save-dev react-devtools-core
|
|
|
42213
42969
|
* Safe to call with no pending queries — still waits for a round-trip.
|
|
42214
42970
|
*/
|
|
42215
42971
|
flush() {
|
|
42216
|
-
return new Promise((
|
|
42217
|
-
this.queue.push({ kind: "sentinel", resolve:
|
|
42972
|
+
return new Promise((resolve6) => {
|
|
42973
|
+
this.queue.push({ kind: "sentinel", resolve: resolve6 });
|
|
42218
42974
|
this.stdout.write(SENTINEL);
|
|
42219
42975
|
});
|
|
42220
42976
|
}
|
|
@@ -44694,8 +45450,8 @@ $ npm install --save-dev react-devtools-core
|
|
|
44694
45450
|
}
|
|
44695
45451
|
}
|
|
44696
45452
|
async waitUntilExit() {
|
|
44697
|
-
this.exitPromise ||= new Promise((
|
|
44698
|
-
this.resolveExitPromise =
|
|
45453
|
+
this.exitPromise ||= new Promise((resolve6, reject) => {
|
|
45454
|
+
this.resolveExitPromise = resolve6;
|
|
44699
45455
|
this.rejectExitPromise = reject;
|
|
44700
45456
|
});
|
|
44701
45457
|
return this.exitPromise;
|
|
@@ -45186,10 +45942,10 @@ async function writeClipboardText(text, platform2 = process.platform, start = sp
|
|
|
45186
45942
|
const candidates = writeClipboardCommands(platform2, env2);
|
|
45187
45943
|
for (const { cmd, args } of candidates) {
|
|
45188
45944
|
try {
|
|
45189
|
-
const ok = await new Promise((
|
|
45945
|
+
const ok = await new Promise((resolve6) => {
|
|
45190
45946
|
const child = start(cmd, [...args], { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
|
|
45191
|
-
child.once("error", () =>
|
|
45192
|
-
child.once("close", (code) =>
|
|
45947
|
+
child.once("error", () => resolve6(false));
|
|
45948
|
+
child.once("close", (code) => resolve6(code === 0));
|
|
45193
45949
|
child.stdin?.end(text);
|
|
45194
45950
|
});
|
|
45195
45951
|
if (ok) {
|
|
@@ -45248,8 +46004,8 @@ async function readOsc52Clipboard(querier, timeoutMs = 500) {
|
|
|
45248
46004
|
if (!querier) {
|
|
45249
46005
|
return null;
|
|
45250
46006
|
}
|
|
45251
|
-
const timeout = new Promise((
|
|
45252
|
-
setTimeout(() =>
|
|
46007
|
+
const timeout = new Promise((resolve6) => {
|
|
46008
|
+
setTimeout(() => resolve6(void 0), timeoutMs);
|
|
45253
46009
|
});
|
|
45254
46010
|
const query = querier.send({
|
|
45255
46011
|
request: buildOsc52ClipboardQuery(),
|
|
@@ -45406,12 +46162,12 @@ async function backupFile(filePath, ops) {
|
|
|
45406
46162
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
45407
46163
|
await ops.copyFile(filePath, `${filePath}.backup.${stamp}`);
|
|
45408
46164
|
}
|
|
45409
|
-
async function configureTerminalKeybindings(
|
|
46165
|
+
async function configureTerminalKeybindings(terminal2, options) {
|
|
45410
46166
|
const env2 = options?.env ?? process.env;
|
|
45411
46167
|
const platform2 = options?.platform ?? process.platform;
|
|
45412
46168
|
const homeDir = options?.homeDir ?? homedir25();
|
|
45413
46169
|
const ops = { ...DEFAULT_FILE_OPS, ...options?.fileOps ?? {} };
|
|
45414
|
-
const meta = TERMINAL_META[
|
|
46170
|
+
const meta = TERMINAL_META[terminal2];
|
|
45415
46171
|
if (isRemoteShellSession(env2)) {
|
|
45416
46172
|
return {
|
|
45417
46173
|
success: false,
|
|
@@ -45606,6 +46362,7 @@ var init_overlayStore = __esm({
|
|
|
45606
46362
|
buildOverlayState = /* @__PURE__ */ __name(() => ({
|
|
45607
46363
|
agents: false,
|
|
45608
46364
|
agentsInitialHistoryIndex: 0,
|
|
46365
|
+
agentsWorkflowId: null,
|
|
45609
46366
|
approval: null,
|
|
45610
46367
|
clarify: null,
|
|
45611
46368
|
confirm: null,
|
|
@@ -45628,6 +46385,7 @@ var init_overlayStore = __esm({
|
|
|
45628
46385
|
...buildOverlayState(),
|
|
45629
46386
|
agents: $overlayState.get().agents,
|
|
45630
46387
|
agentsInitialHistoryIndex: $overlayState.get().agentsInitialHistoryIndex,
|
|
46388
|
+
agentsWorkflowId: $overlayState.get().agentsWorkflowId,
|
|
45631
46389
|
mcpHub: $overlayState.get().mcpHub,
|
|
45632
46390
|
modelPicker: $overlayState.get().modelPicker,
|
|
45633
46391
|
modelPickerMode: $overlayState.get().modelPickerMode,
|
|
@@ -46427,13 +47185,25 @@ var init_debug = __esm({
|
|
|
46427
47185
|
}
|
|
46428
47186
|
});
|
|
46429
47187
|
|
|
47188
|
+
// src/app/workflowStore.ts
|
|
47189
|
+
import { atom as atom3 } from "nanostores";
|
|
47190
|
+
var $workflowSnapshots, setWorkflowSnapshot;
|
|
47191
|
+
var init_workflowStore = __esm({
|
|
47192
|
+
"src/app/workflowStore.ts"() {
|
|
47193
|
+
"use strict";
|
|
47194
|
+
$workflowSnapshots = atom3({});
|
|
47195
|
+
setWorkflowSnapshot = /* @__PURE__ */ __name((snapshot) => $workflowSnapshots.set({ ...$workflowSnapshots.get(), [snapshot.id]: snapshot }), "setWorkflowSnapshot");
|
|
47196
|
+
}
|
|
47197
|
+
});
|
|
47198
|
+
|
|
46430
47199
|
// src/app/slash/commands/openjaw.ts
|
|
46431
|
-
var fmt, money, stub, eventLine, parseScheduleInput, showUsage, openjawCommands;
|
|
47200
|
+
var fmt, money, stub, eventLine, parseScheduleInput, asWorkflowSnapshot, workflowLine, workflowRows, showUsage, openjawCommands;
|
|
46432
47201
|
var init_openjaw = __esm({
|
|
46433
47202
|
"src/app/slash/commands/openjaw.ts"() {
|
|
46434
47203
|
"use strict";
|
|
46435
47204
|
init_usage();
|
|
46436
47205
|
init_overlayStore();
|
|
47206
|
+
init_workflowStore();
|
|
46437
47207
|
fmt = /* @__PURE__ */ __name((n) => (n ?? 0).toLocaleString(), "fmt");
|
|
46438
47208
|
money = /* @__PURE__ */ __name((n) => `$${(n ?? 0).toFixed(4)}`, "money");
|
|
46439
47209
|
stub = /* @__PURE__ */ __name((ctx, command, message) => {
|
|
@@ -46473,6 +47243,14 @@ var init_openjaw = __esm({
|
|
|
46473
47243
|
}
|
|
46474
47244
|
return null;
|
|
46475
47245
|
}, "parseScheduleInput");
|
|
47246
|
+
asWorkflowSnapshot = /* @__PURE__ */ __name((run) => run ? run : null, "asWorkflowSnapshot");
|
|
47247
|
+
workflowLine = /* @__PURE__ */ __name((run) => {
|
|
47248
|
+
const active = run.workers.filter((worker) => worker.status === "running" || worker.status === "queued").length;
|
|
47249
|
+
const done = run.workers.filter((worker) => worker.status === "completed").length;
|
|
47250
|
+
return `${run.id} \xB7 ${run.status} \xB7 ${done}/${run.workers.length} done${active ? ` \xB7 ${active} active` : ""} \xB7 ${run.goal}`;
|
|
47251
|
+
}, "workflowLine");
|
|
47252
|
+
workflowRows = /* @__PURE__ */ __name((runs) => runs.map((run) => [run.id, `${run.status} \xB7 ${run.workerCount}/${run.plannedWorkerCount} workers
|
|
47253
|
+
${run.goal}`]), "workflowRows");
|
|
46476
47254
|
showUsage = /* @__PURE__ */ __name((ctx, render2) => {
|
|
46477
47255
|
ctx.gateway.rpc("session.usage", { session_id: ctx.sid }).then(ctx.guarded(render2)).catch(ctx.guardedErr);
|
|
46478
47256
|
}, "showUsage");
|
|
@@ -46494,6 +47272,10 @@ var init_openjaw = __esm({
|
|
|
46494
47272
|
if (!r.messages?.length) {
|
|
46495
47273
|
ctx.transcript.sys("connect: no output");
|
|
46496
47274
|
}
|
|
47275
|
+
if (!ctx.sid && r.config_updates) {
|
|
47276
|
+
ctx.transcript.sys("provider connected \u2014 starting OpenJaw session\u2026");
|
|
47277
|
+
ctx.session.newSession();
|
|
47278
|
+
}
|
|
46497
47279
|
})
|
|
46498
47280
|
).catch(ctx.guardedErr);
|
|
46499
47281
|
}, "run")
|
|
@@ -46587,6 +47369,70 @@ var init_openjaw = __esm({
|
|
|
46587
47369
|
).catch(ctx.guardedErr);
|
|
46588
47370
|
}, "run")
|
|
46589
47371
|
},
|
|
47372
|
+
{
|
|
47373
|
+
aliases: ["wf"],
|
|
47374
|
+
help: "start or inspect an advisory dynamic workflow",
|
|
47375
|
+
name: "workflow",
|
|
47376
|
+
usage: "/workflow <goal> | /workflow status [id] | list | show [id] | cancel <id>",
|
|
47377
|
+
run: /* @__PURE__ */ __name((arg, ctx) => {
|
|
47378
|
+
const text = arg.trim();
|
|
47379
|
+
if (!text) {
|
|
47380
|
+
return ctx.transcript.sys("usage: /workflow <goal> | /workflow status [id] | list | show [id] | cancel <id>");
|
|
47381
|
+
}
|
|
47382
|
+
const [subRaw, ...rest] = text.split(/\s+/);
|
|
47383
|
+
const sub = subRaw?.toLowerCase() ?? "";
|
|
47384
|
+
const remainder = rest.join(" ").trim();
|
|
47385
|
+
if (sub === "status") {
|
|
47386
|
+
return ctx.gateway.rpc("workflow.status", { id: remainder, session_id: ctx.sid }).then(
|
|
47387
|
+
ctx.guarded((r) => {
|
|
47388
|
+
const snapshot = asWorkflowSnapshot(r.run);
|
|
47389
|
+
if (!snapshot) return ctx.transcript.sys(r.error || "no workflows yet");
|
|
47390
|
+
setWorkflowSnapshot(snapshot);
|
|
47391
|
+
patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0, agentsWorkflowId: snapshot.id });
|
|
47392
|
+
ctx.transcript.sys(`workflow status \xB7 ${workflowLine(snapshot)}`);
|
|
47393
|
+
})
|
|
47394
|
+
).catch(ctx.guardedErr);
|
|
47395
|
+
}
|
|
47396
|
+
if (sub === "list" || sub === "ls") {
|
|
47397
|
+
return ctx.gateway.rpc("workflow.list", { limit: 30, session_id: ctx.sid }).then(
|
|
47398
|
+
ctx.guarded((r) => {
|
|
47399
|
+
const runs = r.runs ?? [];
|
|
47400
|
+
if (!runs.length) return ctx.transcript.sys("no workflows yet");
|
|
47401
|
+
ctx.transcript.panel("Workflows", [{ rows: workflowRows(runs) }]);
|
|
47402
|
+
})
|
|
47403
|
+
).catch(ctx.guardedErr);
|
|
47404
|
+
}
|
|
47405
|
+
if (sub === "show") {
|
|
47406
|
+
return ctx.gateway.rpc("workflow.show", { id: remainder, session_id: ctx.sid }).then(
|
|
47407
|
+
ctx.guarded((r) => {
|
|
47408
|
+
const snapshot = asWorkflowSnapshot(r.run);
|
|
47409
|
+
if (!snapshot) return ctx.transcript.sys(r.error || "no workflows yet");
|
|
47410
|
+
setWorkflowSnapshot(snapshot);
|
|
47411
|
+
ctx.transcript.page(snapshot.summary || workflowLine(snapshot), `Workflow ${snapshot.id}`);
|
|
47412
|
+
})
|
|
47413
|
+
).catch(ctx.guardedErr);
|
|
47414
|
+
}
|
|
47415
|
+
if (sub === "cancel" || sub === "stop") {
|
|
47416
|
+
if (!remainder) return ctx.transcript.sys("usage: /workflow cancel <runId|workerId>");
|
|
47417
|
+
return ctx.gateway.rpc("workflow.cancel", { id: remainder, session_id: ctx.sid }).then(
|
|
47418
|
+
ctx.guarded((r) => {
|
|
47419
|
+
const snapshot = asWorkflowSnapshot(r.run);
|
|
47420
|
+
if (snapshot) setWorkflowSnapshot(snapshot);
|
|
47421
|
+
ctx.transcript.sys(r.ok ? `workflow cancel requested: ${remainder}` : r.error || `not found: ${remainder}`);
|
|
47422
|
+
})
|
|
47423
|
+
).catch(ctx.guardedErr);
|
|
47424
|
+
}
|
|
47425
|
+
return ctx.gateway.rpc("workflow.start", { goal: text, session_id: ctx.sid }).then(
|
|
47426
|
+
ctx.guarded((r) => {
|
|
47427
|
+
const snapshot = asWorkflowSnapshot(r.run);
|
|
47428
|
+
if (!snapshot) return ctx.transcript.sys(r.error || "workflow failed to start");
|
|
47429
|
+
setWorkflowSnapshot(snapshot);
|
|
47430
|
+
patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0, agentsWorkflowId: snapshot.id });
|
|
47431
|
+
ctx.transcript.sys(`workflow ${snapshot.id} started \xB7 ${snapshot.plannedWorkerCount} workers \xB7 concurrency ${snapshot.concurrency}`);
|
|
47432
|
+
})
|
|
47433
|
+
).catch(ctx.guardedErr);
|
|
47434
|
+
}, "run")
|
|
47435
|
+
},
|
|
46590
47436
|
{
|
|
46591
47437
|
help: "schedule a recurring prompt",
|
|
46592
47438
|
name: "schedule",
|
|
@@ -46719,7 +47565,7 @@ var init_openjaw = __esm({
|
|
|
46719
47565
|
});
|
|
46720
47566
|
|
|
46721
47567
|
// src/app/delegationStore.ts
|
|
46722
|
-
import { atom as
|
|
47568
|
+
import { atom as atom4 } from "nanostores";
|
|
46723
47569
|
var buildState, $delegationState, getDelegationState, patchDelegationState, $overlaySectionsOpen, toggleOverlaySection, applyDelegationStatus;
|
|
46724
47570
|
var init_delegationStore = __esm({
|
|
46725
47571
|
"src/app/delegationStore.ts"() {
|
|
@@ -46730,10 +47576,10 @@ var init_delegationStore = __esm({
|
|
|
46730
47576
|
paused: false,
|
|
46731
47577
|
updatedAt: null
|
|
46732
47578
|
}), "buildState");
|
|
46733
|
-
$delegationState =
|
|
47579
|
+
$delegationState = atom4(buildState());
|
|
46734
47580
|
getDelegationState = /* @__PURE__ */ __name(() => $delegationState.get(), "getDelegationState");
|
|
46735
47581
|
patchDelegationState = /* @__PURE__ */ __name((next) => $delegationState.set({ ...$delegationState.get(), ...next }), "patchDelegationState");
|
|
46736
|
-
$overlaySectionsOpen =
|
|
47582
|
+
$overlaySectionsOpen = atom4({});
|
|
46737
47583
|
toggleOverlaySection = /* @__PURE__ */ __name((title, defaultOpen) => {
|
|
46738
47584
|
const state = $overlaySectionsOpen.get();
|
|
46739
47585
|
const current = title in state ? state[title] : defaultOpen;
|
|
@@ -46759,7 +47605,7 @@ var init_delegationStore = __esm({
|
|
|
46759
47605
|
});
|
|
46760
47606
|
|
|
46761
47607
|
// src/app/spawnHistoryStore.ts
|
|
46762
|
-
import { atom as
|
|
47608
|
+
import { atom as atom5 } from "nanostores";
|
|
46763
47609
|
function summarizeLabel(subagents) {
|
|
46764
47610
|
const top = subagents.filter((s) => s.parentId == null || subagents.every((o) => o.id !== s.parentId)).slice(0, 2).map((s) => s.goal || "subagent").join(" \xB7 ");
|
|
46765
47611
|
return top || `${subagents.length} agent${subagents.length === 1 ? "" : "s"}`;
|
|
@@ -46802,8 +47648,8 @@ var init_spawnHistoryStore = __esm({
|
|
|
46802
47648
|
"src/app/spawnHistoryStore.ts"() {
|
|
46803
47649
|
"use strict";
|
|
46804
47650
|
HISTORY_LIMIT = 10;
|
|
46805
|
-
$spawnHistory =
|
|
46806
|
-
$spawnDiff =
|
|
47651
|
+
$spawnHistory = atom5([]);
|
|
47652
|
+
$spawnDiff = atom5(null);
|
|
46807
47653
|
getSpawnHistory = /* @__PURE__ */ __name(() => $spawnHistory.get(), "getSpawnHistory");
|
|
46808
47654
|
clearDiffPair = /* @__PURE__ */ __name(() => $spawnDiff.set(null), "clearDiffPair");
|
|
46809
47655
|
setDiffPair = /* @__PURE__ */ __name((pair) => $spawnDiff.set(pair), "setDiffPair");
|
|
@@ -47113,15 +47959,15 @@ var init_ops = __esm({
|
|
|
47113
47959
|
}
|
|
47114
47960
|
const [a, b] = parts;
|
|
47115
47961
|
const history = getSpawnHistory();
|
|
47116
|
-
const
|
|
47962
|
+
const resolve6 = /* @__PURE__ */ __name((token) => {
|
|
47117
47963
|
const n = parseInt(token, 10);
|
|
47118
47964
|
if (Number.isFinite(n) && n >= 1 && n <= history.length) {
|
|
47119
47965
|
return history[n - 1] ?? null;
|
|
47120
47966
|
}
|
|
47121
47967
|
return null;
|
|
47122
47968
|
}, "resolve");
|
|
47123
|
-
const baseline =
|
|
47124
|
-
const candidate =
|
|
47969
|
+
const baseline = resolve6(a);
|
|
47970
|
+
const candidate = resolve6(b);
|
|
47125
47971
|
if (!baseline || !candidate) {
|
|
47126
47972
|
return ctx.transcript.sys(`replay-diff: could not resolve indices \xB7 history has ${history.length} entries`);
|
|
47127
47973
|
}
|
|
@@ -47994,76 +48840,20 @@ var init_session2 = __esm({
|
|
|
47994
48840
|
}
|
|
47995
48841
|
});
|
|
47996
48842
|
|
|
47997
|
-
// src/lib/externalCli.ts
|
|
47998
|
-
import { spawn as spawn6 } from "node:child_process";
|
|
47999
|
-
var resolveHermesBin, launchHermesCommand;
|
|
48000
|
-
var init_externalCli = __esm({
|
|
48001
|
-
"src/lib/externalCli.ts"() {
|
|
48002
|
-
"use strict";
|
|
48003
|
-
resolveHermesBin = /* @__PURE__ */ __name(() => process.env.OPENJAW_BIN?.trim() || "hermes", "resolveHermesBin");
|
|
48004
|
-
launchHermesCommand = /* @__PURE__ */ __name((args) => new Promise((resolve5) => {
|
|
48005
|
-
const child = spawn6(resolveHermesBin(), args, { stdio: "inherit" });
|
|
48006
|
-
child.on("error", (err) => resolve5({ code: null, error: err.message }));
|
|
48007
|
-
child.on("exit", (code) => resolve5({ code }));
|
|
48008
|
-
}), "launchHermesCommand");
|
|
48009
|
-
}
|
|
48010
|
-
});
|
|
48011
|
-
|
|
48012
|
-
// src/app/setupHandoff.ts
|
|
48013
|
-
async function runExternalSetup({ args, ctx, done, launcher, suspend }) {
|
|
48014
|
-
const { gateway, session, transcript } = ctx;
|
|
48015
|
-
transcript.sys(`launching \`hermes ${args.join(" ")}\`\u2026`);
|
|
48016
|
-
patchUiState({ status: "setup running\u2026" });
|
|
48017
|
-
let result = { code: null };
|
|
48018
|
-
await suspend(async () => {
|
|
48019
|
-
result = await launcher(args);
|
|
48020
|
-
});
|
|
48021
|
-
if (result.error) {
|
|
48022
|
-
transcript.sys(`error launching hermes: ${result.error}`);
|
|
48023
|
-
patchUiState({ status: "setup required" });
|
|
48024
|
-
return;
|
|
48025
|
-
}
|
|
48026
|
-
if (result.code !== 0) {
|
|
48027
|
-
transcript.sys(`hermes ${args[0]} exited with code ${result.code}`);
|
|
48028
|
-
patchUiState({ status: "setup required" });
|
|
48029
|
-
return;
|
|
48030
|
-
}
|
|
48031
|
-
const setup = await gateway.rpc("setup.status", {});
|
|
48032
|
-
if (setup?.provider_configured === false) {
|
|
48033
|
-
transcript.sys("still no provider configured");
|
|
48034
|
-
patchUiState({ status: "setup required" });
|
|
48035
|
-
return;
|
|
48036
|
-
}
|
|
48037
|
-
transcript.sys(done);
|
|
48038
|
-
session.newSession();
|
|
48039
|
-
}
|
|
48040
|
-
var init_setupHandoff = __esm({
|
|
48041
|
-
"src/app/setupHandoff.ts"() {
|
|
48042
|
-
"use strict";
|
|
48043
|
-
init_uiStore();
|
|
48044
|
-
__name(runExternalSetup, "runExternalSetup");
|
|
48045
|
-
}
|
|
48046
|
-
});
|
|
48047
|
-
|
|
48048
48843
|
// src/app/slash/commands/setup.ts
|
|
48049
48844
|
var setupCommands;
|
|
48050
48845
|
var init_setup = __esm({
|
|
48051
48846
|
"src/app/slash/commands/setup.ts"() {
|
|
48052
48847
|
"use strict";
|
|
48053
|
-
|
|
48054
|
-
init_externalCli();
|
|
48055
|
-
init_setupHandoff();
|
|
48848
|
+
init_overlayStore();
|
|
48056
48849
|
setupCommands = [
|
|
48057
48850
|
{
|
|
48058
|
-
help: "run
|
|
48851
|
+
help: "first-run setup help; opens provider connection picker",
|
|
48059
48852
|
name: "setup",
|
|
48060
|
-
run: /* @__PURE__ */ __name((
|
|
48061
|
-
|
|
48062
|
-
|
|
48063
|
-
|
|
48064
|
-
launcher: launchHermesCommand,
|
|
48065
|
-
suspend: withInkSuspended
|
|
48066
|
-
}), "run")
|
|
48853
|
+
run: /* @__PURE__ */ __name((_arg, ctx) => {
|
|
48854
|
+
ctx.transcript.sys("OpenJaw setup: run /connect to set up a provider, then /model to choose a model.");
|
|
48855
|
+
patchOverlayState({ modelPicker: true, modelPickerMode: "connect" });
|
|
48856
|
+
}, "run")
|
|
48067
48857
|
}
|
|
48068
48858
|
];
|
|
48069
48859
|
}
|
|
@@ -48219,17 +49009,856 @@ var init_catalog = __esm({
|
|
|
48219
49009
|
}
|
|
48220
49010
|
});
|
|
48221
49011
|
|
|
48222
|
-
// src/
|
|
48223
|
-
|
|
48224
|
-
|
|
48225
|
-
|
|
49012
|
+
// src/workflows/planner.ts
|
|
49013
|
+
function resolveDynamicWorkflowConfig(config) {
|
|
49014
|
+
const raw = config.features?.dynamic_workflows ?? {};
|
|
49015
|
+
return {
|
|
49016
|
+
enabled: raw.enabled ?? DEFAULT_DYNAMIC_WORKFLOW_CONFIG.enabled,
|
|
49017
|
+
plannerMode: "adaptive",
|
|
49018
|
+
hardMaxWorkers: clampInt(raw.hard_max_workers, DEFAULT_DYNAMIC_WORKFLOW_CONFIG.hardMaxWorkers, 1, 1e4),
|
|
49019
|
+
hardMaxConcurrentWorkers: clampInt(
|
|
49020
|
+
raw.hard_max_concurrent_workers,
|
|
49021
|
+
DEFAULT_DYNAMIC_WORKFLOW_CONFIG.hardMaxConcurrentWorkers,
|
|
49022
|
+
1,
|
|
49023
|
+
2048
|
|
49024
|
+
),
|
|
49025
|
+
workerTimeoutMs: clampInt(raw.worker_timeout_ms, DEFAULT_DYNAMIC_WORKFLOW_CONFIG.workerTimeoutMs, 1e4, 36e5),
|
|
49026
|
+
persistHistory: raw.persist_history ?? DEFAULT_DYNAMIC_WORKFLOW_CONFIG.persistHistory
|
|
49027
|
+
};
|
|
49028
|
+
}
|
|
49029
|
+
function initialConcurrency(plannedWorkers, limits) {
|
|
49030
|
+
if (plannedWorkers <= 0) return 0;
|
|
49031
|
+
return Math.min(Math.ceil(Math.sqrt(plannedWorkers) * 2), plannedWorkers, limits.hardMaxConcurrentWorkers);
|
|
49032
|
+
}
|
|
49033
|
+
function adjustConcurrency(current, plannedWorkers, limits, event) {
|
|
49034
|
+
if (plannedWorkers <= 0) return 0;
|
|
49035
|
+
if (event === "rate_limited") return Math.max(1, Math.floor(current / 2));
|
|
49036
|
+
return Math.min(current + 1, plannedWorkers, limits.hardMaxConcurrentWorkers);
|
|
49037
|
+
}
|
|
49038
|
+
function planWorkflow(goal, runId, limits) {
|
|
49039
|
+
const taskGoals = deriveTaskGoals(goal);
|
|
49040
|
+
const specs = [];
|
|
49041
|
+
const maxWorkers = limits.hardMaxWorkers;
|
|
49042
|
+
for (const taskGoal of taskGoals) {
|
|
49043
|
+
if (specs.length >= maxWorkers) break;
|
|
49044
|
+
const id = `${runId}-w${specs.length + 1}`;
|
|
49045
|
+
specs.push({
|
|
49046
|
+
depth: 0,
|
|
49047
|
+
goal: taskGoal,
|
|
49048
|
+
id,
|
|
49049
|
+
index: specs.length,
|
|
49050
|
+
parentId: null,
|
|
49051
|
+
prompt: buildTaskPrompt(goal, taskGoal),
|
|
49052
|
+
role: "task"
|
|
49053
|
+
});
|
|
49054
|
+
}
|
|
49055
|
+
const taskSpecs = [...specs];
|
|
49056
|
+
const verifierEvery = taskSpecs.length >= 8 ? 4 : 3;
|
|
49057
|
+
for (let i = 0; i < taskSpecs.length && specs.length < maxWorkers; i += verifierEvery) {
|
|
49058
|
+
const batch = taskSpecs.slice(i, i + verifierEvery);
|
|
49059
|
+
const id = `${runId}-w${specs.length + 1}`;
|
|
49060
|
+
specs.push({
|
|
49061
|
+
depth: 1,
|
|
49062
|
+
dependsOn: batch.map((item) => item.id),
|
|
49063
|
+
goal: `Verify findings from workers ${batch.map((item) => item.index + 1).join(", ")}`,
|
|
49064
|
+
id,
|
|
49065
|
+
index: specs.length,
|
|
49066
|
+
parentId: batch[0]?.id ?? null,
|
|
49067
|
+
prompt: buildVerifierPrompt(goal, batch),
|
|
49068
|
+
role: "verifier"
|
|
49069
|
+
});
|
|
49070
|
+
}
|
|
49071
|
+
return { concurrency: initialConcurrency(specs.length, limits), specs };
|
|
49072
|
+
}
|
|
49073
|
+
function deriveTaskGoals(goal) {
|
|
49074
|
+
const explicit = extractExplicitTasks(goal);
|
|
49075
|
+
const words = goal.trim().split(/\s+/).filter(Boolean).length;
|
|
49076
|
+
const complexity = Math.min(12, Math.max(3, Math.ceil(words / 35)));
|
|
49077
|
+
const inferred = inferLanes(goal);
|
|
49078
|
+
const seeds = explicit.length > 1 ? explicit : inferred;
|
|
49079
|
+
const out = [];
|
|
49080
|
+
for (const item of seeds) {
|
|
49081
|
+
pushUnique(out, item);
|
|
49082
|
+
}
|
|
49083
|
+
while (out.length < complexity) {
|
|
49084
|
+
const next = DEFAULT_LANES[out.length % DEFAULT_LANES.length];
|
|
49085
|
+
pushUnique(out, next);
|
|
49086
|
+
if (out.length >= DEFAULT_LANES.length && explicit.length <= 1) break;
|
|
49087
|
+
}
|
|
49088
|
+
return out.slice(0, Math.max(1, out.length));
|
|
49089
|
+
}
|
|
49090
|
+
function extractExplicitTasks(goal) {
|
|
49091
|
+
return goal.split(/\r?\n/).map((line) => line.trim().replace(/^[-*+]\s+/, "").replace(/^\d+[.)]\s+/, "").trim()).filter((line) => line.length >= 12);
|
|
49092
|
+
}
|
|
49093
|
+
function inferLanes(goal) {
|
|
49094
|
+
const lower = goal.toLowerCase();
|
|
49095
|
+
const lanes = ["Map the current state and locate relevant files or evidence"];
|
|
49096
|
+
if (/code|repo|branch|test|build|bug|feature|implement/.test(lower)) {
|
|
49097
|
+
lanes.push("Review implementation risks, edge cases, and integration points");
|
|
49098
|
+
lanes.push("Inspect validation strategy and likely test coverage");
|
|
49099
|
+
}
|
|
49100
|
+
if (/research|latest|current|external|claude|compare|similar/.test(lower)) {
|
|
49101
|
+
lanes.push("Research external context and comparable workflow behavior");
|
|
49102
|
+
}
|
|
49103
|
+
if (/ui|status|overlay|navigate|progress|worker/.test(lower)) {
|
|
49104
|
+
lanes.push("Evaluate user experience, status visibility, and navigation requirements");
|
|
49105
|
+
}
|
|
49106
|
+
if (/security|safe|permission|mutat|write|approval/.test(lower)) {
|
|
49107
|
+
lanes.push("Analyze safety boundaries, permissions, and failure modes");
|
|
49108
|
+
}
|
|
49109
|
+
lanes.push("Draft concrete recommendations and acceptance criteria");
|
|
49110
|
+
return lanes;
|
|
49111
|
+
}
|
|
49112
|
+
function pushUnique(items, value) {
|
|
49113
|
+
const normalized = value.trim();
|
|
49114
|
+
if (!normalized) return;
|
|
49115
|
+
if (!items.some((item) => item.toLowerCase() === normalized.toLowerCase())) items.push(normalized);
|
|
49116
|
+
}
|
|
49117
|
+
function buildTaskPrompt(overallGoal, taskGoal) {
|
|
49118
|
+
return [
|
|
49119
|
+
`Overall workflow goal: ${overallGoal}`,
|
|
49120
|
+
`Your assigned read-only worker goal: ${taskGoal}`,
|
|
49121
|
+
"",
|
|
49122
|
+
"Work independently. Use only read/search/analysis tools. Do not modify files, send messages, update memory, or run shell/code execution.",
|
|
49123
|
+
"Return concise findings with evidence, confidence, and open questions."
|
|
49124
|
+
].join("\n");
|
|
49125
|
+
}
|
|
49126
|
+
function buildVerifierPrompt(overallGoal, batch) {
|
|
49127
|
+
return [
|
|
49128
|
+
`Overall workflow goal: ${overallGoal}`,
|
|
49129
|
+
`Verify the likely claims and gaps for these worker lanes: ${batch.map((item) => item.goal).join("; ")}`,
|
|
49130
|
+
"",
|
|
49131
|
+
"Use read-only checks. Label findings as verified, partially verified, or unverified. Do not modify anything."
|
|
49132
|
+
].join("\n");
|
|
49133
|
+
}
|
|
49134
|
+
var DEFAULT_DYNAMIC_WORKFLOW_CONFIG, clampInt, DEFAULT_LANES;
|
|
49135
|
+
var init_planner = __esm({
|
|
49136
|
+
"src/workflows/planner.ts"() {
|
|
49137
|
+
"use strict";
|
|
49138
|
+
DEFAULT_DYNAMIC_WORKFLOW_CONFIG = {
|
|
49139
|
+
enabled: true,
|
|
49140
|
+
plannerMode: "adaptive",
|
|
49141
|
+
hardMaxWorkers: 1024,
|
|
49142
|
+
hardMaxConcurrentWorkers: 128,
|
|
49143
|
+
workerTimeoutMs: 18e4,
|
|
49144
|
+
persistHistory: true
|
|
49145
|
+
};
|
|
49146
|
+
clampInt = /* @__PURE__ */ __name((value, fallback, min, max) => {
|
|
49147
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
49148
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
49149
|
+
return Math.min(max, Math.max(min, Math.floor(parsed)));
|
|
49150
|
+
}, "clampInt");
|
|
49151
|
+
__name(resolveDynamicWorkflowConfig, "resolveDynamicWorkflowConfig");
|
|
49152
|
+
__name(initialConcurrency, "initialConcurrency");
|
|
49153
|
+
__name(adjustConcurrency, "adjustConcurrency");
|
|
49154
|
+
__name(planWorkflow, "planWorkflow");
|
|
49155
|
+
__name(deriveTaskGoals, "deriveTaskGoals");
|
|
49156
|
+
__name(extractExplicitTasks, "extractExplicitTasks");
|
|
49157
|
+
__name(inferLanes, "inferLanes");
|
|
49158
|
+
DEFAULT_LANES = [
|
|
49159
|
+
"Map the current state and locate relevant files or evidence",
|
|
49160
|
+
"Review implementation risks, edge cases, and integration points",
|
|
49161
|
+
"Inspect validation strategy and likely test coverage",
|
|
49162
|
+
"Analyze safety boundaries, permissions, and failure modes",
|
|
49163
|
+
"Draft concrete recommendations and acceptance criteria"
|
|
49164
|
+
];
|
|
49165
|
+
__name(pushUnique, "pushUnique");
|
|
49166
|
+
__name(buildTaskPrompt, "buildTaskPrompt");
|
|
49167
|
+
__name(buildVerifierPrompt, "buildVerifierPrompt");
|
|
49168
|
+
}
|
|
49169
|
+
});
|
|
49170
|
+
|
|
49171
|
+
// src/workflows/persistence.ts
|
|
49172
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync7, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
|
|
48226
49173
|
import { homedir as homedir28 } from "node:os";
|
|
48227
|
-
import { basename as basename3,
|
|
49174
|
+
import { basename as basename3, join as join42, resolve as resolve4 } from "node:path";
|
|
49175
|
+
function ensureDir(path3) {
|
|
49176
|
+
if (!existsSync31(path3)) mkdirSync17(path3, { recursive: true });
|
|
49177
|
+
}
|
|
49178
|
+
function safeSegment(value) {
|
|
49179
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 80) || "default";
|
|
49180
|
+
}
|
|
49181
|
+
function readJson(path3) {
|
|
49182
|
+
try {
|
|
49183
|
+
return JSON.parse(readFileSync28(path3, "utf8"));
|
|
49184
|
+
} catch {
|
|
49185
|
+
return null;
|
|
49186
|
+
}
|
|
49187
|
+
}
|
|
49188
|
+
function saveWorkflowSnapshot(snapshot) {
|
|
49189
|
+
ensureDir(workflowDir());
|
|
49190
|
+
const path3 = join42(workflowDir(), `${safeSegment(snapshot.id)}.json`);
|
|
49191
|
+
writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
|
|
49192
|
+
`, "utf8");
|
|
49193
|
+
return path3;
|
|
49194
|
+
}
|
|
49195
|
+
function loadWorkflowSnapshot(id) {
|
|
49196
|
+
const path3 = join42(workflowDir(), `${safeSegment(id)}.json`);
|
|
49197
|
+
return readJson(path3);
|
|
49198
|
+
}
|
|
49199
|
+
function listWorkflowSnapshots(limit = 30) {
|
|
49200
|
+
if (!existsSync31(workflowDir())) return [];
|
|
49201
|
+
const entries = [];
|
|
49202
|
+
for (const entry of readdirSync7(workflowDir(), { withFileTypes: true })) {
|
|
49203
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
49204
|
+
const path3 = join42(workflowDir(), entry.name);
|
|
49205
|
+
const snapshot = readJson(path3);
|
|
49206
|
+
if (!snapshot) continue;
|
|
49207
|
+
entries.push({
|
|
49208
|
+
finishedAt: snapshot.finishedAt,
|
|
49209
|
+
goal: snapshot.goal,
|
|
49210
|
+
id: snapshot.id,
|
|
49211
|
+
path: path3,
|
|
49212
|
+
plannedWorkerCount: snapshot.plannedWorkerCount,
|
|
49213
|
+
startedAt: snapshot.startedAt,
|
|
49214
|
+
status: snapshot.status,
|
|
49215
|
+
workerCount: snapshot.workers.length
|
|
49216
|
+
});
|
|
49217
|
+
}
|
|
49218
|
+
return entries.sort((a, b) => (b.finishedAt ?? b.startedAt) - (a.finishedAt ?? a.startedAt)).slice(0, Math.max(1, limit));
|
|
49219
|
+
}
|
|
49220
|
+
function saveSpawnTreeSnapshot(input) {
|
|
49221
|
+
const sessionId = safeSegment(input.session_id ?? "default");
|
|
49222
|
+
const dir2 = join42(spawnTreeDir(), sessionId);
|
|
49223
|
+
ensureDir(dir2);
|
|
49224
|
+
const stamp = new Date((input.finished_at ?? Date.now() / 1e3) * 1e3).toISOString().replace(/[:.]/g, "-");
|
|
49225
|
+
const path3 = join42(dir2, `${stamp}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
49226
|
+
const snapshot = {
|
|
49227
|
+
count: input.subagents?.length ?? 0,
|
|
49228
|
+
finished_at: input.finished_at,
|
|
49229
|
+
label: input.label,
|
|
49230
|
+
session_id: input.session_id,
|
|
49231
|
+
started_at: input.started_at,
|
|
49232
|
+
subagents: input.subagents ?? []
|
|
49233
|
+
};
|
|
49234
|
+
writeFileSync19(path3, `${JSON.stringify(snapshot, null, 2)}
|
|
49235
|
+
`, "utf8");
|
|
49236
|
+
return path3;
|
|
49237
|
+
}
|
|
49238
|
+
function listSpawnTreeSnapshots(sessionId = "default", limit = 30) {
|
|
49239
|
+
const dir2 = join42(spawnTreeDir(), safeSegment(sessionId));
|
|
49240
|
+
if (!existsSync31(dir2)) return [];
|
|
49241
|
+
const entries = [];
|
|
49242
|
+
for (const entry of readdirSync7(dir2, { withFileTypes: true })) {
|
|
49243
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
49244
|
+
const path3 = join42(dir2, entry.name);
|
|
49245
|
+
const snapshot = readJson(path3);
|
|
49246
|
+
if (!snapshot) continue;
|
|
49247
|
+
entries.push({
|
|
49248
|
+
count: snapshot.count,
|
|
49249
|
+
finished_at: snapshot.finished_at,
|
|
49250
|
+
label: snapshot.label,
|
|
49251
|
+
path: path3,
|
|
49252
|
+
session_id: snapshot.session_id,
|
|
49253
|
+
started_at: snapshot.started_at
|
|
49254
|
+
});
|
|
49255
|
+
}
|
|
49256
|
+
return entries.sort((a, b) => (b.finished_at ?? 0) - (a.finished_at ?? 0)).slice(0, Math.max(1, limit));
|
|
49257
|
+
}
|
|
49258
|
+
function loadSpawnTreeSnapshot(path3) {
|
|
49259
|
+
const root = resolve4(spawnTreeDir());
|
|
49260
|
+
const resolved = resolve4(path3);
|
|
49261
|
+
if (!resolved.startsWith(root)) return null;
|
|
49262
|
+
const snapshot = readJson(resolved);
|
|
49263
|
+
return snapshot ? { ...snapshot, path: resolved } : null;
|
|
49264
|
+
}
|
|
49265
|
+
var rootDir, workflowDir, spawnTreeDir;
|
|
49266
|
+
var init_persistence = __esm({
|
|
49267
|
+
"src/workflows/persistence.ts"() {
|
|
49268
|
+
"use strict";
|
|
49269
|
+
rootDir = /* @__PURE__ */ __name(() => join42(homedir28(), ".openjaw-agent"), "rootDir");
|
|
49270
|
+
workflowDir = /* @__PURE__ */ __name(() => join42(rootDir(), "workflows"), "workflowDir");
|
|
49271
|
+
spawnTreeDir = /* @__PURE__ */ __name(() => join42(rootDir(), "spawn-trees"), "spawnTreeDir");
|
|
49272
|
+
__name(ensureDir, "ensureDir");
|
|
49273
|
+
__name(safeSegment, "safeSegment");
|
|
49274
|
+
__name(readJson, "readJson");
|
|
49275
|
+
__name(saveWorkflowSnapshot, "saveWorkflowSnapshot");
|
|
49276
|
+
__name(loadWorkflowSnapshot, "loadWorkflowSnapshot");
|
|
49277
|
+
__name(listWorkflowSnapshots, "listWorkflowSnapshots");
|
|
49278
|
+
__name(saveSpawnTreeSnapshot, "saveSpawnTreeSnapshot");
|
|
49279
|
+
__name(listSpawnTreeSnapshots, "listSpawnTreeSnapshots");
|
|
49280
|
+
__name(loadSpawnTreeSnapshot, "loadSpawnTreeSnapshot");
|
|
49281
|
+
}
|
|
49282
|
+
});
|
|
49283
|
+
|
|
49284
|
+
// src/workflows/readOnlyTools.ts
|
|
49285
|
+
function isWorkflowReadOnlyTool(name) {
|
|
49286
|
+
return READ_ONLY_TOOLS.has(name);
|
|
49287
|
+
}
|
|
49288
|
+
var READ_ONLY_TOOLS, ReadOnlyToolRuntime;
|
|
49289
|
+
var init_readOnlyTools = __esm({
|
|
49290
|
+
"src/workflows/readOnlyTools.ts"() {
|
|
49291
|
+
"use strict";
|
|
49292
|
+
READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
49293
|
+
"browser_snapshot",
|
|
49294
|
+
"browser_extract",
|
|
49295
|
+
"file_info",
|
|
49296
|
+
"file_list",
|
|
49297
|
+
"file_read",
|
|
49298
|
+
"glob",
|
|
49299
|
+
"grep",
|
|
49300
|
+
"image_view",
|
|
49301
|
+
"web_extract",
|
|
49302
|
+
"web_fetch",
|
|
49303
|
+
"web_search"
|
|
49304
|
+
]);
|
|
49305
|
+
__name(isWorkflowReadOnlyTool, "isWorkflowReadOnlyTool");
|
|
49306
|
+
ReadOnlyToolRuntime = class {
|
|
49307
|
+
constructor(inner) {
|
|
49308
|
+
this.inner = inner;
|
|
49309
|
+
}
|
|
49310
|
+
inner;
|
|
49311
|
+
static {
|
|
49312
|
+
__name(this, "ReadOnlyToolRuntime");
|
|
49313
|
+
}
|
|
49314
|
+
listTools() {
|
|
49315
|
+
return this.inner.listTools().filter((tool) => isWorkflowReadOnlyTool(tool.name));
|
|
49316
|
+
}
|
|
49317
|
+
async execute(name, input) {
|
|
49318
|
+
if (!isWorkflowReadOnlyTool(name)) {
|
|
49319
|
+
throw new Error(`Tool ${name} is not available in advisory workflow workers`);
|
|
49320
|
+
}
|
|
49321
|
+
return await this.inner.execute(name, input);
|
|
49322
|
+
}
|
|
49323
|
+
};
|
|
49324
|
+
}
|
|
49325
|
+
});
|
|
49326
|
+
|
|
49327
|
+
// src/workflows/manager.ts
|
|
49328
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
49329
|
+
function buildPlannerSystemPrompt(limits) {
|
|
49330
|
+
return [
|
|
49331
|
+
"You are the OpenJaw dynamic workflow planner.",
|
|
49332
|
+
"Return ONLY valid JSON. Do not include markdown fences or prose.",
|
|
49333
|
+
"Create a task graph for advisory read-only worker agents. Do not answer the user task.",
|
|
49334
|
+
"Use enough workers to represent genuinely different research lanes, viewpoints, critics, verifiers, and a final synthesizer. Do not default to a small fixed count.",
|
|
49335
|
+
"For panel-review prompts, create separate panelist workers for each requested philosophy and a critic/risk worker before the synthesizer.",
|
|
49336
|
+
`Physical caps: at most ${limits.hardMaxWorkers} total workers and ${limits.hardMaxConcurrentWorkers} concurrent workers. Normal plans should be smaller than the cap but may contain dozens of workers when useful.`,
|
|
49337
|
+
'Schema: {"language":"optional output language","workers":[{"id":"kebab-id","role":"researcher|panelist|critic|verifier|synthesizer","philosophy":"optional","goal":"specific worker goal","depends_on":["worker-id"]}]}',
|
|
49338
|
+
"Always include exactly one synthesizer worker that depends on the most important research, panelist, critic, and verifier workers."
|
|
49339
|
+
].join("\n");
|
|
49340
|
+
}
|
|
49341
|
+
function parseModelPlan(content, goal, runId, limits) {
|
|
49342
|
+
const parsed = JSON.parse(extractJsonObject(content));
|
|
49343
|
+
const workers = Array.isArray(parsed.workers) ? parsed.workers : [];
|
|
49344
|
+
const specs = [];
|
|
49345
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
49346
|
+
for (const worker of workers.slice(0, limits.hardMaxWorkers)) {
|
|
49347
|
+
const rawGoal = String(worker.goal ?? "").trim();
|
|
49348
|
+
if (!rawGoal) continue;
|
|
49349
|
+
const id = uniqueWorkerId(runId, worker.id || rawGoal, usedIds, specs.length + 1);
|
|
49350
|
+
const role = normalizeRole(worker.role);
|
|
49351
|
+
const dependsOn = Array.isArray(worker.depends_on) ? worker.depends_on.map(String).map((value) => scopedWorkerId(runId, value)).filter((value) => usedIds.has(value)) : [];
|
|
49352
|
+
specs.push({
|
|
49353
|
+
depth: dependsOn.length > 0 || role !== "task" ? 1 : 0,
|
|
49354
|
+
dependsOn,
|
|
49355
|
+
goal: rawGoal,
|
|
49356
|
+
id,
|
|
49357
|
+
index: specs.length,
|
|
49358
|
+
parentId: dependsOn[0] ?? null,
|
|
49359
|
+
prompt: buildModelWorkerPrompt(goal, rawGoal, worker.role, worker.philosophy, parsed.language),
|
|
49360
|
+
role
|
|
49361
|
+
});
|
|
49362
|
+
}
|
|
49363
|
+
return ensureSynthesizer({ concurrency: 0, specs }, goal, runId, limits);
|
|
49364
|
+
}
|
|
49365
|
+
function ensureSynthesizer(plan, goal, runId, limits) {
|
|
49366
|
+
const specs = plan.specs.slice(0, limits.hardMaxWorkers);
|
|
49367
|
+
const existing = specs.find((spec) => spec.role === "synthesizer");
|
|
49368
|
+
const dependencies = specs.filter((spec) => spec.role !== "synthesizer").map((spec) => spec.id);
|
|
49369
|
+
if (existing) {
|
|
49370
|
+
existing.dependsOn = existing.dependsOn?.length ? existing.dependsOn : dependencies;
|
|
49371
|
+
existing.depth = Math.max(existing.depth, 1);
|
|
49372
|
+
existing.parentId = existing.dependsOn[0] ?? null;
|
|
49373
|
+
} else if (specs.length < limits.hardMaxWorkers) {
|
|
49374
|
+
specs.push({
|
|
49375
|
+
depth: 1,
|
|
49376
|
+
dependsOn: dependencies,
|
|
49377
|
+
goal: "Synthesize the panel and research outputs into the final answer",
|
|
49378
|
+
id: `${runId}-synthesizer`,
|
|
49379
|
+
index: specs.length,
|
|
49380
|
+
parentId: dependencies[0] ?? null,
|
|
49381
|
+
prompt: buildModelWorkerPrompt(goal, "Synthesize all completed worker outputs into the final answer for the user.", "synthesizer", void 0, void 0),
|
|
49382
|
+
role: "synthesizer"
|
|
49383
|
+
});
|
|
49384
|
+
}
|
|
49385
|
+
return { concurrency: Math.min(Math.ceil(Math.sqrt(specs.length) * 2), specs.length, limits.hardMaxConcurrentWorkers), specs };
|
|
49386
|
+
}
|
|
49387
|
+
function buildModelWorkerPrompt(overallGoal, workerGoal, role, philosophy, language) {
|
|
49388
|
+
return [
|
|
49389
|
+
`Overall workflow goal: ${overallGoal}`,
|
|
49390
|
+
`Worker role: ${role || "researcher"}`,
|
|
49391
|
+
philosophy ? `Investment/work philosophy: ${philosophy}` : "",
|
|
49392
|
+
language ? `Final language preference: ${language}` : "",
|
|
49393
|
+
`Assigned goal: ${workerGoal}`,
|
|
49394
|
+
"",
|
|
49395
|
+
"Use only read/search/analysis tools. Do not modify files, send messages, update memory, or run shell/code execution.",
|
|
49396
|
+
"If prior worker outputs are provided below, use them as context and explicitly resolve agreements, disagreements, and uncertainty.",
|
|
49397
|
+
"Return evidence-backed findings. A synthesizer must produce the final answer directly for the user."
|
|
49398
|
+
].filter(Boolean).join("\n");
|
|
49399
|
+
}
|
|
49400
|
+
function normalizeRole(role) {
|
|
49401
|
+
const lower = String(role ?? "").toLowerCase();
|
|
49402
|
+
if (lower.includes("synth") || lower.includes("chair")) return "synthesizer";
|
|
49403
|
+
if (lower.includes("verify") || lower.includes("critic") || lower.includes("risk")) return "verifier";
|
|
49404
|
+
return "task";
|
|
49405
|
+
}
|
|
49406
|
+
function extractJsonObject(content) {
|
|
49407
|
+
const trimmed = content.trim().replace(/^```(?:json)?\s*/i, "").replace(/```$/i, "").trim();
|
|
49408
|
+
const start = trimmed.indexOf("{");
|
|
49409
|
+
const end = trimmed.lastIndexOf("}");
|
|
49410
|
+
if (start < 0 || end < start) throw new Error("planner response did not contain a JSON object");
|
|
49411
|
+
return trimmed.slice(start, end + 1);
|
|
49412
|
+
}
|
|
49413
|
+
function uniqueWorkerId(runId, value, used, index) {
|
|
49414
|
+
const base = scopedWorkerId(runId, value) || `${runId}-w${index}`;
|
|
49415
|
+
let candidate = base;
|
|
49416
|
+
let suffix = 2;
|
|
49417
|
+
while (used.has(candidate)) {
|
|
49418
|
+
candidate = `${base}-${suffix}`;
|
|
49419
|
+
suffix += 1;
|
|
49420
|
+
}
|
|
49421
|
+
used.add(candidate);
|
|
49422
|
+
return candidate;
|
|
49423
|
+
}
|
|
49424
|
+
function scopedWorkerId(runId, value) {
|
|
49425
|
+
const slug = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
49426
|
+
return slug.startsWith(`${runId}-`) ? slug : `${runId}-${slug || "worker"}`;
|
|
49427
|
+
}
|
|
49428
|
+
function firstParagraph(value) {
|
|
49429
|
+
return value.split(/\n\s*\n/)[0]?.trim().slice(0, 800) || value.trim().slice(0, 800);
|
|
49430
|
+
}
|
|
49431
|
+
function inferVerificationState(value) {
|
|
49432
|
+
const lower = value.toLowerCase();
|
|
49433
|
+
if (lower.includes("partially verified") || lower.includes("partial")) return "partial";
|
|
49434
|
+
if (lower.includes("unverified") || lower.includes("not verified")) return "unverified";
|
|
49435
|
+
if (lower.includes("verified")) return "verified";
|
|
49436
|
+
return "pending";
|
|
49437
|
+
}
|
|
49438
|
+
function dependencyOutputContext(run, spec) {
|
|
49439
|
+
const dependencyIds = spec.dependsOn ?? [];
|
|
49440
|
+
if (dependencyIds.length === 0) return "";
|
|
49441
|
+
const blocks = dependencyIds.map((id) => run.workers.find((worker) => worker.id === id)).filter((worker) => Boolean(worker)).map((worker) => [
|
|
49442
|
+
`## Worker ${worker.id}`,
|
|
49443
|
+
`Role: ${worker.workerRole ?? "task"}`,
|
|
49444
|
+
`Goal: ${worker.goal}`,
|
|
49445
|
+
`Status: ${worker.status}`,
|
|
49446
|
+
worker.summary ? `Summary: ${worker.summary}` : "",
|
|
49447
|
+
worker.details ? `Details:
|
|
49448
|
+
${worker.details}` : ""
|
|
49449
|
+
].filter(Boolean).join("\n"));
|
|
49450
|
+
return blocks.length ? `# Prior worker outputs
|
|
49451
|
+
${blocks.join("\n\n")}` : "";
|
|
49452
|
+
}
|
|
49453
|
+
var terminal, WorkflowManager;
|
|
49454
|
+
var init_manager = __esm({
|
|
49455
|
+
"src/workflows/manager.ts"() {
|
|
49456
|
+
"use strict";
|
|
49457
|
+
init_agent_loop();
|
|
49458
|
+
init_providers();
|
|
49459
|
+
init_planner();
|
|
49460
|
+
init_persistence();
|
|
49461
|
+
init_readOnlyTools();
|
|
49462
|
+
terminal = /* @__PURE__ */ new Set(["cancelled", "completed", "failed"]);
|
|
49463
|
+
WorkflowManager = class {
|
|
49464
|
+
constructor(config, toolRuntime, systemPromptFn, bus, options = {}) {
|
|
49465
|
+
this.config = config;
|
|
49466
|
+
this.toolRuntime = toolRuntime;
|
|
49467
|
+
this.systemPromptFn = systemPromptFn;
|
|
49468
|
+
this.bus = bus;
|
|
49469
|
+
this.planner = options.planner;
|
|
49470
|
+
this.runner = options.runner ?? this.defaultRunner;
|
|
49471
|
+
}
|
|
49472
|
+
config;
|
|
49473
|
+
toolRuntime;
|
|
49474
|
+
systemPromptFn;
|
|
49475
|
+
bus;
|
|
49476
|
+
static {
|
|
49477
|
+
__name(this, "WorkflowManager");
|
|
49478
|
+
}
|
|
49479
|
+
runs = /* @__PURE__ */ new Map();
|
|
49480
|
+
planner;
|
|
49481
|
+
runner;
|
|
49482
|
+
async start(goal, sessionId) {
|
|
49483
|
+
const limits = resolveDynamicWorkflowConfig(this.config);
|
|
49484
|
+
if (!limits.enabled) {
|
|
49485
|
+
throw new Error("dynamic workflows are disabled in config");
|
|
49486
|
+
}
|
|
49487
|
+
const id = `wf-${randomUUID13().slice(0, 8)}`;
|
|
49488
|
+
const plan = await this.buildPlan(goal, id, limits);
|
|
49489
|
+
const now2 = Date.now();
|
|
49490
|
+
const run = {
|
|
49491
|
+
abortController: new AbortController(),
|
|
49492
|
+
activeAborts: /* @__PURE__ */ new Map(),
|
|
49493
|
+
concurrency: plan.concurrency,
|
|
49494
|
+
config: limits,
|
|
49495
|
+
goal,
|
|
49496
|
+
id,
|
|
49497
|
+
plannedWorkerCount: plan.specs.length,
|
|
49498
|
+
startedAt: now2,
|
|
49499
|
+
status: "running",
|
|
49500
|
+
workers: plan.specs.map((spec) => this.workerFromSpec(spec, id, plan.specs.length, now2))
|
|
49501
|
+
};
|
|
49502
|
+
this.runs.set(id, run);
|
|
49503
|
+
this.emitWorkflow("workflow.start", run, sessionId);
|
|
49504
|
+
for (const worker of run.workers) {
|
|
49505
|
+
this.emitSubagent("subagent.spawn_requested", run, worker, sessionId);
|
|
49506
|
+
}
|
|
49507
|
+
void this.executeRun(run, plan.specs, sessionId);
|
|
49508
|
+
return { run: this.snapshot(run), started: true };
|
|
49509
|
+
}
|
|
49510
|
+
status(id) {
|
|
49511
|
+
const run = id ? this.runs.get(id) : this.latestRun();
|
|
49512
|
+
if (run) return this.snapshot(run);
|
|
49513
|
+
if (id) return loadWorkflowSnapshot(id);
|
|
49514
|
+
const first = listWorkflowSnapshots(1)[0];
|
|
49515
|
+
return first ? loadWorkflowSnapshot(first.id) : null;
|
|
49516
|
+
}
|
|
49517
|
+
list(limit = 30) {
|
|
49518
|
+
const live = [...this.runs.values()].map((run) => ({
|
|
49519
|
+
finishedAt: run.finishedAt,
|
|
49520
|
+
goal: run.goal,
|
|
49521
|
+
id: run.id,
|
|
49522
|
+
plannedWorkerCount: run.plannedWorkerCount,
|
|
49523
|
+
startedAt: run.startedAt,
|
|
49524
|
+
status: run.status,
|
|
49525
|
+
workerCount: run.workers.length
|
|
49526
|
+
}));
|
|
49527
|
+
const persisted = listWorkflowSnapshots(limit);
|
|
49528
|
+
const seen = new Set(live.map((entry) => entry.id));
|
|
49529
|
+
return [...live, ...persisted.filter((entry) => !seen.has(entry.id))].sort((a, b) => (b.finishedAt ?? b.startedAt) - (a.finishedAt ?? a.startedAt)).slice(0, Math.max(1, limit));
|
|
49530
|
+
}
|
|
49531
|
+
cancel(id) {
|
|
49532
|
+
const run = this.runs.get(id);
|
|
49533
|
+
if (run) {
|
|
49534
|
+
this.cancelRun(run);
|
|
49535
|
+
return { found: true, run: this.snapshot(run) };
|
|
49536
|
+
}
|
|
49537
|
+
for (const candidate of this.runs.values()) {
|
|
49538
|
+
const worker = candidate.workers.find((item) => item.id === id);
|
|
49539
|
+
if (!worker) continue;
|
|
49540
|
+
candidate.activeAborts.get(worker.id)?.();
|
|
49541
|
+
this.updateWorker(candidate, worker.id, {
|
|
49542
|
+
currentStep: "cancel requested",
|
|
49543
|
+
status: "interrupted"
|
|
49544
|
+
});
|
|
49545
|
+
return { found: true, run: this.snapshot(candidate), workerId: worker.id };
|
|
49546
|
+
}
|
|
49547
|
+
return { found: false };
|
|
49548
|
+
}
|
|
49549
|
+
async buildPlan(goal, runId, limits) {
|
|
49550
|
+
if (this.planner) {
|
|
49551
|
+
return ensureSynthesizer(await this.planner(goal, runId, limits), goal, runId, limits);
|
|
49552
|
+
}
|
|
49553
|
+
try {
|
|
49554
|
+
const provider = createProvider(this.config);
|
|
49555
|
+
const controller = new AbortController();
|
|
49556
|
+
const timer = setTimeout(() => controller.abort(), Math.min(6e4, limits.workerTimeoutMs));
|
|
49557
|
+
try {
|
|
49558
|
+
const result = await provider.chat({
|
|
49559
|
+
systemPrompt: buildPlannerSystemPrompt(limits),
|
|
49560
|
+
messages: [{ role: "user", content: goal }],
|
|
49561
|
+
tools: [],
|
|
49562
|
+
signal: controller.signal
|
|
49563
|
+
});
|
|
49564
|
+
const parsed = parseModelPlan(result.text ?? "", goal, runId, limits);
|
|
49565
|
+
if (parsed.specs.length > 0) return parsed;
|
|
49566
|
+
} finally {
|
|
49567
|
+
clearTimeout(timer);
|
|
49568
|
+
}
|
|
49569
|
+
} catch {
|
|
49570
|
+
}
|
|
49571
|
+
return ensureSynthesizer(planWorkflow(goal, runId, limits), goal, runId, limits);
|
|
49572
|
+
}
|
|
49573
|
+
async executeRun(run, specs, sessionId) {
|
|
49574
|
+
const queue = [...specs];
|
|
49575
|
+
const active = /* @__PURE__ */ new Map();
|
|
49576
|
+
let stableCompletions = 0;
|
|
49577
|
+
try {
|
|
49578
|
+
while ((queue.length > 0 || active.size > 0) && !run.abortController.signal.aborted) {
|
|
49579
|
+
const ready = queue.filter((spec) => this.dependenciesFinished(run, spec));
|
|
49580
|
+
while (ready.length > 0 && active.size < run.concurrency && !run.abortController.signal.aborted) {
|
|
49581
|
+
const spec = ready.shift();
|
|
49582
|
+
queue.splice(queue.indexOf(spec), 1);
|
|
49583
|
+
const promise = this.executeWorker(run, spec, sessionId).then(() => {
|
|
49584
|
+
stableCompletions += 1;
|
|
49585
|
+
if (stableCompletions % Math.max(2, run.concurrency) === 0) {
|
|
49586
|
+
run.concurrency = adjustConcurrency(run.concurrency, run.plannedWorkerCount, run.config, "stable_completion");
|
|
49587
|
+
}
|
|
49588
|
+
}).catch((err) => {
|
|
49589
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
49590
|
+
if (/rate|429|quota|throttle/i.test(message)) {
|
|
49591
|
+
run.concurrency = adjustConcurrency(run.concurrency, run.plannedWorkerCount, run.config, "rate_limited");
|
|
49592
|
+
}
|
|
49593
|
+
}).finally(() => active.delete(spec.id));
|
|
49594
|
+
active.set(spec.id, promise);
|
|
49595
|
+
}
|
|
49596
|
+
if (active.size === 0) break;
|
|
49597
|
+
await Promise.race(active.values());
|
|
49598
|
+
}
|
|
49599
|
+
if (run.abortController.signal.aborted) {
|
|
49600
|
+
for (const worker of run.workers) {
|
|
49601
|
+
if (worker.status === "queued" || worker.status === "running") {
|
|
49602
|
+
this.updateWorker(run, worker.id, { currentStep: "cancelled", status: "interrupted" });
|
|
49603
|
+
}
|
|
49604
|
+
}
|
|
49605
|
+
run.status = "cancelled";
|
|
49606
|
+
run.summary = this.summarize(run, "cancelled");
|
|
49607
|
+
} else if (run.workers.some((worker) => worker.status === "failed")) {
|
|
49608
|
+
run.status = "failed";
|
|
49609
|
+
run.summary = this.summarize(run, "failed");
|
|
49610
|
+
} else {
|
|
49611
|
+
run.status = "completed";
|
|
49612
|
+
run.summary = this.summarize(run, "completed");
|
|
49613
|
+
}
|
|
49614
|
+
} catch (err) {
|
|
49615
|
+
run.status = "failed";
|
|
49616
|
+
run.summary = `Workflow failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
49617
|
+
} finally {
|
|
49618
|
+
run.finishedAt = Date.now();
|
|
49619
|
+
if (run.config.persistHistory) saveWorkflowSnapshot(this.snapshot(run));
|
|
49620
|
+
this.emitWorkflow(run.status === "failed" ? "workflow.error" : "workflow.complete", run, sessionId);
|
|
49621
|
+
}
|
|
49622
|
+
}
|
|
49623
|
+
async executeWorker(run, spec, sessionId) {
|
|
49624
|
+
if (run.abortController.signal.aborted) return;
|
|
49625
|
+
this.updateWorker(run, spec.id, {
|
|
49626
|
+
currentStep: "starting",
|
|
49627
|
+
startedAt: Date.now(),
|
|
49628
|
+
status: "running"
|
|
49629
|
+
});
|
|
49630
|
+
this.emitSubagent("subagent.start", run, this.worker(run, spec.id), sessionId);
|
|
49631
|
+
const controller = new AbortController();
|
|
49632
|
+
const timeoutMs = spec.role === "synthesizer" ? run.config.workerTimeoutMs * 3 : run.config.workerTimeoutMs;
|
|
49633
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
49634
|
+
run.activeAborts.set(spec.id, () => controller.abort());
|
|
49635
|
+
const signal = AbortSignal.any([controller.signal, run.abortController.signal]);
|
|
49636
|
+
try {
|
|
49637
|
+
const result = await this.runner({
|
|
49638
|
+
config: this.config,
|
|
49639
|
+
run: this.snapshot(run),
|
|
49640
|
+
signal,
|
|
49641
|
+
spec,
|
|
49642
|
+
systemPromptFn: this.systemPromptFn,
|
|
49643
|
+
toolRuntime: new ReadOnlyToolRuntime(this.toolRuntime),
|
|
49644
|
+
update: /* @__PURE__ */ __name((patch) => {
|
|
49645
|
+
this.updateWorker(run, spec.id, patch);
|
|
49646
|
+
this.emitSubagent("subagent.progress", run, this.worker(run, spec.id), sessionId);
|
|
49647
|
+
}, "update")
|
|
49648
|
+
});
|
|
49649
|
+
this.updateWorker(run, spec.id, {
|
|
49650
|
+
currentStep: "complete",
|
|
49651
|
+
details: result,
|
|
49652
|
+
durationSeconds: (Date.now() - (this.worker(run, spec.id).startedAt ?? Date.now())) / 1e3,
|
|
49653
|
+
status: signal.aborted ? "interrupted" : "completed",
|
|
49654
|
+
summary: firstParagraph(result),
|
|
49655
|
+
verificationState: spec.role === "verifier" ? inferVerificationState(result) : "not_applicable"
|
|
49656
|
+
});
|
|
49657
|
+
} catch (err) {
|
|
49658
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
49659
|
+
this.updateWorker(run, spec.id, {
|
|
49660
|
+
currentStep: signal.aborted ? "interrupted" : "failed",
|
|
49661
|
+
details: message,
|
|
49662
|
+
durationSeconds: (Date.now() - (this.worker(run, spec.id).startedAt ?? Date.now())) / 1e3,
|
|
49663
|
+
status: signal.aborted ? "interrupted" : "failed",
|
|
49664
|
+
summary: message,
|
|
49665
|
+
verificationState: "unverified"
|
|
49666
|
+
});
|
|
49667
|
+
if (!signal.aborted) throw err;
|
|
49668
|
+
} finally {
|
|
49669
|
+
clearTimeout(timeout);
|
|
49670
|
+
run.activeAborts.delete(spec.id);
|
|
49671
|
+
this.emitSubagent("subagent.complete", run, this.worker(run, spec.id), sessionId);
|
|
49672
|
+
if (run.config.persistHistory) saveWorkflowSnapshot(this.snapshot(run));
|
|
49673
|
+
}
|
|
49674
|
+
}
|
|
49675
|
+
defaultRunner = /* @__PURE__ */ __name(async (ctx) => {
|
|
49676
|
+
const workerConfig = {
|
|
49677
|
+
...ctx.config,
|
|
49678
|
+
features: { ...ctx.config.features, skill_auto_suggest: false }
|
|
49679
|
+
};
|
|
49680
|
+
const loop = new AgentLoop(workerConfig, ctx.toolRuntime);
|
|
49681
|
+
const sections = await ctx.systemPromptFn();
|
|
49682
|
+
const systemPrompt = [
|
|
49683
|
+
...sections,
|
|
49684
|
+
"# Advisory Workflow Worker Rules",
|
|
49685
|
+
"You are a read-only workflow worker. Never modify files, execute shell/code, send messages, update memory, or perform browser actions that change state.",
|
|
49686
|
+
"Use available read/search tools only. Return concise evidence-backed findings."
|
|
49687
|
+
].filter(Boolean).join("\n\n");
|
|
49688
|
+
ctx.signal.addEventListener("abort", () => loop.abort(), { once: true });
|
|
49689
|
+
let answer = "";
|
|
49690
|
+
let streamedText = "";
|
|
49691
|
+
let outputTail = [];
|
|
49692
|
+
let toolCount = 0;
|
|
49693
|
+
const userPrompt = [ctx.spec.prompt, dependencyOutputContext(ctx.run, ctx.spec)].filter(Boolean).join("\n\n");
|
|
49694
|
+
for await (const chunk of loop.run(userPrompt, systemPrompt)) {
|
|
49695
|
+
if (ctx.signal.aborted) {
|
|
49696
|
+
loop.abort();
|
|
49697
|
+
break;
|
|
49698
|
+
}
|
|
49699
|
+
if (chunk.type === "thinking" && chunk.content.trim()) {
|
|
49700
|
+
streamedText += chunk.content;
|
|
49701
|
+
ctx.update({ currentStep: chunk.content.trim().slice(0, 180), thinking: [chunk.content.trim().slice(0, 500)] });
|
|
49702
|
+
}
|
|
49703
|
+
if (chunk.type === "tool_call") {
|
|
49704
|
+
toolCount += 1;
|
|
49705
|
+
ctx.update({ currentStep: `using ${chunk.toolName ?? "tool"}`, toolCount, tools: [`${chunk.toolName ?? "tool"}(...)`] });
|
|
49706
|
+
}
|
|
49707
|
+
if (chunk.type === "tool_result") {
|
|
49708
|
+
outputTail = [...outputTail, { isError: /error/i.test(chunk.content), preview: chunk.content.slice(0, 240), tool: chunk.toolName ?? "tool" }].slice(-8);
|
|
49709
|
+
ctx.update({ outputTail });
|
|
49710
|
+
}
|
|
49711
|
+
if (chunk.type === "answer" && chunk.content.trim()) answer = chunk.content.trim();
|
|
49712
|
+
}
|
|
49713
|
+
const result = answer || streamedText.trim();
|
|
49714
|
+
if (ctx.signal.aborted) {
|
|
49715
|
+
if (result) {
|
|
49716
|
+
return `${result}
|
|
49717
|
+
|
|
49718
|
+
[Partial output preserved after worker interruption.]`;
|
|
49719
|
+
}
|
|
49720
|
+
throw new Error("worker interrupted");
|
|
49721
|
+
}
|
|
49722
|
+
return result || "Worker completed with no final answer.";
|
|
49723
|
+
}, "defaultRunner");
|
|
49724
|
+
workerFromSpec(spec, workflowId, taskCount, now2) {
|
|
49725
|
+
return {
|
|
49726
|
+
currentStep: "queued",
|
|
49727
|
+
depth: spec.depth,
|
|
49728
|
+
goal: spec.goal,
|
|
49729
|
+
id: spec.id,
|
|
49730
|
+
index: spec.index,
|
|
49731
|
+
notes: [],
|
|
49732
|
+
parentId: spec.parentId,
|
|
49733
|
+
startedAt: now2,
|
|
49734
|
+
status: "queued",
|
|
49735
|
+
taskCount,
|
|
49736
|
+
thinking: [],
|
|
49737
|
+
toolCount: 0,
|
|
49738
|
+
tools: [],
|
|
49739
|
+
verificationState: spec.role === "verifier" ? "pending" : "not_applicable",
|
|
49740
|
+
workflowId,
|
|
49741
|
+
workerRole: spec.role
|
|
49742
|
+
};
|
|
49743
|
+
}
|
|
49744
|
+
dependenciesFinished(run, spec) {
|
|
49745
|
+
return (spec.dependsOn ?? []).every((id) => {
|
|
49746
|
+
const status = run.workers.find((worker) => worker.id === id)?.status;
|
|
49747
|
+
return status === "completed" || status === "failed" || status === "interrupted";
|
|
49748
|
+
});
|
|
49749
|
+
}
|
|
49750
|
+
updateWorker(run, id, patch) {
|
|
49751
|
+
run.workers = run.workers.map((worker) => worker.id === id ? { ...worker, ...patch } : worker);
|
|
49752
|
+
}
|
|
49753
|
+
worker(run, id) {
|
|
49754
|
+
return run.workers.find((worker) => worker.id === id) ?? run.workers[0];
|
|
49755
|
+
}
|
|
49756
|
+
snapshot(run) {
|
|
49757
|
+
return {
|
|
49758
|
+
concurrency: run.concurrency,
|
|
49759
|
+
finishedAt: run.finishedAt,
|
|
49760
|
+
goal: run.goal,
|
|
49761
|
+
id: run.id,
|
|
49762
|
+
plannedWorkerCount: run.plannedWorkerCount,
|
|
49763
|
+
startedAt: run.startedAt,
|
|
49764
|
+
status: run.status,
|
|
49765
|
+
summary: run.summary,
|
|
49766
|
+
workers: run.workers.map((worker) => ({ ...worker }))
|
|
49767
|
+
};
|
|
49768
|
+
}
|
|
49769
|
+
latestRun() {
|
|
49770
|
+
const runs = [...this.runs.values()].sort((a, b) => b.startedAt - a.startedAt);
|
|
49771
|
+
return runs[0] ?? null;
|
|
49772
|
+
}
|
|
49773
|
+
cancelRun(run) {
|
|
49774
|
+
if (terminal.has(run.status)) return;
|
|
49775
|
+
run.abortController.abort();
|
|
49776
|
+
for (const abort of run.activeAborts.values()) abort();
|
|
49777
|
+
run.status = "cancelled";
|
|
49778
|
+
}
|
|
49779
|
+
summarize(run, status) {
|
|
49780
|
+
const synthesizer = run.workers.find((worker) => worker.workerRole === "synthesizer" && worker.details && worker.details !== "worker interrupted");
|
|
49781
|
+
if ((status === "completed" || status === "cancelled" || status === "failed") && synthesizer?.details) {
|
|
49782
|
+
return [synthesizer.details, "No files were modified by advisory workflow workers."].join("\n\n");
|
|
49783
|
+
}
|
|
49784
|
+
const counts = run.workers.reduce((acc, worker) => {
|
|
49785
|
+
acc[worker.status] = (acc[worker.status] ?? 0) + 1;
|
|
49786
|
+
return acc;
|
|
49787
|
+
}, {});
|
|
49788
|
+
const highlights = run.workers.filter((worker) => worker.summary).slice(0, 8).map((worker) => `- ${worker.goal}: ${worker.summary}`).join("\n");
|
|
49789
|
+
return [
|
|
49790
|
+
`Workflow ${status}: ${run.goal}`,
|
|
49791
|
+
`Workers: ${run.workers.length} planned \xB7 ${counts.completed ?? 0} completed \xB7 ${counts.failed ?? 0} failed \xB7 ${counts.interrupted ?? 0} interrupted.`,
|
|
49792
|
+
"No files were modified by advisory workflow workers.",
|
|
49793
|
+
highlights ? `
|
|
49794
|
+
Key worker summaries:
|
|
49795
|
+
${highlights}` : ""
|
|
49796
|
+
].filter(Boolean).join("\n");
|
|
49797
|
+
}
|
|
49798
|
+
emitWorkflow(type, run, sessionId) {
|
|
49799
|
+
this.bus.emitEvent({
|
|
49800
|
+
payload: this.snapshot(run),
|
|
49801
|
+
session_id: sessionId ?? void 0,
|
|
49802
|
+
type
|
|
49803
|
+
});
|
|
49804
|
+
}
|
|
49805
|
+
emitSubagent(type, run, worker, sessionId) {
|
|
49806
|
+
this.bus.emitEvent({
|
|
49807
|
+
payload: {
|
|
49808
|
+
cost_usd: worker.costUsd,
|
|
49809
|
+
current_step: worker.currentStep,
|
|
49810
|
+
depth: worker.depth,
|
|
49811
|
+
details: worker.details,
|
|
49812
|
+
duration_seconds: worker.durationSeconds,
|
|
49813
|
+
files_read: worker.filesRead,
|
|
49814
|
+
files_written: worker.filesWritten,
|
|
49815
|
+
goal: worker.goal,
|
|
49816
|
+
input_tokens: worker.inputTokens,
|
|
49817
|
+
output_tail: worker.outputTail?.map((entry) => ({ is_error: entry.isError, preview: entry.preview, tool: entry.tool })),
|
|
49818
|
+
output_tokens: worker.outputTokens,
|
|
49819
|
+
parent_id: worker.parentId,
|
|
49820
|
+
status: worker.status,
|
|
49821
|
+
subagent_id: worker.id,
|
|
49822
|
+
summary: worker.summary,
|
|
49823
|
+
task_count: worker.taskCount,
|
|
49824
|
+
task_index: worker.index,
|
|
49825
|
+
text: worker.currentStep ?? worker.summary,
|
|
49826
|
+
tool_count: worker.toolCount,
|
|
49827
|
+
verification_state: worker.verificationState,
|
|
49828
|
+
workflow_id: run.id,
|
|
49829
|
+
worker_role: worker.workerRole
|
|
49830
|
+
},
|
|
49831
|
+
session_id: sessionId ?? void 0,
|
|
49832
|
+
type
|
|
49833
|
+
});
|
|
49834
|
+
}
|
|
49835
|
+
};
|
|
49836
|
+
__name(buildPlannerSystemPrompt, "buildPlannerSystemPrompt");
|
|
49837
|
+
__name(parseModelPlan, "parseModelPlan");
|
|
49838
|
+
__name(ensureSynthesizer, "ensureSynthesizer");
|
|
49839
|
+
__name(buildModelWorkerPrompt, "buildModelWorkerPrompt");
|
|
49840
|
+
__name(normalizeRole, "normalizeRole");
|
|
49841
|
+
__name(extractJsonObject, "extractJsonObject");
|
|
49842
|
+
__name(uniqueWorkerId, "uniqueWorkerId");
|
|
49843
|
+
__name(scopedWorkerId, "scopedWorkerId");
|
|
49844
|
+
__name(firstParagraph, "firstParagraph");
|
|
49845
|
+
__name(inferVerificationState, "inferVerificationState");
|
|
49846
|
+
__name(dependencyOutputContext, "dependencyOutputContext");
|
|
49847
|
+
}
|
|
49848
|
+
});
|
|
49849
|
+
|
|
49850
|
+
// src/rpcHandlers.ts
|
|
49851
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
49852
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
49853
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync29, rmSync, statSync as statSync4, writeFileSync as writeFileSync20 } from "node:fs";
|
|
49854
|
+
import { homedir as homedir29 } from "node:os";
|
|
49855
|
+
import { basename as basename4, extname as extname4, join as join43 } from "node:path";
|
|
48228
49856
|
function registerRpcHandlers(options) {
|
|
48229
49857
|
const { agentConfig, agentLoop, bridgeEmitter, bridgeManager, bus, mcpManager, systemPromptFn, toolRegistry, voiceManager } = options;
|
|
48230
49858
|
let currentRun = null;
|
|
48231
49859
|
const pendingResponders = /* @__PURE__ */ new Map();
|
|
48232
49860
|
const promptCollector = createPromptCollector(bus, () => agentLoop.sessionId);
|
|
49861
|
+
const workflowManager = new WorkflowManager(agentConfig, toolRegistry, systemPromptFn, bus);
|
|
48233
49862
|
const OAUTH_FLOW_TTL_MS = 15 * 60 * 1e3;
|
|
48234
49863
|
const oauthFlows = /* @__PURE__ */ new Map();
|
|
48235
49864
|
const cleanupOAuthFlow = /* @__PURE__ */ __name((flowId, abort) => {
|
|
@@ -48248,7 +49877,7 @@ function registerRpcHandlers(options) {
|
|
|
48248
49877
|
const user = bridgeUser(rawEvent, source);
|
|
48249
49878
|
const text = formatBridgeText(rawEvent, source, user);
|
|
48250
49879
|
bus.emitEvent({
|
|
48251
|
-
payload: { source, task_id:
|
|
49880
|
+
payload: { source, task_id: randomUUID14(), text, user },
|
|
48252
49881
|
session_id: agentLoop.sessionId,
|
|
48253
49882
|
type: "bridge.message"
|
|
48254
49883
|
});
|
|
@@ -48257,7 +49886,7 @@ function registerRpcHandlers(options) {
|
|
|
48257
49886
|
bus.registerRpc("setup.status", () => {
|
|
48258
49887
|
const hasConfigKey = Boolean(agentConfig.llm.api_key && agentConfig.llm.api_key !== "proxy-token");
|
|
48259
49888
|
const hasEnvKey = Boolean(
|
|
48260
|
-
process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GITHUB_TOKEN
|
|
49889
|
+
process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GITHUB_COPILOT_TOKEN || process.env.GITHUB_TOKEN
|
|
48261
49890
|
);
|
|
48262
49891
|
const hasStoredCredential = PROVIDERS2.some((p) => {
|
|
48263
49892
|
try {
|
|
@@ -48605,12 +50234,12 @@ ${helpMessage}` : field.label;
|
|
|
48605
50234
|
const id = String(params.session_id ?? agentLoop.sessionId) || agentLoop.sessionId;
|
|
48606
50235
|
const data = loadSession(id);
|
|
48607
50236
|
const messages = data?.messages ?? agentLoop.history;
|
|
48608
|
-
const exportDir =
|
|
48609
|
-
if (!
|
|
48610
|
-
|
|
50237
|
+
const exportDir = join43(homedir29(), ".openjaw-agent", "exports");
|
|
50238
|
+
if (!existsSync32(exportDir)) {
|
|
50239
|
+
mkdirSync18(exportDir, { recursive: true });
|
|
48611
50240
|
}
|
|
48612
50241
|
const safeId = id.replace(/[^a-zA-Z0-9_.-]/g, "_") || agentLoop.sessionId;
|
|
48613
|
-
const file2 =
|
|
50242
|
+
const file2 = join43(exportDir, `session-${safeId}.md`);
|
|
48614
50243
|
const lines = [
|
|
48615
50244
|
`# OpenJaw Agent Session ${id}`,
|
|
48616
50245
|
`Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
@@ -48623,7 +50252,7 @@ ${helpMessage}` : field.label;
|
|
|
48623
50252
|
for (const message of messages) {
|
|
48624
50253
|
lines.push(...sessionMessageToMarkdown(message));
|
|
48625
50254
|
}
|
|
48626
|
-
|
|
50255
|
+
writeFileSync20(file2, lines.join("\n"), "utf-8");
|
|
48627
50256
|
return { file: file2, message_count: messages.length };
|
|
48628
50257
|
});
|
|
48629
50258
|
bus.registerRpc("session.interrupt", () => {
|
|
@@ -48640,8 +50269,8 @@ ${helpMessage}` : field.label;
|
|
|
48640
50269
|
return { deleted: "" };
|
|
48641
50270
|
}
|
|
48642
50271
|
try {
|
|
48643
|
-
const file2 =
|
|
48644
|
-
if (
|
|
50272
|
+
const file2 = join43(homedir29(), ".openjaw-agent", "sessions", `${id}.json`);
|
|
50273
|
+
if (existsSync32(file2)) {
|
|
48645
50274
|
rmSync(file2);
|
|
48646
50275
|
}
|
|
48647
50276
|
return { deleted: id };
|
|
@@ -48666,26 +50295,14 @@ ${helpMessage}` : field.label;
|
|
|
48666
50295
|
bus.log("info", `session.steer ${String(params.text ?? "").slice(0, 200)}`);
|
|
48667
50296
|
return { status: "queued", text: String(params.text ?? "") };
|
|
48668
50297
|
});
|
|
48669
|
-
let pendingImageSeq = 0;
|
|
48670
50298
|
let pendingImage = null;
|
|
48671
|
-
const nextImageAttachmentId = /* @__PURE__ */ __name(() => `img-${Date.now().toString(36)}-${++pendingImageSeq}`, "nextImageAttachmentId");
|
|
48672
50299
|
bus.registerRpc("prompt.submit", async (params) => {
|
|
48673
50300
|
const text = String(params.text ?? "");
|
|
48674
50301
|
if (!text) return { ok: false };
|
|
48675
50302
|
const systemPromptArr = await systemPromptFn();
|
|
48676
50303
|
const systemPrompt = systemPromptArr.join("\n\n");
|
|
48677
|
-
const
|
|
48678
|
-
|
|
48679
|
-
if (imageForSubmit) {
|
|
48680
|
-
const modelInfo = agentLoop.getActiveModelMetadata() ?? await agentLoop.listModels().then(() => agentLoop.getActiveModelMetadata()).catch(() => void 0);
|
|
48681
|
-
if (modelInfo?.supportsVision === false) {
|
|
48682
|
-
throw new Error(`model ${agentLoop.model} does not support image input; switch to a vision-capable model before submitting an image`);
|
|
48683
|
-
}
|
|
48684
|
-
}
|
|
48685
|
-
const imageData = imageForSubmit ? { base64: imageForSubmit.base64, mimeType: imageForSubmit.mimeType } : void 0;
|
|
48686
|
-
if (imageForSubmit) {
|
|
48687
|
-
pendingImage = null;
|
|
48688
|
-
}
|
|
50304
|
+
const imageData = pendingImage ? { base64: pendingImage.base64, mimeType: pendingImage.mimeType } : void 0;
|
|
50305
|
+
pendingImage = null;
|
|
48689
50306
|
currentRun = { abort: /* @__PURE__ */ __name(() => agentLoop.abort(), "abort") };
|
|
48690
50307
|
void streamAgentRun({ agentLoop, bus, systemPrompt, text, imageData }).finally(() => {
|
|
48691
50308
|
currentRun = null;
|
|
@@ -48726,11 +50343,11 @@ ${helpMessage}` : field.label;
|
|
|
48726
50343
|
bus.registerRpc("shell.exec", async (params) => {
|
|
48727
50344
|
const command = String(params.command ?? "");
|
|
48728
50345
|
if (!command) return { code: -1, stderr: "empty command" };
|
|
48729
|
-
return await new Promise((
|
|
50346
|
+
return await new Promise((resolve6) => {
|
|
48730
50347
|
const isWin = process.platform === "win32";
|
|
48731
50348
|
const shell = isWin ? "powershell.exe" : "sh";
|
|
48732
50349
|
const args = isWin ? ["-NoProfile", "-Command", command] : ["-c", command];
|
|
48733
|
-
const child =
|
|
50350
|
+
const child = spawn6(shell, args, { timeout: 3e4 });
|
|
48734
50351
|
let stdout = "";
|
|
48735
50352
|
let stderr = "";
|
|
48736
50353
|
let truncated = false;
|
|
@@ -48753,40 +50370,14 @@ ${helpMessage}` : field.label;
|
|
|
48753
50370
|
if (truncated) {
|
|
48754
50371
|
stderr += "\n[output truncated to 1MB]";
|
|
48755
50372
|
}
|
|
48756
|
-
|
|
50373
|
+
resolve6({ code: code ?? -1, stderr, stdout });
|
|
48757
50374
|
});
|
|
48758
50375
|
child.on("error", (err) => {
|
|
48759
|
-
|
|
50376
|
+
resolve6({ code: -1, stderr: err.message });
|
|
48760
50377
|
});
|
|
48761
50378
|
});
|
|
48762
50379
|
});
|
|
48763
50380
|
bus.registerRpc("clipboard.paste", async () => {
|
|
48764
|
-
try {
|
|
48765
|
-
if (clipboardHasImage()) {
|
|
48766
|
-
const img = readClipboardImage();
|
|
48767
|
-
if (img) {
|
|
48768
|
-
const attachmentId = nextImageAttachmentId();
|
|
48769
|
-
pendingImage = {
|
|
48770
|
-
attachmentId,
|
|
48771
|
-
base64: img.base64,
|
|
48772
|
-
mimeType: img.mimeType,
|
|
48773
|
-
name: "clipboard.png"
|
|
48774
|
-
};
|
|
48775
|
-
const byteLength = Math.ceil(img.base64.length * 3 / 4);
|
|
48776
|
-
const tokenEstimate = Math.max(1, Math.ceil(byteLength / 750));
|
|
48777
|
-
return {
|
|
48778
|
-
attached: true,
|
|
48779
|
-
attachment_id: attachmentId,
|
|
48780
|
-
count: 1,
|
|
48781
|
-
height: img.height,
|
|
48782
|
-
name: "clipboard.png",
|
|
48783
|
-
token_estimate: tokenEstimate,
|
|
48784
|
-
width: img.width
|
|
48785
|
-
};
|
|
48786
|
-
}
|
|
48787
|
-
}
|
|
48788
|
-
} catch {
|
|
48789
|
-
}
|
|
48790
50381
|
try {
|
|
48791
50382
|
const text = await readClipboardText();
|
|
48792
50383
|
return { attached: false, message: text ?? "" };
|
|
@@ -48904,7 +50495,7 @@ ${helpMessage}` : field.label;
|
|
|
48904
50495
|
return agentConfig.llm.copilot_enterprise_url;
|
|
48905
50496
|
})();
|
|
48906
50497
|
const flow = await startCopilotDeviceFlow(clientId, enterpriseUrl);
|
|
48907
|
-
const flowId =
|
|
50498
|
+
const flowId = randomUUID14();
|
|
48908
50499
|
const controller = new AbortController();
|
|
48909
50500
|
const timer = setTimeout(() => {
|
|
48910
50501
|
cleanupOAuthFlow(flowId, true);
|
|
@@ -49068,7 +50659,7 @@ ${helpMessage}` : field.label;
|
|
|
49068
50659
|
Object.assign(agentConfig.llm, result.configUpdates);
|
|
49069
50660
|
bus.emitEvent({ payload: sessionInfoSnapshot(agentLoop, toolRegistry), session_id: agentLoop.sessionId, type: "session.info" });
|
|
49070
50661
|
}
|
|
49071
|
-
return { messages };
|
|
50662
|
+
return { config_updates: result.configUpdates, messages };
|
|
49072
50663
|
});
|
|
49073
50664
|
bus.registerRpc("provider.disconnect", (params) => {
|
|
49074
50665
|
const provider = String(params.provider ?? "").trim();
|
|
@@ -49128,6 +50719,32 @@ ${helpMessage}` : field.label;
|
|
|
49128
50719
|
tasks: forks.map((task) => ({ id: task.id, prompt: task.prompt, status: task.status }))
|
|
49129
50720
|
};
|
|
49130
50721
|
});
|
|
50722
|
+
bus.registerRpc("workflow.start", async (params) => {
|
|
50723
|
+
const goal = String(params.goal ?? params.prompt ?? "").trim();
|
|
50724
|
+
if (!goal) return { error: "usage: /workflow <goal>", ok: false };
|
|
50725
|
+
const result = await workflowManager.start(goal, agentLoop.sessionId);
|
|
50726
|
+
return { ok: true, run: result.run };
|
|
50727
|
+
});
|
|
50728
|
+
bus.registerRpc("workflow.status", (params) => {
|
|
50729
|
+
const id = String(params.id ?? params.run_id ?? "").trim();
|
|
50730
|
+
const run = workflowManager.status(id || void 0);
|
|
50731
|
+
return run ? { ok: true, run } : { error: id ? `workflow not found: ${id}` : "no workflows yet", ok: false };
|
|
50732
|
+
});
|
|
50733
|
+
bus.registerRpc("workflow.list", (params) => {
|
|
50734
|
+
const limit = Number(params.limit ?? 30);
|
|
50735
|
+
return { ok: true, runs: workflowManager.list(Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 30) };
|
|
50736
|
+
});
|
|
50737
|
+
bus.registerRpc("workflow.show", (params) => {
|
|
50738
|
+
const id = String(params.id ?? params.run_id ?? "").trim();
|
|
50739
|
+
const run = workflowManager.status(id || void 0);
|
|
50740
|
+
return run ? { ok: true, run, summary: run.summary ?? "" } : { error: id ? `workflow not found: ${id}` : "no workflows yet", ok: false };
|
|
50741
|
+
});
|
|
50742
|
+
bus.registerRpc("workflow.cancel", (params) => {
|
|
50743
|
+
const id = String(params.id ?? params.run_id ?? params.worker_id ?? "").trim();
|
|
50744
|
+
if (!id) return { error: "usage: /workflow cancel <runId|workerId>", ok: false };
|
|
50745
|
+
const result = workflowManager.cancel(id);
|
|
50746
|
+
return result.found ? { ok: true, ...result } : { error: `workflow or worker not found: ${id}`, ok: false };
|
|
50747
|
+
});
|
|
49131
50748
|
bus.registerRpc("schedule.add", (params) => {
|
|
49132
50749
|
const prompt = String(params.prompt ?? "").trim();
|
|
49133
50750
|
const raw = String(params.schedule ?? "").trim();
|
|
@@ -49398,43 +51015,33 @@ ${helpMessage}` : field.label;
|
|
|
49398
51015
|
const firstSpace = raw.search(/\s/);
|
|
49399
51016
|
const path3 = firstSpace > 0 ? raw.slice(0, firstSpace) : raw;
|
|
49400
51017
|
const remainder = firstSpace > 0 ? raw.slice(firstSpace).trim() : "";
|
|
49401
|
-
if (!
|
|
51018
|
+
if (!existsSync32(path3)) {
|
|
49402
51019
|
throw new Error(`image.attach: file not found: ${path3}`);
|
|
49403
51020
|
}
|
|
49404
51021
|
let buffer;
|
|
49405
51022
|
let fileSize = 0;
|
|
49406
51023
|
try {
|
|
49407
|
-
buffer =
|
|
51024
|
+
buffer = readFileSync29(path3);
|
|
49408
51025
|
fileSize = statSync4(path3).size;
|
|
49409
51026
|
} catch (err) {
|
|
49410
51027
|
throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
|
|
49411
51028
|
}
|
|
49412
51029
|
const ext = extname4(path3).toLowerCase().replace(/^\./, "");
|
|
49413
51030
|
const mimeType = ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/png";
|
|
49414
|
-
const attachmentId = nextImageAttachmentId();
|
|
49415
51031
|
pendingImage = {
|
|
49416
|
-
attachmentId,
|
|
49417
51032
|
base64: buffer.toString("base64"),
|
|
49418
51033
|
mimeType,
|
|
49419
|
-
name:
|
|
51034
|
+
name: basename4(path3)
|
|
49420
51035
|
};
|
|
49421
51036
|
const tokenEstimate = Math.max(1, Math.ceil(buffer.byteLength / 750));
|
|
49422
51037
|
return {
|
|
49423
|
-
attachment_id: attachmentId,
|
|
49424
51038
|
height: 0,
|
|
49425
|
-
name:
|
|
51039
|
+
name: basename4(path3),
|
|
49426
51040
|
remainder,
|
|
49427
51041
|
token_estimate: tokenEstimate,
|
|
49428
51042
|
width: 0
|
|
49429
51043
|
};
|
|
49430
51044
|
});
|
|
49431
|
-
bus.registerRpc("image.clear", (params) => {
|
|
49432
|
-
const attachmentId = String(params.attachment_id ?? "");
|
|
49433
|
-
if (!attachmentId || pendingImage?.attachmentId === attachmentId) {
|
|
49434
|
-
pendingImage = null;
|
|
49435
|
-
}
|
|
49436
|
-
return { ok: true };
|
|
49437
|
-
});
|
|
49438
51045
|
bus.registerRpc("process.stop", () => {
|
|
49439
51046
|
const wasRunning = agentLoop.isRunning;
|
|
49440
51047
|
agentLoop.abort();
|
|
@@ -49460,13 +51067,13 @@ ${helpMessage}` : field.label;
|
|
|
49460
51067
|
}
|
|
49461
51068
|
});
|
|
49462
51069
|
bus.registerRpc("reload.env", () => {
|
|
49463
|
-
const envPath =
|
|
49464
|
-
if (!
|
|
51070
|
+
const envPath = join43(homedir29(), ".openjaw-agent", ".env");
|
|
51071
|
+
if (!existsSync32(envPath)) {
|
|
49465
51072
|
return { updated: 0 };
|
|
49466
51073
|
}
|
|
49467
51074
|
let updated = 0;
|
|
49468
51075
|
try {
|
|
49469
|
-
const raw =
|
|
51076
|
+
const raw = readFileSync29(envPath, "utf-8");
|
|
49470
51077
|
for (const line of raw.split(/\r?\n/)) {
|
|
49471
51078
|
const trimmed = line.trim();
|
|
49472
51079
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -49506,8 +51113,25 @@ ${helpMessage}` : field.label;
|
|
|
49506
51113
|
error: "checkpoints are not enabled in this build",
|
|
49507
51114
|
success: false
|
|
49508
51115
|
}));
|
|
49509
|
-
bus.registerRpc("spawn_tree.
|
|
49510
|
-
|
|
51116
|
+
bus.registerRpc("spawn_tree.save", (params) => ({
|
|
51117
|
+
path: saveSpawnTreeSnapshot({
|
|
51118
|
+
finished_at: typeof params.finished_at === "number" ? params.finished_at : Date.now() / 1e3,
|
|
51119
|
+
label: typeof params.label === "string" ? params.label : void 0,
|
|
51120
|
+
session_id: typeof params.session_id === "string" ? params.session_id : agentLoop.sessionId ?? "default",
|
|
51121
|
+
started_at: typeof params.started_at === "number" || params.started_at === null ? params.started_at : null,
|
|
51122
|
+
subagents: Array.isArray(params.subagents) ? params.subagents : []
|
|
51123
|
+
})
|
|
51124
|
+
}));
|
|
51125
|
+
bus.registerRpc("spawn_tree.list", (params) => ({
|
|
51126
|
+
entries: listSpawnTreeSnapshots(
|
|
51127
|
+
typeof params.session_id === "string" ? params.session_id : agentLoop.sessionId ?? "default",
|
|
51128
|
+
typeof params.limit === "number" ? params.limit : 30
|
|
51129
|
+
)
|
|
51130
|
+
}));
|
|
51131
|
+
bus.registerRpc("spawn_tree.load", (params) => {
|
|
51132
|
+
const path3 = String(params.path ?? "").trim();
|
|
51133
|
+
return path3 ? loadSpawnTreeSnapshot(path3) ?? { subagents: [] } : { subagents: [] };
|
|
51134
|
+
});
|
|
49511
51135
|
bus.registerRpc("skills.reload", async () => {
|
|
49512
51136
|
try {
|
|
49513
51137
|
clearSkillsCache();
|
|
@@ -49591,9 +51215,19 @@ ${helpMessage}` : field.label;
|
|
|
49591
51215
|
}
|
|
49592
51216
|
return { output: name ? `unknown command: /${name}` : "(no command)", type: "exec" };
|
|
49593
51217
|
});
|
|
49594
|
-
bus.registerRpc("delegation.status", () => ({
|
|
51218
|
+
bus.registerRpc("delegation.status", () => ({
|
|
51219
|
+
active: [],
|
|
51220
|
+
max_concurrent_children: agentConfig.features?.dynamic_workflows?.hard_max_concurrent_workers ?? 128,
|
|
51221
|
+
max_spawn_depth: agentConfig.features?.dynamic_workflows?.hard_max_workers ?? 1024,
|
|
51222
|
+
paused: false
|
|
51223
|
+
}));
|
|
49595
51224
|
bus.registerRpc("delegation.pause", (params) => ({ paused: Boolean(params.paused) }));
|
|
49596
|
-
bus.registerRpc("subagent.interrupt", () =>
|
|
51225
|
+
bus.registerRpc("subagent.interrupt", (params) => {
|
|
51226
|
+
const id = String(params.subagent_id ?? "").trim();
|
|
51227
|
+
if (!id) return { found: false };
|
|
51228
|
+
const result = workflowManager.cancel(id);
|
|
51229
|
+
return { found: result.found, subagent_id: id };
|
|
51230
|
+
});
|
|
49597
51231
|
syncMcpTools(mcpManager, toolRegistry);
|
|
49598
51232
|
bus.emitEvent({
|
|
49599
51233
|
payload: sessionInfoSnapshot(agentLoop, toolRegistry),
|
|
@@ -49634,7 +51268,6 @@ var init_rpcHandlers = __esm({
|
|
|
49634
51268
|
init_uiStore();
|
|
49635
51269
|
init_catalog();
|
|
49636
51270
|
init_clipboard();
|
|
49637
|
-
init_clipboard_image();
|
|
49638
51271
|
init_models_static();
|
|
49639
51272
|
init_providers();
|
|
49640
51273
|
init_types();
|
|
@@ -49642,6 +51275,8 @@ var init_rpcHandlers = __esm({
|
|
|
49642
51275
|
init_registry2();
|
|
49643
51276
|
init_usage();
|
|
49644
51277
|
init_usageSnapshot();
|
|
51278
|
+
init_manager();
|
|
51279
|
+
init_persistence();
|
|
49645
51280
|
init_registry3();
|
|
49646
51281
|
PROVIDERS2 = ["anthropic", "openai", "github-copilot"];
|
|
49647
51282
|
PROVIDER_LABELS = {
|
|
@@ -49827,8 +51462,8 @@ var init_rpcHandlers = __esm({
|
|
|
49827
51462
|
${raw}`;
|
|
49828
51463
|
return `${header}: ${raw}`;
|
|
49829
51464
|
}, "formatBridgeText");
|
|
49830
|
-
runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((
|
|
49831
|
-
const child =
|
|
51465
|
+
runProcess = /* @__PURE__ */ __name((command, args, timeout = 2e4) => new Promise((resolve6) => {
|
|
51466
|
+
const child = spawn6(command, args, { timeout, windowsHide: true });
|
|
49832
51467
|
let stdout = "";
|
|
49833
51468
|
let stderr = "";
|
|
49834
51469
|
let truncated = false;
|
|
@@ -49851,9 +51486,9 @@ ${raw}`;
|
|
|
49851
51486
|
if (truncated) {
|
|
49852
51487
|
stderr += "\n[output truncated to 1MB]";
|
|
49853
51488
|
}
|
|
49854
|
-
|
|
51489
|
+
resolve6({ code: code ?? -1, stderr, stdout });
|
|
49855
51490
|
});
|
|
49856
|
-
child.on("error", (err) =>
|
|
51491
|
+
child.on("error", (err) => resolve6({ code: -1, stderr: err.message, stdout }));
|
|
49857
51492
|
}), "runProcess");
|
|
49858
51493
|
contentToText = /* @__PURE__ */ __name((content) => {
|
|
49859
51494
|
if (typeof content === "string") return content;
|
|
@@ -50011,14 +51646,14 @@ var init_memoryMonitor = __esm({
|
|
|
50011
51646
|
});
|
|
50012
51647
|
|
|
50013
51648
|
// src/lib/openExternalUrl.ts
|
|
50014
|
-
import { spawn as
|
|
51649
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
50015
51650
|
import { platform } from "node:os";
|
|
50016
51651
|
function openExternalUrl(rawUrl, dependencies = {}) {
|
|
50017
51652
|
const url = parseSafeUrl(rawUrl);
|
|
50018
51653
|
if (!url) {
|
|
50019
51654
|
return false;
|
|
50020
51655
|
}
|
|
50021
|
-
const spawnFn = dependencies.spawn ??
|
|
51656
|
+
const spawnFn = dependencies.spawn ?? spawn7;
|
|
50022
51657
|
const platformId = dependencies.platform?.() ?? platform();
|
|
50023
51658
|
const command = openCommand(platformId);
|
|
50024
51659
|
if (!command) {
|
|
@@ -50808,7 +52443,7 @@ async function terminalParityHints(env2 = process.env, options) {
|
|
|
50808
52443
|
hints.push({
|
|
50809
52444
|
key: "remote",
|
|
50810
52445
|
tone: "warn",
|
|
50811
|
-
message: "SSH session detected \xB7 text clipboard can bridge via OSC52, but image clipboard and local screenshot paths still depend on the machine running
|
|
52446
|
+
message: "SSH session detected \xB7 text clipboard can bridge via OSC52, but image clipboard and local screenshot paths still depend on the machine running OpenJaw"
|
|
50812
52447
|
});
|
|
50813
52448
|
}
|
|
50814
52449
|
return hints;
|
|
@@ -50913,13 +52548,13 @@ var init_setup2 = __esm({
|
|
|
50913
52548
|
SETUP_REQUIRED_TITLE = "Setup Required";
|
|
50914
52549
|
buildSetupRequiredSections = /* @__PURE__ */ __name(() => [
|
|
50915
52550
|
{
|
|
50916
|
-
text: "
|
|
52551
|
+
text: "OpenJaw needs a model provider before the TUI can start a session. Run /connect to set up a provider, then /model to choose a model."
|
|
50917
52552
|
},
|
|
50918
52553
|
{
|
|
50919
52554
|
rows: [
|
|
50920
|
-
["/
|
|
50921
|
-
["/
|
|
50922
|
-
["Ctrl+C", "exit and
|
|
52555
|
+
["/connect", "set up provider credentials in-place"],
|
|
52556
|
+
["/model", "choose or switch model after connecting"],
|
|
52557
|
+
["Ctrl+C", "exit and restart OpenJaw after setup if needed"]
|
|
50923
52558
|
],
|
|
50924
52559
|
title: "Actions"
|
|
50925
52560
|
}
|
|
@@ -51222,7 +52857,7 @@ var init_reasoning2 = __esm({
|
|
|
51222
52857
|
});
|
|
51223
52858
|
|
|
51224
52859
|
// src/app/turnStore.ts
|
|
51225
|
-
import { atom as
|
|
52860
|
+
import { atom as atom6 } from "nanostores";
|
|
51226
52861
|
import { useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
51227
52862
|
var buildTurnState, $turnState, getTurnState, subscribeTurn, useTurnSelector, patchTurnState, toggleTodoCollapsed, archiveDoneTodos, archiveTodosAtTurnEnd, resetTurnState;
|
|
51228
52863
|
var init_turnStore = __esm({
|
|
@@ -51246,7 +52881,7 @@ var init_turnStore = __esm({
|
|
|
51246
52881
|
tools: [],
|
|
51247
52882
|
turnTrail: []
|
|
51248
52883
|
}), "buildTurnState");
|
|
51249
|
-
$turnState =
|
|
52884
|
+
$turnState = atom6(buildTurnState());
|
|
51250
52885
|
getTurnState = /* @__PURE__ */ __name(() => $turnState.get(), "getTurnState");
|
|
51251
52886
|
subscribeTurn = /* @__PURE__ */ __name((cb) => $turnState.listen(() => cb()), "subscribeTurn");
|
|
51252
52887
|
useTurnSelector = /* @__PURE__ */ __name((selector) => useSyncExternalStore4(
|
|
@@ -51846,7 +53481,9 @@ ${stripped}
|
|
|
51846
53481
|
...base,
|
|
51847
53482
|
apiCalls: p.api_calls ?? base.apiCalls,
|
|
51848
53483
|
costUsd: p.cost_usd ?? base.costUsd,
|
|
53484
|
+
currentStep: p.current_step ?? base.currentStep,
|
|
51849
53485
|
depth: p.depth ?? base.depth,
|
|
53486
|
+
details: p.details ?? base.details,
|
|
51850
53487
|
filesRead: p.files_read ?? base.filesRead,
|
|
51851
53488
|
filesWritten: p.files_written ?? base.filesWritten,
|
|
51852
53489
|
goal: p.goal || base.goal,
|
|
@@ -51860,6 +53497,9 @@ ${stripped}
|
|
|
51860
53497
|
taskCount: p.task_count ?? base.taskCount,
|
|
51861
53498
|
toolCount: p.tool_count ?? base.toolCount,
|
|
51862
53499
|
toolsets: p.toolsets ?? base.toolsets,
|
|
53500
|
+
verificationState: p.verification_state ?? base.verificationState,
|
|
53501
|
+
workflowId: p.workflow_id ?? base.workflowId,
|
|
53502
|
+
workerRole: p.worker_role ?? base.workerRole,
|
|
51863
53503
|
...patch(base)
|
|
51864
53504
|
};
|
|
51865
53505
|
const subagents = existing ? state.subagents.map((item) => item.id === id ? next : item) : [...state.subagents, next].sort((a, b) => a.depth - b.depth || a.index - b.index);
|
|
@@ -51946,7 +53586,7 @@ function createGatewayEventHandler(ctx) {
|
|
|
51946
53586
|
setTimeout(async () => {
|
|
51947
53587
|
let sid = getUiState().sid;
|
|
51948
53588
|
for (let i = 0; !sid && i < 40; i += 1) {
|
|
51949
|
-
await new Promise((
|
|
53589
|
+
await new Promise((resolve6) => setTimeout(resolve6, 100));
|
|
51950
53590
|
sid = getUiState().sid;
|
|
51951
53591
|
}
|
|
51952
53592
|
if (!sid) {
|
|
@@ -52293,6 +53933,26 @@ function createGatewayEventHandler(ctx) {
|
|
|
52293
53933
|
{ createIfMissing: false }
|
|
52294
53934
|
);
|
|
52295
53935
|
return;
|
|
53936
|
+
case "workflow.start":
|
|
53937
|
+
case "workflow.progress":
|
|
53938
|
+
setWorkflowSnapshot(ev.payload);
|
|
53939
|
+
return;
|
|
53940
|
+
case "workflow.complete": {
|
|
53941
|
+
setWorkflowSnapshot(ev.payload);
|
|
53942
|
+
const text = String(ev.payload?.summary ?? "").trim();
|
|
53943
|
+
if (text) {
|
|
53944
|
+
appendMessage({ role: "assistant", text });
|
|
53945
|
+
}
|
|
53946
|
+
return;
|
|
53947
|
+
}
|
|
53948
|
+
case "workflow.error": {
|
|
53949
|
+
setWorkflowSnapshot(ev.payload);
|
|
53950
|
+
const text = String(ev.payload?.summary ?? "").trim();
|
|
53951
|
+
if (text) {
|
|
53952
|
+
sys(text);
|
|
53953
|
+
}
|
|
53954
|
+
return;
|
|
53955
|
+
}
|
|
52296
53956
|
case "message.delta":
|
|
52297
53957
|
turnController.recordMessageDelta(ev.payload ?? {});
|
|
52298
53958
|
return;
|
|
@@ -52327,7 +53987,7 @@ function createGatewayEventHandler(ctx) {
|
|
|
52327
53987
|
}
|
|
52328
53988
|
};
|
|
52329
53989
|
}
|
|
52330
|
-
var NO_PROVIDER_RE, statusFromBusy, applySkin, dropBgTask,
|
|
53990
|
+
var NO_PROVIDER_RE, statusFromBusy, applySkin, dropBgTask, pushUnique2, pushThinking, pushNote, pushTool;
|
|
52331
53991
|
var init_createGatewayEventHandler = __esm({
|
|
52332
53992
|
"src/app/createGatewayEventHandler.ts"() {
|
|
52333
53993
|
"use strict";
|
|
@@ -52342,6 +54002,7 @@ var init_createGatewayEventHandler = __esm({
|
|
|
52342
54002
|
init_overlayStore();
|
|
52343
54003
|
init_turnController();
|
|
52344
54004
|
init_uiStore();
|
|
54005
|
+
init_workflowStore();
|
|
52345
54006
|
NO_PROVIDER_RE = /\bNo (?:LLM|inference) provider configured\b/i;
|
|
52346
54007
|
statusFromBusy = /* @__PURE__ */ __name(() => getUiState().busy ? "running\u2026" : "ready", "statusFromBusy");
|
|
52347
54008
|
applySkin = /* @__PURE__ */ __name((s) => patchUiState({
|
|
@@ -52359,10 +54020,10 @@ var init_createGatewayEventHandler = __esm({
|
|
|
52359
54020
|
next.delete(taskId);
|
|
52360
54021
|
return { ...state, bgTasks: next };
|
|
52361
54022
|
}), "dropBgTask");
|
|
52362
|
-
|
|
52363
|
-
pushThinking =
|
|
52364
|
-
pushNote =
|
|
52365
|
-
pushTool =
|
|
54023
|
+
pushUnique2 = /* @__PURE__ */ __name((max) => (xs, x) => xs.at(-1) === x ? xs : [...xs, x].slice(-max), "pushUnique");
|
|
54024
|
+
pushThinking = pushUnique2(6);
|
|
54025
|
+
pushNote = pushUnique2(6);
|
|
54026
|
+
pushTool = pushUnique2(8);
|
|
52366
54027
|
__name(createGatewayEventHandler, "createGatewayEventHandler");
|
|
52367
54028
|
}
|
|
52368
54029
|
});
|
|
@@ -52506,12 +54167,12 @@ var init_createSlashHandler = __esm({
|
|
|
52506
54167
|
});
|
|
52507
54168
|
|
|
52508
54169
|
// src/app/inputSelectionStore.ts
|
|
52509
|
-
import { atom as
|
|
54170
|
+
import { atom as atom7 } from "nanostores";
|
|
52510
54171
|
var $inputSelection, setInputSelection, getInputSelection;
|
|
52511
54172
|
var init_inputSelectionStore = __esm({
|
|
52512
54173
|
"src/app/inputSelectionStore.ts"() {
|
|
52513
54174
|
"use strict";
|
|
52514
|
-
$inputSelection =
|
|
54175
|
+
$inputSelection = atom7(null);
|
|
52515
54176
|
setInputSelection = /* @__PURE__ */ __name((next) => $inputSelection.set(next), "setInputSelection");
|
|
52516
54177
|
getInputSelection = /* @__PURE__ */ __name(() => $inputSelection.get(), "getInputSelection");
|
|
52517
54178
|
}
|
|
@@ -52672,21 +54333,21 @@ var init_useCompletion = __esm({
|
|
|
52672
54333
|
});
|
|
52673
54334
|
|
|
52674
54335
|
// src/lib/history.ts
|
|
52675
|
-
import { appendFileSync as appendFileSync4, existsSync as
|
|
52676
|
-
import { homedir as
|
|
52677
|
-
import { join as
|
|
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";
|
|
52678
54339
|
function load() {
|
|
52679
54340
|
if (cache3) {
|
|
52680
54341
|
return cache3;
|
|
52681
54342
|
}
|
|
52682
54343
|
try {
|
|
52683
|
-
if (!
|
|
54344
|
+
if (!existsSync33(file)) {
|
|
52684
54345
|
cache3 = [];
|
|
52685
54346
|
return cache3;
|
|
52686
54347
|
}
|
|
52687
54348
|
const entries = [];
|
|
52688
54349
|
let current = [];
|
|
52689
|
-
for (const line of
|
|
54350
|
+
for (const line of readFileSync30(file, "utf8").split("\n")) {
|
|
52690
54351
|
if (line.startsWith("+")) {
|
|
52691
54352
|
current.push(line.slice(1));
|
|
52692
54353
|
} else if (current.length) {
|
|
@@ -52717,8 +54378,8 @@ function append(line) {
|
|
|
52717
54378
|
items.splice(0, items.length - MAX);
|
|
52718
54379
|
}
|
|
52719
54380
|
try {
|
|
52720
|
-
if (!
|
|
52721
|
-
|
|
54381
|
+
if (!existsSync33(dir)) {
|
|
54382
|
+
mkdirSync19(dir, { recursive: true });
|
|
52722
54383
|
}
|
|
52723
54384
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
|
|
52724
54385
|
const encoded = trimmed.split("\n").map((l) => `+${l}`).join("\n");
|
|
@@ -52734,8 +54395,8 @@ var init_history = __esm({
|
|
|
52734
54395
|
"src/lib/history.ts"() {
|
|
52735
54396
|
"use strict";
|
|
52736
54397
|
MAX = 1e3;
|
|
52737
|
-
dir = process.env.OPENJAW_HOME ??
|
|
52738
|
-
file =
|
|
54398
|
+
dir = process.env.OPENJAW_HOME ?? join44(homedir30(), ".openjaw-agent");
|
|
54399
|
+
file = join44(dir, ".openjaw-agent_history");
|
|
52739
54400
|
cache3 = null;
|
|
52740
54401
|
__name(load, "load");
|
|
52741
54402
|
__name(append, "append");
|
|
@@ -52829,7 +54490,7 @@ var init_useQueue = __esm({
|
|
|
52829
54490
|
|
|
52830
54491
|
// src/lib/editor.ts
|
|
52831
54492
|
import { accessSync, constants } from "node:fs";
|
|
52832
|
-
import { delimiter, join as
|
|
54493
|
+
import { delimiter, join as join45 } from "node:path";
|
|
52833
54494
|
var FALLBACKS, isExecutable, resolveEditor;
|
|
52834
54495
|
var init_editor = __esm({
|
|
52835
54496
|
"src/lib/editor.ts"() {
|
|
@@ -52852,7 +54513,7 @@ var init_editor = __esm({
|
|
|
52852
54513
|
return ["notepad.exe"];
|
|
52853
54514
|
}
|
|
52854
54515
|
const dirs = (env2.PATH ?? "").split(delimiter).filter(Boolean);
|
|
52855
|
-
const found = FALLBACKS.flatMap((name) => dirs.map((d) =>
|
|
54516
|
+
const found = FALLBACKS.flatMap((name) => dirs.map((d) => join45(d, name))).find(isExecutable);
|
|
52856
54517
|
return [found ?? "vi"];
|
|
52857
54518
|
}, "resolveEditor");
|
|
52858
54519
|
}
|
|
@@ -52860,9 +54521,9 @@ var init_editor = __esm({
|
|
|
52860
54521
|
|
|
52861
54522
|
// src/app/useComposerState.ts
|
|
52862
54523
|
import { spawnSync } from "node:child_process";
|
|
52863
|
-
import { mkdtempSync, readFileSync as
|
|
54524
|
+
import { mkdtempSync, readFileSync as readFileSync31, rmSync as rmSync2, writeFileSync as writeFileSync21 } from "node:fs";
|
|
52864
54525
|
import { tmpdir as tmpdir12 } from "node:os";
|
|
52865
|
-
import { join as
|
|
54526
|
+
import { join as join46 } from "node:path";
|
|
52866
54527
|
import { useStore } from "@nanostores/react";
|
|
52867
54528
|
import { useCallback as useCallback7, useMemo as useMemo6, useState as useState12 } from "react";
|
|
52868
54529
|
function insertAtCursor(value, cursor, text) {
|
|
@@ -52896,7 +54557,6 @@ function useComposerState({
|
|
|
52896
54557
|
}) {
|
|
52897
54558
|
const [input, setInput] = useState12("");
|
|
52898
54559
|
const [inputBuf, setInputBuf] = useState12([]);
|
|
52899
|
-
const [attachedImage, setAttachedImage] = useState12(null);
|
|
52900
54560
|
const [pasteSnips, setPasteSnips] = useState12([]);
|
|
52901
54561
|
const isBlocked = useStore($isBlocked);
|
|
52902
54562
|
const { querier } = use_stdin_default();
|
|
@@ -52914,25 +54574,14 @@ function useComposerState({
|
|
|
52914
54574
|
} = useQueue();
|
|
52915
54575
|
const { historyRef, historyIdx, setHistoryIdx, historyDraftRef, pushHistory } = useInputHistory();
|
|
52916
54576
|
const { completions, compIdx, setCompIdx, compReplace } = useCompletion(input, isBlocked, gw);
|
|
52917
|
-
const
|
|
52918
|
-
const attachmentId = attachedImage?.attachment_id;
|
|
52919
|
-
setAttachedImage(null);
|
|
52920
|
-
if (attachmentId) {
|
|
52921
|
-
void gw.request("image.clear", { attachment_id: attachmentId }).catch(() => {
|
|
52922
|
-
});
|
|
52923
|
-
}
|
|
52924
|
-
}, [attachedImage?.attachment_id, gw]);
|
|
52925
|
-
const clearIn = useCallback7((options = {}) => {
|
|
54577
|
+
const clearIn = useCallback7(() => {
|
|
52926
54578
|
setInput("");
|
|
52927
54579
|
setInputBuf([]);
|
|
52928
54580
|
setPasteSnips([]);
|
|
52929
|
-
if (!options.keepAttachedImage) {
|
|
52930
|
-
clearAttachedImage();
|
|
52931
|
-
}
|
|
52932
54581
|
setQueueEdit(null);
|
|
52933
54582
|
setHistoryIdx(null);
|
|
52934
54583
|
historyDraftRef.current = "";
|
|
52935
|
-
}, [
|
|
54584
|
+
}, [historyDraftRef, setQueueEdit, setHistoryIdx]);
|
|
52936
54585
|
const handleResolvedPaste = useCallback7(
|
|
52937
54586
|
async ({
|
|
52938
54587
|
bracketed,
|
|
@@ -52955,13 +54604,6 @@ function useComposerState({
|
|
|
52955
54604
|
session_id: sid
|
|
52956
54605
|
});
|
|
52957
54606
|
if (attached?.name) {
|
|
52958
|
-
setAttachedImage({
|
|
52959
|
-
attachment_id: attached.attachment_id,
|
|
52960
|
-
height: attached.height,
|
|
52961
|
-
name: attached.name,
|
|
52962
|
-
token_estimate: attached.token_estimate,
|
|
52963
|
-
width: attached.width
|
|
52964
|
-
});
|
|
52965
54607
|
onImageAttached?.(attached);
|
|
52966
54608
|
const remainder = attached.remainder?.trim() ?? "";
|
|
52967
54609
|
if (!remainder) {
|
|
@@ -53014,7 +54656,7 @@ function useComposerState({
|
|
|
53014
54656
|
}) => {
|
|
53015
54657
|
if (hotkey) {
|
|
53016
54658
|
const preferOsc52 = isRemoteShellSession(process.env);
|
|
53017
|
-
const readPreferredText =
|
|
54659
|
+
const readPreferredText = preferOsc52 ? readOsc52Clipboard(querier).then(async (osc52Text) => {
|
|
53018
54660
|
if (isUsableClipboardText(osc52Text)) {
|
|
53019
54661
|
return osc52Text;
|
|
53020
54662
|
}
|
|
@@ -53024,12 +54666,8 @@ function useComposerState({
|
|
|
53024
54666
|
return clipText;
|
|
53025
54667
|
}
|
|
53026
54668
|
return readOsc52Clipboard(querier);
|
|
53027
|
-
})
|
|
53028
|
-
return
|
|
53029
|
-
if (imageAttached) {
|
|
53030
|
-
return null;
|
|
53031
|
-
}
|
|
53032
|
-
const preferredText = await readPreferredText();
|
|
54669
|
+
});
|
|
54670
|
+
return readPreferredText.then(async (preferredText) => {
|
|
53033
54671
|
if (isUsableClipboardText(preferredText)) {
|
|
53034
54672
|
return handleResolvedPaste({ bracketed: false, cursor, text: preferredText, value });
|
|
53035
54673
|
}
|
|
@@ -53042,10 +54680,10 @@ function useComposerState({
|
|
|
53042
54680
|
[handleResolvedPaste, onClipboardPaste, querier]
|
|
53043
54681
|
);
|
|
53044
54682
|
const openEditor = useCallback7(async () => {
|
|
53045
|
-
const dir2 = mkdtempSync(
|
|
53046
|
-
const file2 =
|
|
54683
|
+
const dir2 = mkdtempSync(join46(tmpdir12(), "hermes-"));
|
|
54684
|
+
const file2 = join46(dir2, "prompt.md");
|
|
53047
54685
|
const [cmd, ...args] = resolveEditor();
|
|
53048
|
-
|
|
54686
|
+
writeFileSync21(file2, [...inputBuf, input].join("\n"));
|
|
53049
54687
|
let exitCode = null;
|
|
53050
54688
|
await withInkSuspended(async () => {
|
|
53051
54689
|
exitCode = spawnSync(cmd, [...args, file2], { stdio: "inherit" }).status;
|
|
@@ -53054,7 +54692,7 @@ function useComposerState({
|
|
|
53054
54692
|
if (exitCode !== 0) {
|
|
53055
54693
|
return;
|
|
53056
54694
|
}
|
|
53057
|
-
const text =
|
|
54695
|
+
const text = readFileSync31(file2, "utf8").trimEnd();
|
|
53058
54696
|
if (!text) {
|
|
53059
54697
|
return;
|
|
53060
54698
|
}
|
|
@@ -53067,7 +54705,6 @@ function useComposerState({
|
|
|
53067
54705
|
}, [input, inputBuf, submitRef]);
|
|
53068
54706
|
const actions = useMemo6(
|
|
53069
54707
|
() => ({
|
|
53070
|
-
clearAttachedImage,
|
|
53071
54708
|
clearIn,
|
|
53072
54709
|
dequeue,
|
|
53073
54710
|
enqueue,
|
|
@@ -53078,7 +54715,6 @@ function useComposerState({
|
|
|
53078
54715
|
replaceQueue: replaceQ,
|
|
53079
54716
|
setCompIdx,
|
|
53080
54717
|
setHistoryIdx,
|
|
53081
|
-
setAttachedImage,
|
|
53082
54718
|
setInput,
|
|
53083
54719
|
setInputBuf,
|
|
53084
54720
|
setPasteSnips,
|
|
@@ -53086,7 +54722,6 @@ function useComposerState({
|
|
|
53086
54722
|
syncQueue
|
|
53087
54723
|
}),
|
|
53088
54724
|
[
|
|
53089
|
-
clearAttachedImage,
|
|
53090
54725
|
clearIn,
|
|
53091
54726
|
dequeue,
|
|
53092
54727
|
enqueue,
|
|
@@ -53113,7 +54748,6 @@ function useComposerState({
|
|
|
53113
54748
|
);
|
|
53114
54749
|
const state = useMemo6(
|
|
53115
54750
|
() => ({
|
|
53116
|
-
attachedImage,
|
|
53117
54751
|
compIdx,
|
|
53118
54752
|
compReplace,
|
|
53119
54753
|
completions,
|
|
@@ -53124,7 +54758,7 @@ function useComposerState({
|
|
|
53124
54758
|
queueEditIdx,
|
|
53125
54759
|
queuedDisplay
|
|
53126
54760
|
}),
|
|
53127
|
-
[
|
|
54761
|
+
[compIdx, compReplace, completions, historyIdx, input, inputBuf, pasteSnips, queueEditIdx, queuedDisplay]
|
|
53128
54762
|
);
|
|
53129
54763
|
return {
|
|
53130
54764
|
actions,
|
|
@@ -53476,11 +55110,11 @@ function applyVoiceRecordResponse(response, starting, voice, sys) {
|
|
|
53476
55110
|
}
|
|
53477
55111
|
}
|
|
53478
55112
|
function useInputHandlers(ctx) {
|
|
53479
|
-
const { actions, composer, gateway, terminal, voice, wheelStep } = ctx;
|
|
55113
|
+
const { actions, composer, gateway, terminal: terminal2, voice, wheelStep } = ctx;
|
|
53480
55114
|
const { actions: cActions, refs: cRefs, state: cState } = composer;
|
|
53481
55115
|
const overlay = useStore2($overlayState);
|
|
53482
55116
|
const isBlocked = useStore2($isBlocked);
|
|
53483
|
-
const pagerPageSize = Math.max(5, (
|
|
55117
|
+
const pagerPageSize = Math.max(5, (terminal2.stdout?.rows ?? 24) - 6);
|
|
53484
55118
|
const scrollIdleTimer = useRef10(null);
|
|
53485
55119
|
const wheelAccelRef = useRef10(initWheelAccelForHost());
|
|
53486
55120
|
const precisionWheelRef = useRef10(initPrecisionWheel());
|
|
@@ -53495,13 +55129,13 @@ function useInputHandlers(ctx) {
|
|
|
53495
55129
|
turnController.relaxStreaming();
|
|
53496
55130
|
}, TYPING_IDLE_MS);
|
|
53497
55131
|
}
|
|
53498
|
-
|
|
55132
|
+
terminal2.scrollWithSelection(delta);
|
|
53499
55133
|
}, "scrollTranscript");
|
|
53500
55134
|
const copySelection = /* @__PURE__ */ __name(() => {
|
|
53501
|
-
|
|
55135
|
+
terminal2.selection.copySelection();
|
|
53502
55136
|
}, "copySelection");
|
|
53503
55137
|
const clearSelection2 = /* @__PURE__ */ __name(() => {
|
|
53504
|
-
|
|
55138
|
+
terminal2.selection.clearSelection();
|
|
53505
55139
|
}, "clearSelection");
|
|
53506
55140
|
const cancelOverlayFromCtrlC = /* @__PURE__ */ __name(() => {
|
|
53507
55141
|
if (overlay.clarify) {
|
|
@@ -53700,7 +55334,7 @@ function useInputHandlers(ctx) {
|
|
|
53700
55334
|
return scrollTranscript(1);
|
|
53701
55335
|
}
|
|
53702
55336
|
if (key.pageUp || key.pageDown) {
|
|
53703
|
-
const viewport =
|
|
55337
|
+
const viewport = terminal2.scrollRef.current?.getViewportHeight() ?? Math.max(6, (terminal2.stdout?.rows ?? 24) - 8);
|
|
53704
55338
|
const step = Math.max(4, Math.floor(viewport / 2));
|
|
53705
55339
|
return scrollTranscript(key.pageUp ? -step : step);
|
|
53706
55340
|
}
|
|
@@ -53710,12 +55344,9 @@ function useInputHandlers(ctx) {
|
|
|
53710
55344
|
if (key.escape && cState.queueEditIdx !== null) {
|
|
53711
55345
|
return cActions.clearIn();
|
|
53712
55346
|
}
|
|
53713
|
-
if (key.escape &&
|
|
55347
|
+
if (key.escape && terminal2.hasSelection) {
|
|
53714
55348
|
return clearSelection2();
|
|
53715
55349
|
}
|
|
53716
|
-
if (key.escape && cState.attachedImage) {
|
|
53717
|
-
return cActions.clearAttachedImage();
|
|
53718
|
-
}
|
|
53719
55350
|
if (key.escape && live.focusedPane === "transcript") {
|
|
53720
55351
|
patchUiState({ focusedPane: "composer" });
|
|
53721
55352
|
return;
|
|
@@ -53739,7 +55370,7 @@ function useInputHandlers(ctx) {
|
|
|
53739
55370
|
}
|
|
53740
55371
|
}
|
|
53741
55372
|
if (isCopyShortcut(key, ch)) {
|
|
53742
|
-
if (
|
|
55373
|
+
if (terminal2.hasSelection) {
|
|
53743
55374
|
return copySelection();
|
|
53744
55375
|
}
|
|
53745
55376
|
const inputSel = getInputSelection();
|
|
@@ -53764,7 +55395,7 @@ function useInputHandlers(ctx) {
|
|
|
53764
55395
|
sys: actions.sys
|
|
53765
55396
|
});
|
|
53766
55397
|
}
|
|
53767
|
-
if (cState.input || cState.inputBuf.length
|
|
55398
|
+
if (cState.input || cState.inputBuf.length) {
|
|
53768
55399
|
return cActions.clearIn();
|
|
53769
55400
|
}
|
|
53770
55401
|
return actions.die();
|
|
@@ -53774,7 +55405,7 @@ function useInputHandlers(ctx) {
|
|
|
53774
55405
|
}
|
|
53775
55406
|
if (isAction(key, ch, "l")) {
|
|
53776
55407
|
clearSelection2();
|
|
53777
|
-
forceRedraw(
|
|
55408
|
+
forceRedraw(terminal2.stdout ?? process.stdout);
|
|
53778
55409
|
return;
|
|
53779
55410
|
}
|
|
53780
55411
|
if (isVoiceToggleKey(key, ch, voice.recordKey)) {
|
|
@@ -53924,7 +55555,7 @@ var init_useLongRunToolCharms = __esm({
|
|
|
53924
55555
|
});
|
|
53925
55556
|
|
|
53926
55557
|
// src/app/useSessionLifecycle.ts
|
|
53927
|
-
import { writeFileSync as
|
|
55558
|
+
import { writeFileSync as writeFileSync22 } from "node:fs";
|
|
53928
55559
|
import { useCallback as useCallback8 } from "react";
|
|
53929
55560
|
function useSessionLifecycle(opts) {
|
|
53930
55561
|
const {
|
|
@@ -53954,7 +55585,6 @@ function useSessionLifecycle(opts) {
|
|
|
53954
55585
|
setHistoryItems([]);
|
|
53955
55586
|
setLastUserMsg("");
|
|
53956
55587
|
setStickyPrompt("");
|
|
53957
|
-
composerActions.clearAttachedImage();
|
|
53958
55588
|
composerActions.setPasteSnips([]);
|
|
53959
55589
|
evictInkCaches("half");
|
|
53960
55590
|
}, [composerActions, setHistoryItems, setLastUserMsg, setStickyPrompt, setVoiceProcessing, setVoiceRecording]);
|
|
@@ -53967,7 +55597,6 @@ function useSessionLifecycle(opts) {
|
|
|
53967
55597
|
setHistoryItems(info ? [introMsg(info)] : []);
|
|
53968
55598
|
setStickyPrompt("");
|
|
53969
55599
|
setLastUserMsg("");
|
|
53970
|
-
composerActions.clearAttachedImage();
|
|
53971
55600
|
composerActions.setPasteSnips([]);
|
|
53972
55601
|
patchTurnState({ activity: [] });
|
|
53973
55602
|
patchUiState({ info, usage: usageFrom(info) });
|
|
@@ -54109,7 +55738,7 @@ var init_useSessionLifecycle = __esm({
|
|
|
54109
55738
|
return;
|
|
54110
55739
|
}
|
|
54111
55740
|
try {
|
|
54112
|
-
|
|
55741
|
+
writeFileSync22(file2, JSON.stringify({ session_id: sessionId }), { mode: 384 });
|
|
54113
55742
|
} catch {
|
|
54114
55743
|
}
|
|
54115
55744
|
}, "writeActiveSessionFile");
|
|
@@ -54198,7 +55827,6 @@ function useSubmission(opts) {
|
|
|
54198
55827
|
const expand = expandSnips(composerState.pasteSnips);
|
|
54199
55828
|
const startSubmit = /* @__PURE__ */ __name((displayText, submitText, showUserMessage2 = true) => {
|
|
54200
55829
|
const sid2 = getUiState().sid;
|
|
54201
|
-
const imageAttachment = composerState.attachedImage;
|
|
54202
55830
|
if (!sid2) {
|
|
54203
55831
|
return sys("session not ready yet");
|
|
54204
55832
|
}
|
|
@@ -54211,20 +55839,7 @@ function useSubmission(opts) {
|
|
|
54211
55839
|
patchUiState({ busy: true, status: "running\u2026" });
|
|
54212
55840
|
turnController.bufRef = "";
|
|
54213
55841
|
turnController.interrupted = false;
|
|
54214
|
-
|
|
54215
|
-
if (imageAttachment?.attachment_id) {
|
|
54216
|
-
submitParams.image_attachment_id = imageAttachment.attachment_id;
|
|
54217
|
-
}
|
|
54218
|
-
if (imageAttachment) {
|
|
54219
|
-
composerActions.setAttachedImage(null);
|
|
54220
|
-
}
|
|
54221
|
-
gw.request("prompt.submit", submitParams).catch((e) => {
|
|
54222
|
-
if (imageAttachment) {
|
|
54223
|
-
composerActions.setAttachedImage(imageAttachment);
|
|
54224
|
-
sys(`error: ${e.message}`);
|
|
54225
|
-
patchUiState({ busy: false, status: "ready" });
|
|
54226
|
-
return;
|
|
54227
|
-
}
|
|
55842
|
+
gw.request("prompt.submit", { session_id: sid2, text: submitText }).catch((e) => {
|
|
54228
55843
|
if (isSessionBusyError(e)) {
|
|
54229
55844
|
composerActions.enqueue(submitText);
|
|
54230
55845
|
patchUiState({ busy: true, status: "queued for next turn" });
|
|
@@ -54250,7 +55865,7 @@ function useSubmission(opts) {
|
|
|
54250
55865
|
startSubmit(r.text || text, expand(r.text || text), showUserMessage);
|
|
54251
55866
|
}).catch(() => startSubmit(text, expand(text), showUserMessage));
|
|
54252
55867
|
},
|
|
54253
|
-
[appendMessage, composerActions, composerState.
|
|
55868
|
+
[appendMessage, composerActions, composerState.pasteSnips, gw, maybeGoodVibes, setLastUserMsg, sys]
|
|
54254
55869
|
);
|
|
54255
55870
|
const shellExec = useCallback9(
|
|
54256
55871
|
(cmd) => {
|
|
@@ -54313,16 +55928,6 @@ function useSubmission(opts) {
|
|
|
54313
55928
|
}
|
|
54314
55929
|
sys(note);
|
|
54315
55930
|
}, "fallback");
|
|
54316
|
-
if (composerState.attachedImage) {
|
|
54317
|
-
if (live.sid) {
|
|
54318
|
-
turnController.interruptTurn({ appendMessage, gw, sid: live.sid, sys });
|
|
54319
|
-
}
|
|
54320
|
-
if (hasInterpolation(full)) {
|
|
54321
|
-
patchUiState({ busy: true });
|
|
54322
|
-
return interpolate(full, send);
|
|
54323
|
-
}
|
|
54324
|
-
return send(full);
|
|
54325
|
-
}
|
|
54326
55931
|
if (mode === "queue") {
|
|
54327
55932
|
return composerActions.enqueue(full);
|
|
54328
55933
|
}
|
|
@@ -54344,7 +55949,7 @@ function useSubmission(opts) {
|
|
|
54344
55949
|
}
|
|
54345
55950
|
send(full);
|
|
54346
55951
|
},
|
|
54347
|
-
[appendMessage, composerActions, composerRefs,
|
|
55952
|
+
[appendMessage, composerActions, composerRefs, gw, interpolate, send, sys]
|
|
54348
55953
|
);
|
|
54349
55954
|
const dispatchSubmission = useCallback9(
|
|
54350
55955
|
(full) => {
|
|
@@ -54369,8 +55974,8 @@ function useSubmission(opts) {
|
|
|
54369
55974
|
return;
|
|
54370
55975
|
}
|
|
54371
55976
|
const editIdx = composerRefs.queueEditRef.current;
|
|
55977
|
+
composerActions.clearIn();
|
|
54372
55978
|
if (editIdx !== null) {
|
|
54373
|
-
composerActions.clearIn();
|
|
54374
55979
|
composerActions.replaceQueue(editIdx, full);
|
|
54375
55980
|
const picked = composerRefs.queueRef.current.splice(editIdx, 1)[0];
|
|
54376
55981
|
composerActions.syncQueue();
|
|
@@ -54387,7 +55992,6 @@ function useSubmission(opts) {
|
|
|
54387
55992
|
}
|
|
54388
55993
|
return sendQueued(picked);
|
|
54389
55994
|
}
|
|
54390
|
-
composerActions.clearIn({ keepAttachedImage: !!composerState.attachedImage });
|
|
54391
55995
|
composerActions.pushHistory(full);
|
|
54392
55996
|
if (getUiState().busy) {
|
|
54393
55997
|
return handleBusyInput(full);
|
|
@@ -54398,7 +56002,7 @@ function useSubmission(opts) {
|
|
|
54398
56002
|
}
|
|
54399
56003
|
send(full);
|
|
54400
56004
|
},
|
|
54401
|
-
[appendMessage, composerActions, composerRefs,
|
|
56005
|
+
[appendMessage, composerActions, composerRefs, handleBusyInput, interpolate, send, sendQueued, shellExec, slashRef]
|
|
54402
56006
|
);
|
|
54403
56007
|
const submit = useCallback9(
|
|
54404
56008
|
(value) => {
|
|
@@ -54516,7 +56120,8 @@ function useMainApp(gw) {
|
|
|
54516
56120
|
const scrollRef = useRef13(null);
|
|
54517
56121
|
const onEventRef = useRef13(() => {
|
|
54518
56122
|
});
|
|
54519
|
-
const clipboardPasteRef = useRef13(() =>
|
|
56123
|
+
const clipboardPasteRef = useRef13(() => {
|
|
56124
|
+
});
|
|
54520
56125
|
const submitRef = useRef13(() => {
|
|
54521
56126
|
});
|
|
54522
56127
|
const terminalHintsShownRef = useRef13(/* @__PURE__ */ new Set());
|
|
@@ -54569,7 +56174,9 @@ function useMainApp(gw) {
|
|
|
54569
56174
|
const composer = useComposerState({
|
|
54570
56175
|
gw,
|
|
54571
56176
|
onClipboardPaste: /* @__PURE__ */ __name((quiet) => clipboardPasteRef.current(quiet), "onClipboardPaste"),
|
|
54572
|
-
onImageAttached: /* @__PURE__ */ __name(() =>
|
|
56177
|
+
onImageAttached: /* @__PURE__ */ __name((info) => {
|
|
56178
|
+
sys(attachedImageNotice(info));
|
|
56179
|
+
}, "onImageAttached"),
|
|
54573
56180
|
submitRef
|
|
54574
56181
|
});
|
|
54575
56182
|
const { actions: composerActions, refs: composerRefs, state: composerState } = composer;
|
|
@@ -54777,25 +56384,17 @@ function useMainApp(gw) {
|
|
|
54777
56384
|
const paste2 = useCallback10(
|
|
54778
56385
|
(quiet = false) => rpc("clipboard.paste", { session_id: getUiState().sid }).then((r) => {
|
|
54779
56386
|
if (!r) {
|
|
54780
|
-
return
|
|
56387
|
+
return;
|
|
54781
56388
|
}
|
|
54782
56389
|
if (r.attached) {
|
|
54783
|
-
|
|
54784
|
-
|
|
54785
|
-
height: r.height,
|
|
54786
|
-
name: r.name ?? `Image #${r.count ?? 1}`,
|
|
54787
|
-
token_estimate: r.token_estimate,
|
|
54788
|
-
width: r.width
|
|
54789
|
-
});
|
|
54790
|
-
patchUiState({ status: "image attached" });
|
|
54791
|
-
return true;
|
|
56390
|
+
const meta = imageTokenMeta(r);
|
|
56391
|
+
return sys(`\u{1F4CE} Image #${r.count} attached from clipboard${meta ? ` \xB7 ${meta}` : ""}`);
|
|
54792
56392
|
}
|
|
54793
56393
|
if (!quiet) {
|
|
54794
56394
|
sys(r.message || "No image found in clipboard");
|
|
54795
56395
|
}
|
|
54796
|
-
return false;
|
|
54797
56396
|
}),
|
|
54798
|
-
[
|
|
56397
|
+
[rpc, sys]
|
|
54799
56398
|
);
|
|
54800
56399
|
clipboardPasteRef.current = paste2;
|
|
54801
56400
|
const { dispatchSubmission, send, sendQueued, submit } = useSubmission({
|
|
@@ -54996,8 +56595,13 @@ function useMainApp(gw) {
|
|
|
54996
56595
|
);
|
|
54997
56596
|
const onModelSelect = useCallback10((value) => {
|
|
54998
56597
|
patchOverlayState({ modelPicker: false });
|
|
56598
|
+
if (value === "__openjaw_connect_complete__") {
|
|
56599
|
+
sys("provider connected \u2014 starting OpenJaw session\u2026");
|
|
56600
|
+
session.newSession();
|
|
56601
|
+
return;
|
|
56602
|
+
}
|
|
54999
56603
|
slashRef.current(`/model ${value}`);
|
|
55000
|
-
}, []);
|
|
56604
|
+
}, [session, sys]);
|
|
55001
56605
|
const hasReasoning = useTurnSelector((state) => Boolean(state.reasoning.trim()));
|
|
55002
56606
|
const anyPanelVisible = SECTION_NAMES.some(
|
|
55003
56607
|
(s) => sectionMode(s, ui.detailsMode, ui.sections, ui.detailsModeCommandOverride) !== "hidden"
|
|
@@ -55035,7 +56639,6 @@ function useMainApp(gw) {
|
|
|
55035
56639
|
cols,
|
|
55036
56640
|
compIdx: composerState.compIdx,
|
|
55037
56641
|
completions: composerState.completions,
|
|
55038
|
-
attachedImage: composerState.attachedImage,
|
|
55039
56642
|
empty,
|
|
55040
56643
|
handleTextPaste: composerActions.handleTextPaste,
|
|
55041
56644
|
input: composerState.input,
|
|
@@ -55092,6 +56695,7 @@ var init_useMainApp = __esm({
|
|
|
55092
56695
|
init_env();
|
|
55093
56696
|
init_limits();
|
|
55094
56697
|
init_details();
|
|
56698
|
+
init_messages();
|
|
55095
56699
|
init_paths();
|
|
55096
56700
|
init_useGitBranch();
|
|
55097
56701
|
init_useVirtualHistory();
|
|
@@ -55169,9 +56773,9 @@ __export(perfPane_exports, {
|
|
|
55169
56773
|
PerfPane: () => PerfPane,
|
|
55170
56774
|
logFrameEvent: () => logFrameEvent
|
|
55171
56775
|
});
|
|
55172
|
-
import { appendFileSync as appendFileSync5, mkdirSync as
|
|
55173
|
-
import { homedir as
|
|
55174
|
-
import { dirname as dirname7, join as
|
|
56776
|
+
import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync20 } from "node:fs";
|
|
56777
|
+
import { homedir as homedir31 } from "node:os";
|
|
56778
|
+
import { dirname as dirname7, join as join47 } from "node:path";
|
|
55175
56779
|
import { Profiler } from "react";
|
|
55176
56780
|
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
55177
56781
|
function PerfPane({ children, id }) {
|
|
@@ -55187,13 +56791,13 @@ var init_perfPane = __esm({
|
|
|
55187
56791
|
init_entry_exports();
|
|
55188
56792
|
ENABLED = /^(?:1|true|yes|on)$/i.test((process.env.OPENJAW_DEV_PERF ?? "").trim());
|
|
55189
56793
|
THRESHOLD_MS = Number(process.env.OPENJAW_DEV_PERF_MS ?? "2") || 0;
|
|
55190
|
-
LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() ||
|
|
56794
|
+
LOG_PATH2 = process.env.OPENJAW_DEV_PERF_LOG?.trim() || join47(homedir31(), ".openjaw-agent", "perf.log");
|
|
55191
56795
|
logReady = false;
|
|
55192
56796
|
writeRow = /* @__PURE__ */ __name((row) => {
|
|
55193
56797
|
if (!logReady) {
|
|
55194
56798
|
logReady = true;
|
|
55195
56799
|
try {
|
|
55196
|
-
|
|
56800
|
+
mkdirSync20(dirname7(LOG_PATH2), { recursive: true });
|
|
55197
56801
|
} catch {
|
|
55198
56802
|
}
|
|
55199
56803
|
}
|
|
@@ -55446,6 +57050,10 @@ function Detail({ id, node, t }) {
|
|
|
55446
57050
|
] }),
|
|
55447
57051
|
/* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", marginTop: 1, children: [
|
|
55448
57052
|
/* @__PURE__ */ jsx18(Field, { name: "depth", t, value: `${item.depth} \xB7 ${item.status}` }),
|
|
57053
|
+
item.workflowId ? /* @__PURE__ */ jsx18(Field, { name: "workflow", t, value: item.workflowId }) : null,
|
|
57054
|
+
item.workerRole ? /* @__PURE__ */ jsx18(Field, { name: "role", t, value: item.workerRole }) : null,
|
|
57055
|
+
item.verificationState ? /* @__PURE__ */ jsx18(Field, { name: "verification", t, value: item.verificationState }) : null,
|
|
57056
|
+
item.currentStep ? /* @__PURE__ */ jsx18(Field, { name: "current", t, value: item.currentStep }) : null,
|
|
55449
57057
|
item.model ? /* @__PURE__ */ jsx18(Field, { name: "model", t, value: item.model }) : null,
|
|
55450
57058
|
item.toolsets?.length ? /* @__PURE__ */ jsx18(Field, { name: "toolsets", t, value: item.toolsets.join(", ") }) : null,
|
|
55451
57059
|
/* @__PURE__ */ jsx18(Field, { name: "tools", t, value: `${item.toolCount ?? 0} (subtree ${agg.totalTools})` }),
|
|
@@ -55520,7 +57128,8 @@ function Detail({ id, node, t }) {
|
|
|
55520
57128
|
" ",
|
|
55521
57129
|
line
|
|
55522
57130
|
] }, i)) }) : null,
|
|
55523
|
-
item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Summary", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.summary }) }) : null
|
|
57131
|
+
item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Summary", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.summary }) }) : null,
|
|
57132
|
+
item.details && item.details !== item.summary ? /* @__PURE__ */ jsx18(OverlaySection, { defaultOpen: true, t, title: "Details", children: /* @__PURE__ */ jsx18(Text9, { color: t.color.text, wrap: "wrap", children: item.details }) }) : null
|
|
55524
57133
|
] });
|
|
55525
57134
|
}
|
|
55526
57135
|
function ListRow({
|
|
@@ -55620,12 +57229,23 @@ function DiffView({
|
|
|
55620
57229
|
] })
|
|
55621
57230
|
] });
|
|
55622
57231
|
}
|
|
55623
|
-
function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
|
|
55624
|
-
const
|
|
57232
|
+
function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t, workflowId = null }) {
|
|
57233
|
+
const allLiveSubagents = useTurnSelector((state) => state.subagents);
|
|
55625
57234
|
const delegation = useStore4($delegationState);
|
|
55626
57235
|
const history = useStore4($spawnHistory);
|
|
55627
57236
|
const diffPair = useStore4($spawnDiff);
|
|
57237
|
+
const workflowSnapshots = useStore4($workflowSnapshots);
|
|
55628
57238
|
const { stdout } = useStdout();
|
|
57239
|
+
const liveSubagents = workflowId ? allLiveSubagents.filter((item) => item.workflowId === workflowId) : allLiveSubagents;
|
|
57240
|
+
const workflowSnapshot = workflowId ? workflowSnapshots[workflowId] ?? null : null;
|
|
57241
|
+
const workflowReplaySnapshot = workflowSnapshot && liveSubagents.length === 0 ? {
|
|
57242
|
+
finishedAt: workflowSnapshot.finishedAt ?? Date.now(),
|
|
57243
|
+
id: workflowSnapshot.id,
|
|
57244
|
+
label: workflowSnapshot.goal,
|
|
57245
|
+
sessionId: workflowSnapshot.id,
|
|
57246
|
+
startedAt: workflowSnapshot.startedAt,
|
|
57247
|
+
subagents: workflowSnapshot.workers
|
|
57248
|
+
} : null;
|
|
55629
57249
|
const [historyIndex, setHistoryIndex] = useState14(
|
|
55630
57250
|
() => Math.max(0, Math.min(history.length, Math.floor(initialHistoryIndex)))
|
|
55631
57251
|
);
|
|
@@ -55637,9 +57257,9 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
|
|
|
55637
57257
|
const [mode, setMode] = useState14("list");
|
|
55638
57258
|
const detailScrollRef = useRef14(null);
|
|
55639
57259
|
const prevLiveCountRef = useRef14(liveSubagents.length);
|
|
55640
|
-
const activeSnapshot = historyIndex > 0 ? history[historyIndex - 1] : null;
|
|
55641
|
-
const justFinishedSnapshot = historyIndex === 0 && liveSubagents.length === 0 ? history[0] ?? null : null;
|
|
55642
|
-
const effectiveSnapshot = activeSnapshot ?? justFinishedSnapshot;
|
|
57260
|
+
const activeSnapshot = !workflowId && historyIndex > 0 ? history[historyIndex - 1] : null;
|
|
57261
|
+
const justFinishedSnapshot = !workflowId && historyIndex === 0 && liveSubagents.length === 0 ? history[0] ?? null : null;
|
|
57262
|
+
const effectiveSnapshot = workflowReplaySnapshot ?? activeSnapshot ?? justFinishedSnapshot;
|
|
55643
57263
|
const replayMode = effectiveSnapshot != null;
|
|
55644
57264
|
const subagents = replayMode ? effectiveSnapshot.subagents : liveSubagents;
|
|
55645
57265
|
const tree = useMemo8(() => buildSubagentTree(subagents), [subagents]);
|
|
@@ -55808,7 +57428,7 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
|
|
|
55808
57428
|
const capsLabel = delegation.maxSpawnDepth ? `caps d${delegation.maxSpawnDepth}/${delegation.maxConcurrentChildren ?? "?"}` : "";
|
|
55809
57429
|
const title = replayMode && effectiveSnapshot ? `${historyIndex > 0 ? `Replay ${historyIndex}/${history.length}` : "Last turn"} \xB7 finished ${new Date(
|
|
55810
57430
|
effectiveSnapshot.finishedAt
|
|
55811
|
-
).toLocaleTimeString()}` : `Spawn tree${delegation.paused ? " \xB7 \u23F8 paused" : ""}`;
|
|
57431
|
+
).toLocaleTimeString()}` : workflowId ? `Workflow ${workflowId}${delegation.paused ? " \xB7 \u23F8 paused" : ""}` : `Spawn tree${delegation.paused ? " \xB7 \u23F8 paused" : ""}`;
|
|
55812
57432
|
const metaLine = [formatSummary(totals), spark, capsLabel, mix2 ? `\xB7 ${mix2}` : ""].filter(Boolean).join(" ");
|
|
55813
57433
|
const controlsHint = replayMode ? " \xB7 controls locked" : ` \xB7 x kill \xB7 X subtree \xB7 p ${delegation.paused ? "resume" : "pause"}`;
|
|
55814
57434
|
if (diffPair) {
|
|
@@ -55822,7 +57442,7 @@ function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }) {
|
|
|
55822
57442
|
metaLine
|
|
55823
57443
|
] }) : null
|
|
55824
57444
|
] }) }),
|
|
55825
|
-
rows.length === 0 ? /* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx18(Text9, { color: t.color.muted, children: "No subagents this turn. Trigger delegate_task to populate the tree." }) }) : mode === "list" ? /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
|
|
57445
|
+
rows.length === 0 ? /* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx18(Text9, { color: t.color.muted, children: workflowId ? "No workers for this workflow yet." : "No subagents this turn. Trigger delegate_task to populate the tree." }) }) : mode === "list" ? /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
|
|
55826
57446
|
/* @__PURE__ */ jsx18(GanttStrip, { cols, cursor, flatNodes: rows, maxRows: 6, now: now2, t }),
|
|
55827
57447
|
/* @__PURE__ */ jsx18(Box_default, { flexDirection: "column", flexGrow: 0, flexShrink: 0, overflow: "hidden", children: rows.slice(listWindowStart, listWindowStart + rowsH).map((node, i) => /* @__PURE__ */ jsx18(
|
|
55828
57448
|
ListRow,
|
|
@@ -55868,6 +57488,7 @@ var init_agentsOverlay = __esm({
|
|
|
55868
57488
|
init_overlayStore();
|
|
55869
57489
|
init_spawnHistoryStore();
|
|
55870
57490
|
init_turnStore();
|
|
57491
|
+
init_workflowStore();
|
|
55871
57492
|
init_rpc();
|
|
55872
57493
|
init_subagentTree();
|
|
55873
57494
|
init_text();
|
|
@@ -57796,7 +59417,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
|
|
|
57796
59417
|
refreshProviders();
|
|
57797
59418
|
}
|
|
57798
59419
|
if (mode === "connect") {
|
|
57799
|
-
|
|
59420
|
+
onSelect("__openjaw_connect_complete__");
|
|
57800
59421
|
return;
|
|
57801
59422
|
}
|
|
57802
59423
|
setStage("model");
|
|
@@ -57892,7 +59513,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
|
|
|
57892
59513
|
setKeyInput("");
|
|
57893
59514
|
setKeySaving(false);
|
|
57894
59515
|
if (mode === "connect") {
|
|
57895
|
-
|
|
59516
|
+
onSelect("__openjaw_connect_complete__");
|
|
57896
59517
|
return;
|
|
57897
59518
|
}
|
|
57898
59519
|
setStage("model");
|
|
@@ -57931,7 +59552,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
|
|
|
57931
59552
|
if (r?.disconnected) {
|
|
57932
59553
|
setProviders(
|
|
57933
59554
|
(prev) => prev.map(
|
|
57934
|
-
(p) => p.slug === provider.slug ? { ...p, authenticated: false, models: [], model_options: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : "run
|
|
59555
|
+
(p) => p.slug === provider.slug ? { ...p, authenticated: false, models: [], model_options: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : "run /connect to configure" } : p
|
|
57935
59556
|
)
|
|
57936
59557
|
);
|
|
57937
59558
|
}
|
|
@@ -57979,7 +59600,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
|
|
|
57979
59600
|
return;
|
|
57980
59601
|
}
|
|
57981
59602
|
if (mode === "connect") {
|
|
57982
|
-
|
|
59603
|
+
onSelect("__openjaw_connect_complete__");
|
|
57983
59604
|
return;
|
|
57984
59605
|
}
|
|
57985
59606
|
setStage("model");
|
|
@@ -58067,7 +59688,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
|
|
|
58067
59688
|
"Configure ",
|
|
58068
59689
|
provider.name
|
|
58069
59690
|
] }),
|
|
58070
|
-
/* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to
|
|
59691
|
+
/* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to OpenJaw credentials)" }),
|
|
58071
59692
|
/* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: " " }),
|
|
58072
59693
|
/* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
|
|
58073
59694
|
provider.key_env,
|
|
@@ -59263,14 +60884,14 @@ __export(fpsStore_exports, {
|
|
|
59263
60884
|
$fpsState: () => $fpsState,
|
|
59264
60885
|
trackFrame: () => trackFrame
|
|
59265
60886
|
});
|
|
59266
|
-
import { atom as
|
|
60887
|
+
import { atom as atom8 } from "nanostores";
|
|
59267
60888
|
var WINDOW_SIZE, $fpsState, timestamps, totalFrames, trackFrame;
|
|
59268
60889
|
var init_fpsStore = __esm({
|
|
59269
60890
|
"src/lib/fpsStore.ts"() {
|
|
59270
60891
|
"use strict";
|
|
59271
60892
|
init_env();
|
|
59272
60893
|
WINDOW_SIZE = 30;
|
|
59273
|
-
$fpsState =
|
|
60894
|
+
$fpsState = atom8({ fps: 0, lastDurationMs: 0, totalFrames: 0 });
|
|
59274
60895
|
timestamps = [];
|
|
59275
60896
|
totalFrames = 0;
|
|
59276
60897
|
trackFrame = SHOW_FPS ? (durationMs) => {
|
|
@@ -59544,7 +61165,7 @@ var init_emoji = __esm({
|
|
|
59544
61165
|
});
|
|
59545
61166
|
|
|
59546
61167
|
// src/lib/externalLink.ts
|
|
59547
|
-
import { isIP } from "node:net";
|
|
61168
|
+
import { isIP as isIP2 } from "node:net";
|
|
59548
61169
|
import { useEffect as useEffect21, useMemo as useMemo13, useState as useState25 } from "react";
|
|
59549
61170
|
function normalizeExternalUrl(value) {
|
|
59550
61171
|
const trimmed = value.trim();
|
|
@@ -59650,13 +61271,13 @@ function isPrivateIpv6(value) {
|
|
|
59650
61271
|
}
|
|
59651
61272
|
return false;
|
|
59652
61273
|
}
|
|
59653
|
-
function
|
|
61274
|
+
function normalizeHostname2(value) {
|
|
59654
61275
|
const withoutBrackets = value.replace(/^\[/, "").replace(/\]$/, "");
|
|
59655
61276
|
const withoutZoneId = withoutBrackets.split("%", 1)[0];
|
|
59656
61277
|
return withoutZoneId.replace(/\.$/, "").toLowerCase();
|
|
59657
61278
|
}
|
|
59658
61279
|
function isPrivateOrLocalHost(hostname) {
|
|
59659
|
-
const normalized =
|
|
61280
|
+
const normalized = normalizeHostname2(hostname);
|
|
59660
61281
|
if (!normalized) {
|
|
59661
61282
|
return true;
|
|
59662
61283
|
}
|
|
@@ -59666,7 +61287,7 @@ function isPrivateOrLocalHost(hostname) {
|
|
|
59666
61287
|
if (LOCAL_HOST_SUFFIXES.some((suffix) => normalized.endsWith(suffix))) {
|
|
59667
61288
|
return true;
|
|
59668
61289
|
}
|
|
59669
|
-
const ipVersion =
|
|
61290
|
+
const ipVersion = isIP2(normalized);
|
|
59670
61291
|
if (ipVersion === 4) {
|
|
59671
61292
|
return isPrivateIpv4(normalized);
|
|
59672
61293
|
}
|
|
@@ -59850,7 +61471,7 @@ var init_externalLink = __esm({
|
|
|
59850
61471
|
__name(parseIpv4Octets, "parseIpv4Octets");
|
|
59851
61472
|
__name(isPrivateIpv4, "isPrivateIpv4");
|
|
59852
61473
|
__name(isPrivateIpv6, "isPrivateIpv6");
|
|
59853
|
-
__name(
|
|
61474
|
+
__name(normalizeHostname2, "normalizeHostname");
|
|
59854
61475
|
__name(isPrivateOrLocalHost, "isPrivateOrLocalHost");
|
|
59855
61476
|
__name(isTitleFetchable, "isTitleFetchable");
|
|
59856
61477
|
__name(decodeHtmlEntities, "decodeHtmlEntities");
|
|
@@ -60434,7 +62055,7 @@ var init_mathUnicode = __esm({
|
|
|
60434
62055
|
|
|
60435
62056
|
// src/lib/syntax.ts
|
|
60436
62057
|
function highlightLine(line, lang, t) {
|
|
60437
|
-
const spec =
|
|
62058
|
+
const spec = resolve5(lang);
|
|
60438
62059
|
if (!spec) {
|
|
60439
62060
|
return [["", line]];
|
|
60440
62061
|
}
|
|
@@ -60466,7 +62087,7 @@ function highlightLine(line, lang, t) {
|
|
|
60466
62087
|
}
|
|
60467
62088
|
return tokens;
|
|
60468
62089
|
}
|
|
60469
|
-
var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS,
|
|
62090
|
+
var KW, TS, PY, SH, GO, RUST, SQL, LANGS, ALIAS, resolve5, isHighlightable, TOKEN_RE;
|
|
60470
62091
|
var init_syntax = __esm({
|
|
60471
62092
|
"src/lib/syntax.ts"() {
|
|
60472
62093
|
"use strict";
|
|
@@ -60520,8 +62141,8 @@ var init_syntax = __esm({
|
|
|
60520
62141
|
yml: "yaml",
|
|
60521
62142
|
zsh: "sh"
|
|
60522
62143
|
};
|
|
60523
|
-
|
|
60524
|
-
isHighlightable = /* @__PURE__ */ __name((lang) =>
|
|
62144
|
+
resolve5 = /* @__PURE__ */ __name((lang) => LANGS[ALIAS[lang] ?? lang] ?? null, "resolve");
|
|
62145
|
+
isHighlightable = /* @__PURE__ */ __name((lang) => resolve5(lang) !== null, "isHighlightable");
|
|
60525
62146
|
TOKEN_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|\b\d+(?:\.\d+)?\b|[A-Za-z_$][\w$]*/g;
|
|
60526
62147
|
__name(highlightLine, "highlightLine");
|
|
60527
62148
|
}
|
|
@@ -62396,7 +64017,6 @@ var init_appLayout = __esm({
|
|
|
62396
64017
|
init_env();
|
|
62397
64018
|
init_limits();
|
|
62398
64019
|
init_placeholders();
|
|
62399
|
-
init_messages();
|
|
62400
64020
|
init_inputMetrics();
|
|
62401
64021
|
init_perfPane();
|
|
62402
64022
|
init_agentsOverlay();
|
|
@@ -62516,7 +64136,6 @@ var init_appLayout = __esm({
|
|
|
62516
64136
|
const inputColumns = stableComposerColumns(composer.cols, promptWidth);
|
|
62517
64137
|
const inputHeight = inputVisualHeight(composer.input, inputColumns);
|
|
62518
64138
|
const inputMouseRef = useRef19(null);
|
|
62519
|
-
const attachedImageMeta = composer.attachedImage ? imageTokenMeta(composer.attachedImage) : "";
|
|
62520
64139
|
const captureInputDrag = /* @__PURE__ */ __name((e) => {
|
|
62521
64140
|
if (e.button !== 0) {
|
|
62522
64141
|
return;
|
|
@@ -62587,12 +64206,6 @@ var init_appLayout = __esm({
|
|
|
62587
64206
|
),
|
|
62588
64207
|
composer.input === "?" && !composer.inputBuf.length && /* @__PURE__ */ jsx41(HelpHint, { t: ui.theme }),
|
|
62589
64208
|
!isBlocked && /* @__PURE__ */ jsxs28(Fragment11, { children: [
|
|
62590
|
-
composer.attachedImage && /* @__PURE__ */ jsx41(Box_default, { width: Math.max(1, composer.cols - 2), children: /* @__PURE__ */ jsxs28(Text9, { color: ui.theme.color.systemNote, wrap: "truncate-end", children: [
|
|
62591
|
-
"\u{1F4CE} ",
|
|
62592
|
-
composer.attachedImage.name,
|
|
62593
|
-
attachedImageMeta ? ` \xB7 ${attachedImageMeta}` : "",
|
|
62594
|
-
/* @__PURE__ */ jsx41(Text9, { color: ui.theme.color.muted, children: " \xB7 Esc to remove" })
|
|
62595
|
-
] }) }),
|
|
62596
64209
|
composer.inputBuf.map((line, i) => /* @__PURE__ */ jsxs28(Box_default, { children: [
|
|
62597
64210
|
/* @__PURE__ */ jsx41(Box_default, { width: promptWidth, children: i === 0 ? /* @__PURE__ */ jsx41(PromptPrefix, { color: ui.theme.color.muted, promptText, width: promptWidth }) : /* @__PURE__ */ jsx41(Text9, { color: ui.theme.color.muted, children: promptBlank }) }),
|
|
62598
64211
|
/* @__PURE__ */ jsx41(Text9, { color: ui.theme.color.composeText, children: line || " " })
|
|
@@ -62653,8 +64266,9 @@ var init_appLayout = __esm({
|
|
|
62653
64266
|
{
|
|
62654
64267
|
gw,
|
|
62655
64268
|
initialHistoryIndex: overlay.agentsInitialHistoryIndex,
|
|
62656
|
-
onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0 }),
|
|
62657
|
-
t: ui.theme
|
|
64269
|
+
onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0, agentsWorkflowId: null }),
|
|
64270
|
+
t: ui.theme,
|
|
64271
|
+
workflowId: overlay.agentsWorkflowId
|
|
62658
64272
|
}
|
|
62659
64273
|
);
|
|
62660
64274
|
}, "AgentsOverlayPane"));
|