offhands 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/daemon.mjs +421 -86
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -4777,7 +4777,7 @@ var require_main = __commonJS({
|
|
|
4777
4777
|
|
|
4778
4778
|
// ../daemon/src/index.ts
|
|
4779
4779
|
import { resolve as resolve5 } from "node:path";
|
|
4780
|
-
import { existsSync as
|
|
4780
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
4781
4781
|
|
|
4782
4782
|
// ../daemon/src/runners/claude-code.ts
|
|
4783
4783
|
import { spawn } from "node:child_process";
|
|
@@ -4897,11 +4897,11 @@ function contextUsage(usage, costUsd) {
|
|
|
4897
4897
|
function summariseToolInput(name, input) {
|
|
4898
4898
|
if (typeof input !== "object" || input === null) return name;
|
|
4899
4899
|
const i2 = input;
|
|
4900
|
-
const
|
|
4900
|
+
const firstString3 = (...keys) => {
|
|
4901
4901
|
for (const k2 of keys) if (typeof i2[k2] === "string" && i2[k2] !== "") return i2[k2];
|
|
4902
4902
|
return void 0;
|
|
4903
4903
|
};
|
|
4904
|
-
const hint =
|
|
4904
|
+
const hint = firstString3("file_path", "path", "command", "pattern", "query", "url", "description") ?? "";
|
|
4905
4905
|
return hint ? `${name}: ${truncate(hint, 120)}` : name;
|
|
4906
4906
|
}
|
|
4907
4907
|
function truncate(s2, max) {
|
|
@@ -5297,11 +5297,344 @@ stderr: ${stderrTail}` : ""}`
|
|
|
5297
5297
|
}
|
|
5298
5298
|
};
|
|
5299
5299
|
|
|
5300
|
+
// ../daemon/src/runners/opencode-cli.ts
|
|
5301
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
5302
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
5303
|
+
import { homedir as homedir3 } from "node:os";
|
|
5304
|
+
import { delimiter as delimiter2, join as join3 } from "node:path";
|
|
5305
|
+
|
|
5306
|
+
// ../daemon/src/runners/opencode-map.ts
|
|
5307
|
+
function mapOpenCodeEvent(value) {
|
|
5308
|
+
if (typeof value !== "object" || value === null) return [];
|
|
5309
|
+
const v2 = value;
|
|
5310
|
+
const type = typeof v2.type === "string" ? v2.type : "";
|
|
5311
|
+
const data = v2.data ?? {};
|
|
5312
|
+
if (type === "text_delta" || type === "message.delta" && data.type === "text") {
|
|
5313
|
+
const chunk = typeof data.text === "string" ? data.text : "";
|
|
5314
|
+
return chunk ? [{ type: "text", chunk }] : [];
|
|
5315
|
+
}
|
|
5316
|
+
if (type === "thinking_delta" || type === "message.delta" && data.type === "thinking") {
|
|
5317
|
+
const chunk = typeof data.thinking === "string" ? data.thinking : "";
|
|
5318
|
+
return chunk ? [{ type: "thinking", chunk }] : [];
|
|
5319
|
+
}
|
|
5320
|
+
if (type === "tool_call_start" || type === "message.delta" && data.type === "tool_use") {
|
|
5321
|
+
const name = firstString2(data, "toolName", "name", "tool") ?? "tool";
|
|
5322
|
+
const summary = firstString2(data, "command", "path", "file", "summary", "description", "query") ?? "";
|
|
5323
|
+
return [{ type: "tool", name, summary: summary ? `${name}: ${truncate3(summary, 120)}` : name }];
|
|
5324
|
+
}
|
|
5325
|
+
if (type === "tool_call_end" || type === "tool_call_result") {
|
|
5326
|
+
return [];
|
|
5327
|
+
}
|
|
5328
|
+
if (type === "permission.request" || type === "permission_request") {
|
|
5329
|
+
const id = firstString2(data, "id", "permissionId") ?? crypto.randomUUID();
|
|
5330
|
+
const action = firstString2(data, "toolName", "action", "command") ?? "action";
|
|
5331
|
+
const detail = firstString2(data, "detail", "description", "message") ?? "";
|
|
5332
|
+
const risk = firstString2(data, "risk") === "high" ? "high" : "low";
|
|
5333
|
+
const preview = firstString2(data, "preview", "diff", "command") ?? void 0;
|
|
5334
|
+
const question = data.question ? mapQuestion(data.question) : void 0;
|
|
5335
|
+
return [{
|
|
5336
|
+
type: "approval",
|
|
5337
|
+
id,
|
|
5338
|
+
action,
|
|
5339
|
+
detail,
|
|
5340
|
+
risk,
|
|
5341
|
+
preview,
|
|
5342
|
+
question
|
|
5343
|
+
}];
|
|
5344
|
+
}
|
|
5345
|
+
if (type === "result" || type === "completed" || type === "done") {
|
|
5346
|
+
const isError = v2.is_error === true || v2.error === true || typeof data.exitCode === "number" && data.exitCode !== 0;
|
|
5347
|
+
const text = firstString2(v2, "result", "text", "summary", "message") ?? "";
|
|
5348
|
+
if (isError) {
|
|
5349
|
+
return [{ type: "error", message: text || `run failed (${String(v2.subtype ?? v2.type ?? "unknown")})` }];
|
|
5350
|
+
}
|
|
5351
|
+
const events = [{ type: "done", summary: text }];
|
|
5352
|
+
const usage = extractUsage(v2, data);
|
|
5353
|
+
if (usage) events.push(usage);
|
|
5354
|
+
return events;
|
|
5355
|
+
}
|
|
5356
|
+
if (type === "error" || type === "failed") {
|
|
5357
|
+
const message = firstString2(v2, "message", "error", "detail", "result") ?? "unknown error";
|
|
5358
|
+
return [{ type: "error", message }];
|
|
5359
|
+
}
|
|
5360
|
+
if (type === "rate_limit" || type === "usage") {
|
|
5361
|
+
const usage = extractUsage(v2, data);
|
|
5362
|
+
return usage ? [usage] : [];
|
|
5363
|
+
}
|
|
5364
|
+
if (type === "session.created" || type === "session.updated") {
|
|
5365
|
+
return [];
|
|
5366
|
+
}
|
|
5367
|
+
return [];
|
|
5368
|
+
}
|
|
5369
|
+
function extractOpenCodeSessionId(value) {
|
|
5370
|
+
if (typeof value !== "object" || value === null) return null;
|
|
5371
|
+
const v2 = value;
|
|
5372
|
+
const data = v2.data ?? {};
|
|
5373
|
+
return firstString2(v2, "sessionId", "session_id", "id") ?? firstString2(data, "sessionId", "session_id", "id") ?? null;
|
|
5374
|
+
}
|
|
5375
|
+
function mapQuestion(q2) {
|
|
5376
|
+
if (typeof q2 !== "object" || q2 === null) return void 0;
|
|
5377
|
+
const qq = q2;
|
|
5378
|
+
const text = typeof qq.text === "string" ? qq.text : "";
|
|
5379
|
+
const options = Array.isArray(qq.options) ? qq.options.filter((o2) => typeof o2 === "object" && o2 !== null).map((o2) => ({
|
|
5380
|
+
label: typeof o2.label === "string" ? o2.label : "",
|
|
5381
|
+
description: typeof o2.description === "string" ? o2.description : void 0
|
|
5382
|
+
})) : [];
|
|
5383
|
+
const multiSelect = typeof qq.multiSelect === "boolean" ? qq.multiSelect : void 0;
|
|
5384
|
+
if (!text && options.length === 0) return void 0;
|
|
5385
|
+
return { text, options, multiSelect };
|
|
5386
|
+
}
|
|
5387
|
+
function extractUsage(v2, data) {
|
|
5388
|
+
const usage = v2.usage ?? data.usage;
|
|
5389
|
+
const costUsd = typeof v2.costUsd === "number" ? v2.costUsd : typeof data.costUsd === "number" ? data.costUsd : typeof v2.total_cost_usd === "number" ? v2.total_cost_usd : void 0;
|
|
5390
|
+
if (!usage && costUsd === void 0) return null;
|
|
5391
|
+
const n2 = (k2) => {
|
|
5392
|
+
const val = usage?.[k2] ?? data[k2];
|
|
5393
|
+
return typeof val === "number" ? val : 0;
|
|
5394
|
+
};
|
|
5395
|
+
const contextTokens = n2("inputTokens") + n2("input_tokens") + n2("cacheReadInputTokens") + n2("cache_read_input_tokens") + n2("cacheCreationInputTokens") + n2("cache_creation_input_tokens") + n2("outputTokens") + n2("output_tokens") + n2("reasoningTokens") + n2("reasoning_tokens");
|
|
5396
|
+
if (contextTokens <= 0 && costUsd === void 0) return null;
|
|
5397
|
+
return {
|
|
5398
|
+
type: "usage",
|
|
5399
|
+
contextTokens,
|
|
5400
|
+
contextWindow: 2e5,
|
|
5401
|
+
// OpenCode doesn't expose window; use reasonable default
|
|
5402
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
function firstString2(obj, ...keys) {
|
|
5406
|
+
for (const k2 of keys) {
|
|
5407
|
+
const val = obj[k2];
|
|
5408
|
+
if (typeof val === "string" && val !== "") return val;
|
|
5409
|
+
}
|
|
5410
|
+
return void 0;
|
|
5411
|
+
}
|
|
5412
|
+
function truncate3(s2, max) {
|
|
5413
|
+
return s2.length <= max ? s2 : s2.slice(0, max - 1) + "\u2026";
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
// ../daemon/src/runners/opencode-cli.ts
|
|
5417
|
+
var OpenCodeRunner = class {
|
|
5418
|
+
id = "opencode";
|
|
5419
|
+
name = "OpenCode";
|
|
5420
|
+
supportsApprovals = true;
|
|
5421
|
+
models = [];
|
|
5422
|
+
// populated dynamically via `opencode models`
|
|
5423
|
+
/** [command, ...prefixArgs] resolved once. */
|
|
5424
|
+
resolved = null;
|
|
5425
|
+
modelsCache = [];
|
|
5426
|
+
resolveCommand() {
|
|
5427
|
+
if (this.resolved) return this.resolved;
|
|
5428
|
+
if (process.platform === "win32") {
|
|
5429
|
+
const pathDirs = (process.env.PATH ?? "").split(delimiter2);
|
|
5430
|
+
const npmGlobalPaths = [
|
|
5431
|
+
process.env.APPDATA ? join3(process.env.APPDATA, "npm") : null,
|
|
5432
|
+
process.env.LOCALAPPDATA ? join3(process.env.LOCALAPPDATA, "npm") : null,
|
|
5433
|
+
"C:\\nvm4w\\nodejs",
|
|
5434
|
+
"C:\\Program Files\\nodejs",
|
|
5435
|
+
"C:\\Program Files (x86)\\nodejs"
|
|
5436
|
+
].filter((p2) => p2 !== null && existsSync3(p2));
|
|
5437
|
+
const allDirs = [...pathDirs, ...npmGlobalPaths];
|
|
5438
|
+
for (const pathDir of allDirs) {
|
|
5439
|
+
if (!pathDir) continue;
|
|
5440
|
+
const cmdPath = join3(pathDir, "opencode.cmd");
|
|
5441
|
+
if (existsSync3(cmdPath)) {
|
|
5442
|
+
this.resolved = [cmdPath];
|
|
5443
|
+
return this.resolved;
|
|
5444
|
+
}
|
|
5445
|
+
const nodeModulesBin = join3(pathDir, "node_modules", ".bin", "opencode.cmd");
|
|
5446
|
+
if (existsSync3(nodeModulesBin)) {
|
|
5447
|
+
this.resolved = [nodeModulesBin];
|
|
5448
|
+
return this.resolved;
|
|
5449
|
+
}
|
|
5450
|
+
for (const bin of ["bun", "pnpm", "yarn"]) {
|
|
5451
|
+
const binPath = join3(pathDir, bin + ".cmd");
|
|
5452
|
+
if (existsSync3(binPath)) {
|
|
5453
|
+
this.resolved = [binPath, "exec", "opencode"];
|
|
5454
|
+
return this.resolved;
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
const native = join3(homedir3(), "AppData", "Local", "opencode", "opencode.exe");
|
|
5459
|
+
if (existsSync3(native)) {
|
|
5460
|
+
this.resolved = [native];
|
|
5461
|
+
return this.resolved;
|
|
5462
|
+
}
|
|
5463
|
+
return null;
|
|
5464
|
+
}
|
|
5465
|
+
this.resolved = ["opencode"];
|
|
5466
|
+
return this.resolved;
|
|
5467
|
+
}
|
|
5468
|
+
async detect() {
|
|
5469
|
+
const cmd = this.resolveCommand();
|
|
5470
|
+
if (!cmd) return false;
|
|
5471
|
+
await this.fetchModels();
|
|
5472
|
+
return new Promise((resolve6) => {
|
|
5473
|
+
const p2 = spawn4(cmd[0], [...cmd.slice(1), "--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
|
5474
|
+
p2.on("error", () => resolve6(false));
|
|
5475
|
+
p2.on("exit", (code) => resolve6(code === 0));
|
|
5476
|
+
});
|
|
5477
|
+
}
|
|
5478
|
+
async fetchModels() {
|
|
5479
|
+
if (this.modelsCache.length > 0) return this.modelsCache;
|
|
5480
|
+
const cmd = this.resolveCommand();
|
|
5481
|
+
if (!cmd) return [];
|
|
5482
|
+
return new Promise((resolve6) => {
|
|
5483
|
+
const p2 = spawn4(cmd[0], [...cmd.slice(1), "models"], {
|
|
5484
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
5485
|
+
shell: process.platform === "win32"
|
|
5486
|
+
});
|
|
5487
|
+
let output = "";
|
|
5488
|
+
p2.stdout.setEncoding("utf8");
|
|
5489
|
+
p2.stdout.on("data", (chunk) => {
|
|
5490
|
+
output += chunk;
|
|
5491
|
+
});
|
|
5492
|
+
p2.on("close", (code) => {
|
|
5493
|
+
if (code === 0) {
|
|
5494
|
+
try {
|
|
5495
|
+
const lines = output.trim().split("\n");
|
|
5496
|
+
const models = lines.map((l2) => l2.trim()).filter((l2) => l2 && !l2.startsWith("opencode/"));
|
|
5497
|
+
this.modelsCache = models;
|
|
5498
|
+
Object.defineProperty(this, "models", {
|
|
5499
|
+
value: models,
|
|
5500
|
+
writable: false,
|
|
5501
|
+
configurable: true
|
|
5502
|
+
});
|
|
5503
|
+
resolve6(models);
|
|
5504
|
+
} catch {
|
|
5505
|
+
resolve6([]);
|
|
5506
|
+
}
|
|
5507
|
+
} else {
|
|
5508
|
+
resolve6([]);
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
p2.on("error", () => resolve6([]));
|
|
5512
|
+
});
|
|
5513
|
+
}
|
|
5514
|
+
loggedIn() {
|
|
5515
|
+
return existsSync3(join3(homedir3(), ".local", "share", "opencode", "auth.json"));
|
|
5516
|
+
}
|
|
5517
|
+
start(run, callbacks) {
|
|
5518
|
+
const queue = new AsyncEventQueue();
|
|
5519
|
+
const parser = new NdjsonParser();
|
|
5520
|
+
let child;
|
|
5521
|
+
let stderrTail = "";
|
|
5522
|
+
let sawTerminal = false;
|
|
5523
|
+
let reportedConversation = false;
|
|
5524
|
+
const emit = (events) => {
|
|
5525
|
+
for (const e of events) {
|
|
5526
|
+
if (e.type === "done" || e.type === "error") sawTerminal = true;
|
|
5527
|
+
queue.push(e);
|
|
5528
|
+
}
|
|
5529
|
+
};
|
|
5530
|
+
const cmd = this.resolveCommand();
|
|
5531
|
+
if (!cmd) {
|
|
5532
|
+
queue.push({ type: "error", message: "opencode CLI not found" });
|
|
5533
|
+
queue.close();
|
|
5534
|
+
return { events: queue, respond: () => {
|
|
5535
|
+
}, cancel: () => {
|
|
5536
|
+
} };
|
|
5537
|
+
}
|
|
5538
|
+
const args2 = [
|
|
5539
|
+
...cmd.slice(1),
|
|
5540
|
+
"run",
|
|
5541
|
+
run.prompt,
|
|
5542
|
+
"--format",
|
|
5543
|
+
"json",
|
|
5544
|
+
"--no-color"
|
|
5545
|
+
];
|
|
5546
|
+
if (run.model) {
|
|
5547
|
+
args2.push("--model", run.model);
|
|
5548
|
+
}
|
|
5549
|
+
if (run.resumeConversationId) {
|
|
5550
|
+
args2.push("--session", run.resumeConversationId);
|
|
5551
|
+
}
|
|
5552
|
+
const mode = run.permissionMode ?? "guarded";
|
|
5553
|
+
if (mode === "bypass") {
|
|
5554
|
+
args2.push("--auto");
|
|
5555
|
+
} else if (mode === "plan") {
|
|
5556
|
+
args2.push("--agent", "plan");
|
|
5557
|
+
} else {
|
|
5558
|
+
}
|
|
5559
|
+
if (run.effort) {
|
|
5560
|
+
const variantMap = { low: "minimal", medium: "high", high: "max", max: "max" };
|
|
5561
|
+
const variant = variantMap[run.effort];
|
|
5562
|
+
if (variant) {
|
|
5563
|
+
args2.push("--variant", variant);
|
|
5564
|
+
args2.push("--thinking");
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
if (run.attachments?.length) {
|
|
5568
|
+
for (const file of run.attachments) {
|
|
5569
|
+
args2.push("--file", file);
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
args2.push("--dir", run.workspace);
|
|
5573
|
+
try {
|
|
5574
|
+
child = spawn4(cmd[0], args2, {
|
|
5575
|
+
cwd: run.workspace,
|
|
5576
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5577
|
+
shell: process.platform === "win32"
|
|
5578
|
+
});
|
|
5579
|
+
} catch (e) {
|
|
5580
|
+
queue.push({ type: "error", message: `failed to spawn opencode: ${String(e)}` });
|
|
5581
|
+
queue.close();
|
|
5582
|
+
return { events: queue, respond: () => {
|
|
5583
|
+
}, cancel: () => {
|
|
5584
|
+
} };
|
|
5585
|
+
}
|
|
5586
|
+
child.stdout.setEncoding("utf8");
|
|
5587
|
+
child.stdout.on("data", (chunk) => {
|
|
5588
|
+
for (const result of parser.push(chunk)) {
|
|
5589
|
+
if (result.ok) {
|
|
5590
|
+
if (!reportedConversation) {
|
|
5591
|
+
const id = extractOpenCodeSessionId(result.value);
|
|
5592
|
+
if (id) {
|
|
5593
|
+
reportedConversation = true;
|
|
5594
|
+
callbacks?.onConversationId?.(id);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
emit(mapOpenCodeEvent(result.value));
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
child.stderr.setEncoding("utf8");
|
|
5602
|
+
child.stderr.on("data", (chunk) => {
|
|
5603
|
+
stderrTail = (stderrTail + chunk).slice(-2e3);
|
|
5604
|
+
});
|
|
5605
|
+
child.on("error", (err) => {
|
|
5606
|
+
emit([{ type: "error", message: `opencode process error: ${err.message}` }]);
|
|
5607
|
+
queue.close();
|
|
5608
|
+
});
|
|
5609
|
+
child.on("close", (code) => {
|
|
5610
|
+
for (const result of parser.flush()) {
|
|
5611
|
+
if (result.ok) emit(mapOpenCodeEvent(result.value));
|
|
5612
|
+
}
|
|
5613
|
+
if (!sawTerminal) {
|
|
5614
|
+
emit([
|
|
5615
|
+
{
|
|
5616
|
+
type: "error",
|
|
5617
|
+
message: `opencode exited with code ${code ?? "unknown"} before a result${stderrTail ? `
|
|
5618
|
+
stderr: ${stderrTail}` : ""}`
|
|
5619
|
+
}
|
|
5620
|
+
]);
|
|
5621
|
+
}
|
|
5622
|
+
queue.close();
|
|
5623
|
+
});
|
|
5624
|
+
return {
|
|
5625
|
+
events: queue,
|
|
5626
|
+
respond: (approvalId, ok, answer) => {
|
|
5627
|
+
},
|
|
5628
|
+
cancel: () => child.kill()
|
|
5629
|
+
};
|
|
5630
|
+
}
|
|
5631
|
+
};
|
|
5632
|
+
|
|
5300
5633
|
// ../daemon/src/session-manager.ts
|
|
5301
5634
|
import { randomUUID } from "node:crypto";
|
|
5302
5635
|
import { hostname, platform, tmpdir } from "node:os";
|
|
5303
|
-
import { existsSync as
|
|
5304
|
-
import { dirname as dirname2, join as
|
|
5636
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5637
|
+
import { dirname as dirname2, join as join6, basename as basename3, parse as parse2, resolve as resolve3 } from "node:path";
|
|
5305
5638
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5306
5639
|
|
|
5307
5640
|
// ../daemon/src/receipts.ts
|
|
@@ -5366,25 +5699,25 @@ function shouldScreenshot(touchedFiles, devUrl2) {
|
|
|
5366
5699
|
}
|
|
5367
5700
|
|
|
5368
5701
|
// ../daemon/src/claude-history.ts
|
|
5369
|
-
import { existsSync as
|
|
5370
|
-
import { homedir as
|
|
5371
|
-
import { basename, join as
|
|
5372
|
-
var DEFAULT_PROJECTS_DIR =
|
|
5702
|
+
import { existsSync as existsSync4, readdirSync, readFileSync, statSync } from "node:fs";
|
|
5703
|
+
import { homedir as homedir4 } from "node:os";
|
|
5704
|
+
import { basename, join as join4, resolve } from "node:path";
|
|
5705
|
+
var DEFAULT_PROJECTS_DIR = join4(homedir4(), ".claude", "projects");
|
|
5373
5706
|
function listClaudeConversations(workspace, projectsDir = process.env.OFFHAND_CLAUDE_PROJECTS_DIR ?? DEFAULT_PROJECTS_DIR) {
|
|
5374
5707
|
const projectDir = findProjectDir(workspace, projectsDir);
|
|
5375
5708
|
if (!projectDir) return [];
|
|
5376
5709
|
return listJsonlFiles(projectDir).map((path) => summarizeConversation(path, workspace)).filter((c2) => c2 !== null).sort((a2, b2) => b2.lastActiveMs - a2.lastActiveMs).slice(0, 50);
|
|
5377
5710
|
}
|
|
5378
5711
|
function findProjectDir(workspace, projectsDir) {
|
|
5379
|
-
if (!
|
|
5712
|
+
if (!existsSync4(projectsDir)) return null;
|
|
5380
5713
|
const candidates = /* @__PURE__ */ new Set([encodeWorkspace(workspace), encodeWorkspace(resolve(workspace))]);
|
|
5381
5714
|
for (const candidate of candidates) {
|
|
5382
|
-
const exact =
|
|
5383
|
-
if (
|
|
5715
|
+
const exact = join4(projectsDir, candidate);
|
|
5716
|
+
if (existsSync4(exact)) return exact;
|
|
5384
5717
|
}
|
|
5385
5718
|
const lower = new Set([...candidates].map((c2) => c2.toLowerCase()));
|
|
5386
5719
|
for (const entry of safeReadDir(projectsDir)) {
|
|
5387
|
-
if (entry.isDirectory() && lower.has(entry.name.toLowerCase())) return
|
|
5720
|
+
if (entry.isDirectory() && lower.has(entry.name.toLowerCase())) return join4(projectsDir, entry.name);
|
|
5388
5721
|
}
|
|
5389
5722
|
return null;
|
|
5390
5723
|
}
|
|
@@ -5392,12 +5725,12 @@ function encodeWorkspace(workspace) {
|
|
|
5392
5725
|
return workspace.replace(/[^A-Za-z0-9]/g, "-");
|
|
5393
5726
|
}
|
|
5394
5727
|
function listJsonlFiles(projectDir) {
|
|
5395
|
-
const files = safeReadDir(projectDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) =>
|
|
5396
|
-
const sessionsDir =
|
|
5397
|
-
if (!
|
|
5728
|
+
const files = safeReadDir(projectDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => join4(projectDir, entry.name));
|
|
5729
|
+
const sessionsDir = join4(projectDir, "sessions");
|
|
5730
|
+
if (!existsSync4(sessionsDir)) return files;
|
|
5398
5731
|
return [
|
|
5399
5732
|
...files,
|
|
5400
|
-
...safeReadDir(sessionsDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) =>
|
|
5733
|
+
...safeReadDir(sessionsDir).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => join4(sessionsDir, entry.name))
|
|
5401
5734
|
];
|
|
5402
5735
|
}
|
|
5403
5736
|
function summarizeConversation(path, workspace) {
|
|
@@ -5416,7 +5749,7 @@ function summarizeConversation(path, workspace) {
|
|
|
5416
5749
|
return {
|
|
5417
5750
|
conversationId: basename(path, ".jsonl"),
|
|
5418
5751
|
workspace,
|
|
5419
|
-
firstPrompt:
|
|
5752
|
+
firstPrompt: truncate4(firstPrompt || "(empty prompt)", 120),
|
|
5420
5753
|
lastActiveMs: Math.floor(statSync(path).mtimeMs),
|
|
5421
5754
|
messageCount
|
|
5422
5755
|
};
|
|
@@ -5442,7 +5775,7 @@ function userMessageText(value) {
|
|
|
5442
5775
|
function collapseText(text) {
|
|
5443
5776
|
return text.replace(/\s+/g, " ").trim();
|
|
5444
5777
|
}
|
|
5445
|
-
function
|
|
5778
|
+
function truncate4(text, max) {
|
|
5446
5779
|
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
5447
5780
|
}
|
|
5448
5781
|
function safeReadDir(path) {
|
|
@@ -5459,7 +5792,7 @@ function isRecord(value) {
|
|
|
5459
5792
|
// ../daemon/src/drop.ts
|
|
5460
5793
|
import {
|
|
5461
5794
|
copyFileSync,
|
|
5462
|
-
existsSync as
|
|
5795
|
+
existsSync as existsSync5,
|
|
5463
5796
|
mkdirSync,
|
|
5464
5797
|
readdirSync as readdirSync2,
|
|
5465
5798
|
readFileSync as readFileSync2,
|
|
@@ -5468,18 +5801,18 @@ import {
|
|
|
5468
5801
|
watch,
|
|
5469
5802
|
writeFileSync
|
|
5470
5803
|
} from "node:fs";
|
|
5471
|
-
import { homedir as
|
|
5472
|
-
import { basename as basename2, join as
|
|
5473
|
-
import { spawn as
|
|
5804
|
+
import { homedir as homedir5 } from "node:os";
|
|
5805
|
+
import { basename as basename2, join as join5, parse, resolve as resolve2 } from "node:path";
|
|
5806
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
5474
5807
|
var DROP_LOG_SESSION_ID = "drop";
|
|
5475
5808
|
function offhandHome() {
|
|
5476
|
-
return process.env.OFFHAND_HOME ??
|
|
5809
|
+
return process.env.OFFHAND_HOME ?? join5(homedir5(), ".offhand");
|
|
5477
5810
|
}
|
|
5478
5811
|
function dropOutboxDir(home = offhandHome()) {
|
|
5479
|
-
return
|
|
5812
|
+
return join5(home, "drop-outbox");
|
|
5480
5813
|
}
|
|
5481
5814
|
function incomingDropDir() {
|
|
5482
|
-
return
|
|
5815
|
+
return join5(homedir5(), "Downloads", "offhand");
|
|
5483
5816
|
}
|
|
5484
5817
|
function safeDropName(input) {
|
|
5485
5818
|
const base = basename2(input).replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").replace(/[. ]+$/g, "").trim();
|
|
@@ -5503,13 +5836,13 @@ function queueDropFileForPhone(sourcePath, outbox = dropOutboxDir()) {
|
|
|
5503
5836
|
const st = statSync2(source);
|
|
5504
5837
|
if (!st.isFile()) throw new Error(`not a file: ${sourcePath}`);
|
|
5505
5838
|
mkdirSync(outbox, { recursive: true });
|
|
5506
|
-
const dest =
|
|
5839
|
+
const dest = join5(outbox, dedupeDropName(basename2(source), safeNames(outbox)));
|
|
5507
5840
|
copyFileSync(source, dest);
|
|
5508
5841
|
return dest;
|
|
5509
5842
|
}
|
|
5510
5843
|
function saveIncomingDrop(name, bytes, dir = incomingDropDir()) {
|
|
5511
5844
|
mkdirSync(dir, { recursive: true });
|
|
5512
|
-
const path =
|
|
5845
|
+
const path = join5(dir, dedupeDropName(name, safeNames(dir)));
|
|
5513
5846
|
writeFileSync(path, bytes);
|
|
5514
5847
|
return path;
|
|
5515
5848
|
}
|
|
@@ -5552,7 +5885,7 @@ function startDropOutboxWatcher(outbox, sendFile) {
|
|
|
5552
5885
|
console.error(`drop: sent but could not delete ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
5553
5886
|
}
|
|
5554
5887
|
} catch (e) {
|
|
5555
|
-
if (
|
|
5888
|
+
if (existsSync5(path)) {
|
|
5556
5889
|
console.error(`drop: failed to send ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
5557
5890
|
schedule(15e3);
|
|
5558
5891
|
}
|
|
@@ -5564,7 +5897,7 @@ function startDropOutboxWatcher(outbox, sendFile) {
|
|
|
5564
5897
|
timer = null;
|
|
5565
5898
|
if (closed) return;
|
|
5566
5899
|
for (const entry of safeEntries(outbox)) {
|
|
5567
|
-
if (entry.isFile()) void processFile(
|
|
5900
|
+
if (entry.isFile()) void processFile(join5(outbox, entry.name));
|
|
5568
5901
|
}
|
|
5569
5902
|
};
|
|
5570
5903
|
watcher = watch(outbox, () => schedule());
|
|
@@ -5592,7 +5925,7 @@ function showDropToast(savedPath, textToClipboard) {
|
|
|
5592
5925
|
`$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)`,
|
|
5593
5926
|
`[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(${psQuote(appId)}).Show($toast)`
|
|
5594
5927
|
].filter(Boolean);
|
|
5595
|
-
const child =
|
|
5928
|
+
const child = spawn5("powershell", ["-NoProfile", "-Command", lines.join("; ")], {
|
|
5596
5929
|
windowsHide: true,
|
|
5597
5930
|
stdio: ["ignore", "ignore", "pipe"]
|
|
5598
5931
|
});
|
|
@@ -5650,7 +5983,7 @@ function mimeFromName(path) {
|
|
|
5650
5983
|
// ../daemon/src/session-manager.ts
|
|
5651
5984
|
function resolveDaemonVersion() {
|
|
5652
5985
|
try {
|
|
5653
|
-
const pkgPath =
|
|
5986
|
+
const pkgPath = join6(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
5654
5987
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
5655
5988
|
return pkg.version ?? "0.0.0";
|
|
5656
5989
|
} catch {
|
|
@@ -5748,9 +6081,9 @@ var SessionManager = class {
|
|
|
5748
6081
|
prompt += "\n\nAttached files (read them from disk):";
|
|
5749
6082
|
for (const a2 of msg.attachments) {
|
|
5750
6083
|
const bytes = await this.attachmentFetcher(a2.blobId);
|
|
5751
|
-
const dir =
|
|
6084
|
+
const dir = join6(tmpdir(), "offhand-attachments");
|
|
5752
6085
|
mkdirSync2(dir, { recursive: true });
|
|
5753
|
-
const path =
|
|
6086
|
+
const path = join6(dir, `${a2.blobId.slice(0, 8)}-${basename3(a2.name)}`);
|
|
5754
6087
|
writeFileSync2(path, bytes);
|
|
5755
6088
|
prompt += `
|
|
5756
6089
|
- ${path} (${a2.mime})`;
|
|
@@ -5988,17 +6321,17 @@ function listFolders(path) {
|
|
|
5988
6321
|
if (!path) return { path: "", parent: null, dirs: driveRoots() };
|
|
5989
6322
|
const current = resolve3(path);
|
|
5990
6323
|
const dirs = safeReadDir2(current).filter((entry) => entry.isDirectory() && !skipDir(entry.name)).map((entry) => {
|
|
5991
|
-
const full =
|
|
5992
|
-
return { name: entry.name, path: full, isGit:
|
|
6324
|
+
const full = join6(current, entry.name);
|
|
6325
|
+
return { name: entry.name, path: full, isGit: existsSync6(join6(full, ".git")) };
|
|
5993
6326
|
}).sort((a2, b2) => Number(b2.isGit) - Number(a2.isGit) || a2.name.localeCompare(b2.name)).slice(0, 200);
|
|
5994
6327
|
return { path: current, parent: isRoot(current) ? null : dirname2(current), dirs };
|
|
5995
6328
|
}
|
|
5996
6329
|
function driveRoots() {
|
|
5997
|
-
if (process.platform !== "win32") return [{ name: "/", path: "/", isGit:
|
|
6330
|
+
if (process.platform !== "win32") return [{ name: "/", path: "/", isGit: existsSync6("/.git") }];
|
|
5998
6331
|
const roots = [];
|
|
5999
6332
|
for (let code = 67; code <= 90; code++) {
|
|
6000
6333
|
const name = `${String.fromCharCode(code)}:\\`;
|
|
6001
|
-
if (
|
|
6334
|
+
if (existsSync6(name)) roots.push({ name, path: name, isGit: existsSync6(join6(name, ".git")) });
|
|
6002
6335
|
}
|
|
6003
6336
|
return roots.sort((a2, b2) => a2.name.localeCompare(b2.name)).slice(0, 200);
|
|
6004
6337
|
}
|
|
@@ -6031,14 +6364,14 @@ function stripTrailing(path) {
|
|
|
6031
6364
|
// ../daemon/src/store.ts
|
|
6032
6365
|
import { DatabaseSync } from "node:sqlite";
|
|
6033
6366
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
6034
|
-
import { homedir as
|
|
6035
|
-
import { join as
|
|
6367
|
+
import { homedir as homedir6 } from "node:os";
|
|
6368
|
+
import { join as join7, basename as basename4 } from "node:path";
|
|
6036
6369
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
6037
6370
|
var Store = class {
|
|
6038
6371
|
db;
|
|
6039
|
-
constructor(dir = process.env.OFFHAND_HOME ??
|
|
6372
|
+
constructor(dir = process.env.OFFHAND_HOME ?? join7(homedir6(), ".offhand")) {
|
|
6040
6373
|
mkdirSync3(dir, { recursive: true });
|
|
6041
|
-
this.db = new DatabaseSync(
|
|
6374
|
+
this.db = new DatabaseSync(join7(dir, "offhand.db"));
|
|
6042
6375
|
this.db.exec(`
|
|
6043
6376
|
PRAGMA journal_mode = WAL;
|
|
6044
6377
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -10280,6 +10613,11 @@ var coerce = {
|
|
|
10280
10613
|
var NEVER = INVALID;
|
|
10281
10614
|
|
|
10282
10615
|
// ../shared/src/run-events.ts
|
|
10616
|
+
var ApprovalQuestionSchema = external_exports.object({
|
|
10617
|
+
text: external_exports.string(),
|
|
10618
|
+
options: external_exports.array(external_exports.object({ label: external_exports.string(), description: external_exports.string().optional() })),
|
|
10619
|
+
multiSelect: external_exports.boolean().optional()
|
|
10620
|
+
});
|
|
10283
10621
|
var RunEventSchema = external_exports.discriminatedUnion("type", [
|
|
10284
10622
|
external_exports.object({ type: external_exports.literal("text"), chunk: external_exports.string() }),
|
|
10285
10623
|
/** Extended-thinking content, streamed the same way as text (medium+ effort tiers). */
|
|
@@ -10294,11 +10632,7 @@ var RunEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
10294
10632
|
/** Optional content preview (edit diff / command) shown in the approval sheet. */
|
|
10295
10633
|
preview: external_exports.string().optional(),
|
|
10296
10634
|
/** AskUserQuestion payload: render the actual question with options. */
|
|
10297
|
-
question:
|
|
10298
|
-
text: external_exports.string(),
|
|
10299
|
-
options: external_exports.array(external_exports.object({ label: external_exports.string(), description: external_exports.string().optional() })),
|
|
10300
|
-
multiSelect: external_exports.boolean().optional()
|
|
10301
|
-
}).optional()
|
|
10635
|
+
question: ApprovalQuestionSchema.optional()
|
|
10302
10636
|
}),
|
|
10303
10637
|
/** Encrypted blob reference (M5): phone fetches + decrypts locally. */
|
|
10304
10638
|
external_exports.object({
|
|
@@ -10681,9 +11015,9 @@ if (typeof Module.getRandomValue === "undefined") {
|
|
|
10681
11015
|
Module.getRandomValue = randomValuesStandard;
|
|
10682
11016
|
} catch (e) {
|
|
10683
11017
|
try {
|
|
10684
|
-
|
|
11018
|
+
crypto2 = null;
|
|
10685
11019
|
randomValueNodeJS = function() {
|
|
10686
|
-
var buf =
|
|
11020
|
+
var buf = crypto2["randomBytes"](4);
|
|
10687
11021
|
return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
|
|
10688
11022
|
};
|
|
10689
11023
|
randomValueNodeJS();
|
|
@@ -10696,7 +11030,7 @@ if (typeof Module.getRandomValue === "undefined") {
|
|
|
10696
11030
|
var window_;
|
|
10697
11031
|
var crypto_;
|
|
10698
11032
|
var randomValuesStandard;
|
|
10699
|
-
var
|
|
11033
|
+
var crypto2;
|
|
10700
11034
|
var randomValueNodeJS;
|
|
10701
11035
|
var _Module = Module;
|
|
10702
11036
|
Module.ready = new Promise(function(resolve6, reject) {
|
|
@@ -37590,7 +37924,7 @@ Module.ready = new Promise(function(resolve6, reject) {
|
|
|
37590
37924
|
try {
|
|
37591
37925
|
var window_ = "object" === typeof window ? window : self;
|
|
37592
37926
|
var crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
|
|
37593
|
-
crypto_ = crypto_ === void 0 ?
|
|
37927
|
+
crypto_ = crypto_ === void 0 ? crypto2 : crypto_;
|
|
37594
37928
|
var randomValuesStandard = function() {
|
|
37595
37929
|
var buf = new Uint32Array(1);
|
|
37596
37930
|
crypto_.getRandomValues(buf);
|
|
@@ -37600,9 +37934,9 @@ Module.ready = new Promise(function(resolve6, reject) {
|
|
|
37600
37934
|
Module3.getRandomValue = randomValuesStandard;
|
|
37601
37935
|
} catch (e) {
|
|
37602
37936
|
try {
|
|
37603
|
-
var
|
|
37937
|
+
var crypto2 = null;
|
|
37604
37938
|
var randomValueNodeJS = function() {
|
|
37605
|
-
var buf =
|
|
37939
|
+
var buf = crypto2["randomBytes"](4);
|
|
37606
37940
|
return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
|
|
37607
37941
|
};
|
|
37608
37942
|
randomValueNodeJS();
|
|
@@ -38248,7 +38582,7 @@ Module.ready = new Promise(function(resolve6, reject) {
|
|
|
38248
38582
|
try {
|
|
38249
38583
|
var window_ = "object" === typeof window ? window : self;
|
|
38250
38584
|
var crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
|
|
38251
|
-
crypto_ = crypto_ === void 0 ?
|
|
38585
|
+
crypto_ = crypto_ === void 0 ? crypto2 : crypto_;
|
|
38252
38586
|
var randomValuesStandard = function() {
|
|
38253
38587
|
var buf = new Uint32Array(1);
|
|
38254
38588
|
crypto_.getRandomValues(buf);
|
|
@@ -38258,9 +38592,9 @@ Module.ready = new Promise(function(resolve6, reject) {
|
|
|
38258
38592
|
Module2.getRandomValue = randomValuesStandard;
|
|
38259
38593
|
} catch (e) {
|
|
38260
38594
|
try {
|
|
38261
|
-
var
|
|
38595
|
+
var crypto2 = null;
|
|
38262
38596
|
var randomValueNodeJS = function() {
|
|
38263
|
-
var buf =
|
|
38597
|
+
var buf = crypto2["randomBytes"](4);
|
|
38264
38598
|
return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
|
|
38265
38599
|
};
|
|
38266
38600
|
randomValueNodeJS();
|
|
@@ -41366,17 +41700,17 @@ var RelayClient = class {
|
|
|
41366
41700
|
};
|
|
41367
41701
|
|
|
41368
41702
|
// ../daemon/src/pairing.ts
|
|
41369
|
-
import { existsSync as
|
|
41370
|
-
import { homedir as
|
|
41371
|
-
import { join as
|
|
41703
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
|
|
41704
|
+
import { homedir as homedir7 } from "node:os";
|
|
41705
|
+
import { join as join8 } from "node:path";
|
|
41372
41706
|
import { randomInt } from "node:crypto";
|
|
41373
|
-
var PAIRING_DIR = process.env.OFFHAND_HOME ??
|
|
41374
|
-
var PAIRING_PATH =
|
|
41707
|
+
var PAIRING_DIR = process.env.OFFHAND_HOME ?? join8(homedir7(), ".offhand");
|
|
41708
|
+
var PAIRING_PATH = join8(PAIRING_DIR, "pairing.json");
|
|
41375
41709
|
var POLL_MS = 2e3;
|
|
41376
41710
|
var POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
41377
41711
|
async function ensurePairing(relayUrl2, forceNew = false, webUrl2 = "https://offhand-web.onrender.com") {
|
|
41378
41712
|
await ready;
|
|
41379
|
-
if (!forceNew &&
|
|
41713
|
+
if (!forceNew && existsSync7(PAIRING_PATH)) {
|
|
41380
41714
|
const f2 = JSON.parse(readFileSync4(PAIRING_PATH, "utf8"));
|
|
41381
41715
|
const kp2 = { publicKey: fromB64u(f2.daemonPublicKey), secretKey: fromB64u(f2.daemonSecretKey) };
|
|
41382
41716
|
const phonePk = fromB64u(f2.phonePublicKey);
|
|
@@ -41449,7 +41783,7 @@ async function ensurePairing(relayUrl2, forceNew = false, webUrl2 = "https://off
|
|
|
41449
41783
|
// ../daemon/src/approvals.ts
|
|
41450
41784
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
41451
41785
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
41452
|
-
import { join as
|
|
41786
|
+
import { join as join9 } from "node:path";
|
|
41453
41787
|
var ApprovalBroker = class {
|
|
41454
41788
|
constructor(timeoutMs) {
|
|
41455
41789
|
this.timeoutMs = timeoutMs;
|
|
@@ -41473,7 +41807,7 @@ var ApprovalBroker = class {
|
|
|
41473
41807
|
}
|
|
41474
41808
|
const risk = classifyRisk(toolName, input);
|
|
41475
41809
|
const filePath = input?.file_path;
|
|
41476
|
-
if (toolName === "Read" && typeof filePath === "string" && filePath.toLowerCase().startsWith(
|
|
41810
|
+
if (toolName === "Read" && typeof filePath === "string" && filePath.toLowerCase().startsWith(join9(tmpdir2(), "offhand-attachments").toLowerCase())) {
|
|
41477
41811
|
return Promise.resolve({ approve: true });
|
|
41478
41812
|
}
|
|
41479
41813
|
if (this.policyProvider() === "trusting" && risk === "low" && toolName !== "AskUserQuestion") {
|
|
@@ -41540,11 +41874,11 @@ function summariseInput(input) {
|
|
|
41540
41874
|
if (typeof input !== "object" || input === null) return String(input ?? "");
|
|
41541
41875
|
const i2 = input;
|
|
41542
41876
|
const q2 = firstQuestion(i2);
|
|
41543
|
-
if (q2) return
|
|
41877
|
+
if (q2) return truncate5(q2.text, 200);
|
|
41544
41878
|
for (const key of ["command", "file_path", "path", "url", "pattern", "description"]) {
|
|
41545
|
-
if (typeof i2[key] === "string" && i2[key] !== "") return
|
|
41879
|
+
if (typeof i2[key] === "string" && i2[key] !== "") return truncate5(`${key}: ${i2[key]}`, 200);
|
|
41546
41880
|
}
|
|
41547
|
-
return
|
|
41881
|
+
return truncate5(JSON.stringify(input), 200);
|
|
41548
41882
|
}
|
|
41549
41883
|
function firstQuestion(i2) {
|
|
41550
41884
|
if (!Array.isArray(i2.questions) || i2.questions.length === 0) return void 0;
|
|
@@ -41562,7 +41896,7 @@ function buildPreview(toolName, input) {
|
|
|
41562
41896
|
const q2 = firstQuestion(i2);
|
|
41563
41897
|
if (q2) {
|
|
41564
41898
|
const lines = [q2.text, ...q2.options.map((o2, n2) => `${n2 + 1}. ${o2.label}${o2.description ? ` \u2014 ${o2.description}` : ""}`)];
|
|
41565
|
-
return
|
|
41899
|
+
return truncate5(lines.join("\n"), 600);
|
|
41566
41900
|
}
|
|
41567
41901
|
if (/^(Edit|MultiEdit)/.test(toolName) && typeof i2.new_string === "string") {
|
|
41568
41902
|
const oldS = typeof i2.old_string === "string" ? i2.old_string : "";
|
|
@@ -41570,15 +41904,15 @@ function buildPreview(toolName, input) {
|
|
|
41570
41904
|
...oldS.split("\n").map((l2) => `- ${l2}`),
|
|
41571
41905
|
...i2.new_string.split("\n").map((l2) => `+ ${l2}`)
|
|
41572
41906
|
];
|
|
41573
|
-
return
|
|
41907
|
+
return truncate5(lines.join("\n"), 600);
|
|
41574
41908
|
}
|
|
41575
41909
|
if (/^Write/.test(toolName) && typeof i2.content === "string") {
|
|
41576
|
-
return
|
|
41910
|
+
return truncate5(i2.content.split("\n").map((l2) => `+ ${l2}`).join("\n"), 600);
|
|
41577
41911
|
}
|
|
41578
|
-
if (typeof i2.command === "string") return
|
|
41912
|
+
if (typeof i2.command === "string") return truncate5(`$ ${i2.command}`, 600);
|
|
41579
41913
|
return void 0;
|
|
41580
41914
|
}
|
|
41581
|
-
function
|
|
41915
|
+
function truncate5(s2, max) {
|
|
41582
41916
|
return s2.length <= max ? s2 : s2.slice(0, max - 1) + "\u2026";
|
|
41583
41917
|
}
|
|
41584
41918
|
|
|
@@ -41615,28 +41949,28 @@ async function downloadArtifact(relayUrl2, sessionId, blobId, keys) {
|
|
|
41615
41949
|
}
|
|
41616
41950
|
|
|
41617
41951
|
// ../daemon/src/autostart.ts
|
|
41618
|
-
import { homedir as
|
|
41619
|
-
import { join as
|
|
41620
|
-
import { existsSync as
|
|
41952
|
+
import { homedir as homedir8 } from "node:os";
|
|
41953
|
+
import { join as join10, resolve as resolve4, dirname as dirname3 } from "node:path";
|
|
41954
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, rmSync } from "node:fs";
|
|
41621
41955
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
41622
41956
|
import { spawnSync } from "node:child_process";
|
|
41623
41957
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
41624
41958
|
var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
|
|
41625
41959
|
var SHORTCUT_NAME = "OffhandDaemon.lnk";
|
|
41626
|
-
var OFFHAND_HOME = process.env.OFFHAND_HOME ??
|
|
41960
|
+
var OFFHAND_HOME = process.env.OFFHAND_HOME ?? join10(homedir8(), ".offhand");
|
|
41627
41961
|
function getStartupDir() {
|
|
41628
41962
|
if (process.platform !== "win32") {
|
|
41629
|
-
return
|
|
41963
|
+
return join10(homedir8(), ".config", "autostart");
|
|
41630
41964
|
}
|
|
41631
|
-
return
|
|
41965
|
+
return join10(process.env.APPDATA ?? "", "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
|
|
41632
41966
|
}
|
|
41633
41967
|
function getWrapperPath() {
|
|
41634
|
-
const wrapperDir =
|
|
41968
|
+
const wrapperDir = join10(OFFHAND_HOME, "autostart");
|
|
41635
41969
|
mkdirSync5(wrapperDir, { recursive: true });
|
|
41636
|
-
return
|
|
41970
|
+
return join10(wrapperDir, "start-daemon.bat");
|
|
41637
41971
|
}
|
|
41638
41972
|
function getShortcutPath() {
|
|
41639
|
-
return
|
|
41973
|
+
return join10(getStartupDir(), SHORTCUT_NAME);
|
|
41640
41974
|
}
|
|
41641
41975
|
function buildWrapperScript(config) {
|
|
41642
41976
|
const projectRoot = resolve4(__dirname2, "..", "..");
|
|
@@ -41669,7 +42003,7 @@ $Shortcut.Arguments = "/c ${targetPath}"
|
|
|
41669
42003
|
$Shortcut.WorkingDirectory = "${resolve4(__dirname2, "..", "..")}"
|
|
41670
42004
|
$Shortcut.Description = "Offhand Daemon - Auto-start on login"
|
|
41671
42005
|
$Shortcut.Save()`;
|
|
41672
|
-
const scriptPath =
|
|
42006
|
+
const scriptPath = join10(tmpdir3(), "offhand-create-shortcut.ps1");
|
|
41673
42007
|
writeFileSync4(scriptPath, psScript);
|
|
41674
42008
|
const result = spawnSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath], { encoding: "utf8", shell: true });
|
|
41675
42009
|
try {
|
|
@@ -41679,7 +42013,7 @@ $Shortcut.Save()`;
|
|
|
41679
42013
|
return { success: result.status === 0, output: result.stdout + result.stderr };
|
|
41680
42014
|
}
|
|
41681
42015
|
function checkShortcutExists(shortcutPath) {
|
|
41682
|
-
return
|
|
42016
|
+
return existsSync8(shortcutPath);
|
|
41683
42017
|
}
|
|
41684
42018
|
function installAutostart(config) {
|
|
41685
42019
|
const wrapperPath = getWrapperPath();
|
|
@@ -41699,7 +42033,7 @@ function getAutostartStatus() {
|
|
|
41699
42033
|
const shortcutPath = getShortcutPath();
|
|
41700
42034
|
const installed = checkShortcutExists(shortcutPath);
|
|
41701
42035
|
const wrapperPath = getWrapperPath();
|
|
41702
|
-
const wrapperExists =
|
|
42036
|
+
const wrapperExists = existsSync8(wrapperPath);
|
|
41703
42037
|
let details = `Shortcut: ${shortcutPath} (${installed ? "EXISTS" : "MISSING"})`;
|
|
41704
42038
|
if (wrapperExists) {
|
|
41705
42039
|
details += `
|
|
@@ -41708,7 +42042,7 @@ Wrapper: ${wrapperPath} (EXISTS)`;
|
|
|
41708
42042
|
return { installed, details };
|
|
41709
42043
|
}
|
|
41710
42044
|
function saveConfig(config) {
|
|
41711
|
-
const configPath =
|
|
42045
|
+
const configPath = join10(OFFHAND_HOME, "autostart.json");
|
|
41712
42046
|
writeFileSync4(configPath, JSON.stringify(config, null, 2));
|
|
41713
42047
|
}
|
|
41714
42048
|
function getConfigFromStore() {
|
|
@@ -41757,7 +42091,7 @@ var devUrl = argValue("--dev-url");
|
|
|
41757
42091
|
var store = new Store();
|
|
41758
42092
|
var wsArgs = argValues("--workspace").map((w2) => resolve5(w2));
|
|
41759
42093
|
for (const w2 of wsArgs) {
|
|
41760
|
-
if (!
|
|
42094
|
+
if (!existsSync9(w2)) {
|
|
41761
42095
|
console.error(`workspace does not exist: ${w2}`);
|
|
41762
42096
|
process.exit(1);
|
|
41763
42097
|
}
|
|
@@ -41769,6 +42103,7 @@ var approvalUrl = `http://127.0.0.1:${port}/approval`;
|
|
|
41769
42103
|
var runners = [
|
|
41770
42104
|
new ClaudeCodeRunner(broker, approvalUrl),
|
|
41771
42105
|
new CopilotCliRunner(),
|
|
42106
|
+
new OpenCodeRunner(),
|
|
41772
42107
|
new CodexCliRunner(),
|
|
41773
42108
|
new CursorAgentRunner(),
|
|
41774
42109
|
new GeminiCliRunner()
|