hostwares-cli 2.4.3 → 2.4.5
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/index.js +92 -171
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1877,12 +1877,6 @@ async function runTurn(opts) {
|
|
|
1877
1877
|
let assistantContent = null;
|
|
1878
1878
|
let toolResults = [];
|
|
1879
1879
|
let firstHop = true;
|
|
1880
|
-
const callCounts = /* @__PURE__ */ new Map();
|
|
1881
|
-
const writeCounts = /* @__PURE__ */ new Map();
|
|
1882
|
-
let stalledHops = 0;
|
|
1883
|
-
let totalGuardHits = 0;
|
|
1884
|
-
let emptyHops = 0;
|
|
1885
|
-
const MAX_EMPTY_RETRIES = 1;
|
|
1886
1880
|
const steering = buildSteeringContext(host.cwd ?? process.cwd(), opts.message);
|
|
1887
1881
|
const firstMessage = steering ? `${steering}
|
|
1888
1882
|
|
|
@@ -1897,7 +1891,6 @@ ${opts.message}` : opts.message;
|
|
|
1897
1891
|
throw new AbortedError();
|
|
1898
1892
|
emit({ type: "thinking" });
|
|
1899
1893
|
const pending = [];
|
|
1900
|
-
const textBefore = text.length;
|
|
1901
1894
|
const done = await streamChat(credentials, {
|
|
1902
1895
|
...firstHop ? { message: firstMessage, images: opts.images } : { toolResults, assistantContent },
|
|
1903
1896
|
conversationId,
|
|
@@ -1952,113 +1945,15 @@ ${opts.message}` : opts.message;
|
|
|
1952
1945
|
}
|
|
1953
1946
|
}
|
|
1954
1947
|
firstHop = false;
|
|
1955
|
-
if (!pending.length)
|
|
1956
|
-
const producedText = text.length > textBefore;
|
|
1957
|
-
if (!producedText && hop > 0 && emptyHops < MAX_EMPTY_RETRIES) {
|
|
1958
|
-
emptyHops++;
|
|
1959
|
-
toolResults = [{
|
|
1960
|
-
id: `empty-${hop}`,
|
|
1961
|
-
result: `[continue] Your last turn was empty \u2014 no message and no tool call. Do NOT stop here. Look at what has been done so far in this conversation and take the NEXT concrete step toward the goal: create the next file, run the next command, or if the task is genuinely complete, give a short final answer stating what you built and how to run it. Act now.`
|
|
1962
|
-
}];
|
|
1963
|
-
assistantContent = assistantContent ?? "";
|
|
1964
|
-
continue;
|
|
1965
|
-
}
|
|
1966
|
-
if (!producedText && emptyHops >= MAX_EMPTY_RETRIES) {
|
|
1967
|
-
const wroteSomething = writeCounts.size > 0;
|
|
1968
|
-
emit({ type: "error", message: wroteSomething ? `Stopping: the last steps returned nothing new. What I changed is saved \u2014 tell me the specific next change and I'll make it directly.` : `Stopping: I couldn't make progress on that. Tell me the specific step you want (e.g. "create the server file") and I'll do it directly.` });
|
|
1969
|
-
}
|
|
1948
|
+
if (!pending.length)
|
|
1970
1949
|
break;
|
|
1950
|
+
const ran = await executePending(pending, opts, emit, checkpoint);
|
|
1951
|
+
if (ran === null) {
|
|
1952
|
+
return finish({ conversationId, turnCredits, balance, text, aborted: true });
|
|
1971
1953
|
}
|
|
1972
|
-
|
|
1973
|
-
const toRun = [];
|
|
1974
|
-
const shortCircuited = [];
|
|
1975
|
-
for (const call of pending) {
|
|
1976
|
-
const fp = `${call.name}:${stableStringify(call.input)}`;
|
|
1977
|
-
const seen = (callCounts.get(fp) ?? 0) + 1;
|
|
1978
|
-
callCounts.set(fp, seen);
|
|
1979
|
-
const path = typeof call.input?.path === "string" ? call.input.path : void 0;
|
|
1980
|
-
if (seen > MAX_IDENTICAL_CALLS && isReadOnlyTool(call.name)) {
|
|
1981
|
-
shortCircuited.push({
|
|
1982
|
-
id: call.id,
|
|
1983
|
-
result: `[loop guard] You already ran ${call.name}(${compactArgs(call.input)}) and nothing has changed since \u2014 re-reading it will keep returning the same thing. This call was NOT executed. STOP re-inspecting and DECIDE from what you already know: if a file the task needs does not exist yet, WRITE it now (write_file) \u2014 an empty or missing directory means the file has not been created, not that you should look again. If every file the task needs already exists and has been run/verified, give your final answer now (what you built + how to run it) and end. Do NOT read or list again.`
|
|
1984
|
-
});
|
|
1985
|
-
emit({ type: "tool:end", id: call.id, tool: call.name, ok: false, durationMs: 0, summary: "loop guard: repeated read skipped" });
|
|
1986
|
-
} else if (WRITE_TOOLS.has(call.name) && path && (writeCounts.get(path) ?? 0) >= MAX_WRITES_PER_FILE) {
|
|
1987
|
-
shortCircuited.push({
|
|
1988
|
-
id: call.id,
|
|
1989
|
-
result: `[loop guard] You have already written ${path} ${writeCounts.get(path)} times this turn \u2014 its content is on disk. This write was NOT executed. Stop rewriting it: verify it (run it / typecheck it) or give your final answer. Do not write this file again unless you are making a specific, different change.`
|
|
1990
|
-
});
|
|
1991
|
-
emit({ type: "tool:end", id: call.id, tool: call.name, ok: false, durationMs: 0, summary: "loop guard: write thrash skipped" });
|
|
1992
|
-
} else {
|
|
1993
|
-
toRun.push(call);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
totalGuardHits += shortCircuited.length;
|
|
1997
|
-
if (toRun.length === 0 && shortCircuited.length > 0)
|
|
1998
|
-
stalledHops++;
|
|
1999
|
-
else
|
|
2000
|
-
stalledHops = 0;
|
|
2001
|
-
const wroteThisHop = toRun.some((c2) => WRITE_TOOLS.has(c2.name));
|
|
2002
|
-
if (wroteThisHop) {
|
|
2003
|
-
stalledHops = 0;
|
|
2004
|
-
}
|
|
2005
|
-
const filesWritten = writeCounts.size;
|
|
2006
|
-
const guardHitCeiling = 6 + filesWritten * 3;
|
|
2007
|
-
const stallCeiling = filesWritten > 0 ? 4 : 2;
|
|
2008
|
-
if (stalledHops >= stallCeiling || totalGuardHits >= guardHitCeiling) {
|
|
2009
|
-
const wroteSomething = filesWritten > 0;
|
|
2010
|
-
emit({ type: "error", message: wroteSomething ? `Stopping: I kept re-reading the same files without making new progress. What I built so far is saved. Tell me the specific next change (e.g. "write the UI file" / "start it") and I'll do it directly.` : `Stopping: I kept re-reading the same files without writing anything. I have enough context now \u2014 tell me the specific change you want and I'll make it directly.` });
|
|
2011
|
-
return finish({ conversationId, turnCredits, balance, text, aborted: false });
|
|
2012
|
-
}
|
|
2013
|
-
for (const c2 of toRun) {
|
|
2014
|
-
if (!MUTATION_TOOLS.has(c2.name))
|
|
2015
|
-
continue;
|
|
2016
|
-
const p = typeof c2.input?.path === "string" ? c2.input.path : void 0;
|
|
2017
|
-
if (!p)
|
|
2018
|
-
continue;
|
|
2019
|
-
if (WRITE_TOOLS.has(c2.name))
|
|
2020
|
-
writeCounts.set(p, (writeCounts.get(p) ?? 0) + 1);
|
|
2021
|
-
for (const key of [...callCounts.keys()]) {
|
|
2022
|
-
if (key.includes(JSON.stringify(p)))
|
|
2023
|
-
callCounts.delete(key);
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
let results = shortCircuited;
|
|
2027
|
-
if (toRun.length) {
|
|
2028
|
-
const ran = await executePending(toRun, opts, emit, checkpoint);
|
|
2029
|
-
if (ran === null) {
|
|
2030
|
-
return finish({ conversationId, turnCredits, balance, text, aborted: true });
|
|
2031
|
-
}
|
|
2032
|
-
for (const call of toRun) {
|
|
2033
|
-
if (!WRITE_TOOLS.has(call.name))
|
|
2034
|
-
continue;
|
|
2035
|
-
const res = ran.find((r) => r.id === call.id);
|
|
2036
|
-
if (!res || /^Error:/i.test(res.result))
|
|
2037
|
-
continue;
|
|
2038
|
-
const path = String(call.input?.path ?? "");
|
|
2039
|
-
if (!path)
|
|
2040
|
-
continue;
|
|
2041
|
-
let hookResults;
|
|
2042
|
-
try {
|
|
2043
|
-
hookResults = await runPostWriteHooks(host.cwd ?? process.cwd(), path);
|
|
2044
|
-
} catch {
|
|
2045
|
-
hookResults = [];
|
|
2046
|
-
}
|
|
2047
|
-
for (const h of hookResults) {
|
|
2048
|
-
emit({ type: "tool:end", id: `${call.id}:${h.name}`, tool: h.name, ok: h.ok, durationMs: 0, summary: h.ok ? `${h.name} passed` : `${h.name} failed` });
|
|
2049
|
-
res.result += h.ok ? `
|
|
2050
|
-
|
|
2051
|
-
[${h.name}] passed on ${path}.` : `
|
|
2052
|
-
|
|
2053
|
-
[${h.name}] FAILED after this write \u2014 fix before continuing:
|
|
2054
|
-
${h.output}`;
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
results = [...shortCircuited, ...ran];
|
|
2058
|
-
}
|
|
2059
|
-
toolResults = results;
|
|
1954
|
+
toolResults = ran;
|
|
2060
1955
|
if (hop === MAX_HOPS - 1) {
|
|
2061
|
-
emit({ type: "error", message: `Paused after ${MAX_HOPS} steps to check in
|
|
1956
|
+
emit({ type: "error", message: `Paused after ${MAX_HOPS} steps to check in \u2014 this is a long task, not necessarily a finished one. Say "continue" and I'll pick up exactly where I left off.` });
|
|
2062
1957
|
}
|
|
2063
1958
|
}
|
|
2064
1959
|
emit({
|
|
@@ -2097,21 +1992,6 @@ function finish(r) {
|
|
|
2097
1992
|
error: r.error
|
|
2098
1993
|
};
|
|
2099
1994
|
}
|
|
2100
|
-
function isReadOnlyTool(name) {
|
|
2101
|
-
return READ_ONLY_TOOLS.has(name);
|
|
2102
|
-
}
|
|
2103
|
-
function stableStringify(v) {
|
|
2104
|
-
if (v === null || typeof v !== "object")
|
|
2105
|
-
return JSON.stringify(v);
|
|
2106
|
-
if (Array.isArray(v))
|
|
2107
|
-
return `[${v.map(stableStringify).join(",")}]`;
|
|
2108
|
-
const o = v;
|
|
2109
|
-
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(",")}}`;
|
|
2110
|
-
}
|
|
2111
|
-
function compactArgs(input) {
|
|
2112
|
-
const s = Object.entries(input).map(([k, val]) => `${k}: ${JSON.stringify(val)}`).join(", ");
|
|
2113
|
-
return s.length > 120 ? `${s.slice(0, 117)}...` : s;
|
|
2114
|
-
}
|
|
2115
1995
|
async function executePending(calls, opts, emit, checkpoint) {
|
|
2116
1996
|
const results = [];
|
|
2117
1997
|
for (const call of calls) {
|
|
@@ -2151,14 +2031,14 @@ async function executePending(calls, opts, emit, checkpoint) {
|
|
|
2151
2031
|
let oldContent = null;
|
|
2152
2032
|
if (WRITE_TOOLS.has(call.name) && typeof call.input?.path === "string" && call.input.path) {
|
|
2153
2033
|
try {
|
|
2154
|
-
const { readFileSync:
|
|
2155
|
-
const { resolve: pathResolve2, join:
|
|
2156
|
-
const { homedir:
|
|
2034
|
+
const { readFileSync: readFileSync10, existsSync: existsSync9 } = await import("fs");
|
|
2035
|
+
const { resolve: pathResolve2, join: join10 } = await import("path");
|
|
2036
|
+
const { homedir: homedir6 } = await import("os");
|
|
2157
2037
|
const raw = String(call.input.path);
|
|
2158
|
-
const expanded = raw === "~" ?
|
|
2038
|
+
const expanded = raw === "~" ? homedir6() : raw.startsWith("~/") ? join10(homedir6(), raw.slice(2)) : raw;
|
|
2159
2039
|
const abs = pathResolve2(expanded);
|
|
2160
|
-
if (
|
|
2161
|
-
oldContent =
|
|
2040
|
+
if (existsSync9(abs))
|
|
2041
|
+
oldContent = readFileSync10(abs, "utf8");
|
|
2162
2042
|
else
|
|
2163
2043
|
oldContent = "";
|
|
2164
2044
|
} catch {
|
|
@@ -2199,7 +2079,25 @@ async function executePending(calls, opts, emit, checkpoint) {
|
|
|
2199
2079
|
}
|
|
2200
2080
|
const bytes = effect.kind === "wrote" || effect.kind === "modified" ? byteLength(call.input?.content) : void 0;
|
|
2201
2081
|
emit({ type: "tool:end", id: call.id, tool: call.name, ok: !failed2, durationMs: Date.now() - started, effect, bytes });
|
|
2202
|
-
|
|
2082
|
+
let resultText = output;
|
|
2083
|
+
if (!failed2 && WRITE_TOOLS.has(call.name) && typeof call.input?.path === "string" && call.input.path) {
|
|
2084
|
+
let hookResults;
|
|
2085
|
+
try {
|
|
2086
|
+
hookResults = await runPostWriteHooks(opts.host.cwd ?? process.cwd(), String(call.input.path));
|
|
2087
|
+
} catch {
|
|
2088
|
+
hookResults = [];
|
|
2089
|
+
}
|
|
2090
|
+
for (const h of hookResults) {
|
|
2091
|
+
emit({ type: "tool:end", id: `${call.id}:${h.name}`, tool: h.name, ok: h.ok, durationMs: 0, summary: h.ok ? `${h.name} passed` : `${h.name} failed` });
|
|
2092
|
+
resultText += h.ok ? `
|
|
2093
|
+
|
|
2094
|
+
[${h.name}] passed on ${call.input.path}.` : `
|
|
2095
|
+
|
|
2096
|
+
[${h.name}] FAILED after this write \u2014 fix before continuing:
|
|
2097
|
+
${h.output}`;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
results.push({ id: call.id, result: truncate(resultText) });
|
|
2203
2101
|
}
|
|
2204
2102
|
return results;
|
|
2205
2103
|
}
|
|
@@ -2247,7 +2145,7 @@ function targetOf(call) {
|
|
|
2247
2145
|
return void 0;
|
|
2248
2146
|
return s.length > 60 ? `${s.slice(0, 57)}\u2026` : s;
|
|
2249
2147
|
}
|
|
2250
|
-
var MAX_HOPS,
|
|
2148
|
+
var MAX_HOPS, MAX_RESULT_CHARS, MUTATION_TOOLS, WRITE_TOOLS;
|
|
2251
2149
|
var init_loop = __esm({
|
|
2252
2150
|
"node_modules/@hostwares/agent-client/dist/esm/agent/loop.js"() {
|
|
2253
2151
|
init_client();
|
|
@@ -2259,28 +2157,13 @@ var init_loop = __esm({
|
|
|
2259
2157
|
init_subagents();
|
|
2260
2158
|
init_safety();
|
|
2261
2159
|
init_events();
|
|
2262
|
-
MAX_HOPS =
|
|
2263
|
-
MAX_IDENTICAL_CALLS = 2;
|
|
2264
|
-
MAX_WRITES_PER_FILE = 2;
|
|
2160
|
+
MAX_HOPS = 80;
|
|
2265
2161
|
MAX_RESULT_CHARS = 3e4;
|
|
2266
|
-
READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
2267
|
-
"read_file",
|
|
2268
|
-
"list_directory",
|
|
2269
|
-
"search_files",
|
|
2270
|
-
"file_search",
|
|
2271
|
-
"get_system_info",
|
|
2272
|
-
"list_processes",
|
|
2273
|
-
"get_process_output",
|
|
2274
|
-
"check_port",
|
|
2275
|
-
"git_status",
|
|
2276
|
-
"git_log",
|
|
2277
|
-
"git_diff",
|
|
2278
|
-
"docker_ps"
|
|
2279
|
-
]);
|
|
2280
2162
|
MUTATION_TOOLS = /* @__PURE__ */ new Set([
|
|
2281
2163
|
"write_file",
|
|
2282
2164
|
"str_replace_file",
|
|
2283
2165
|
"append_file",
|
|
2166
|
+
"edit_file",
|
|
2284
2167
|
"delete_path",
|
|
2285
2168
|
"git_commit",
|
|
2286
2169
|
"git_add",
|
|
@@ -2290,7 +2173,7 @@ var init_loop = __esm({
|
|
|
2290
2173
|
"stop_process",
|
|
2291
2174
|
"kill_process"
|
|
2292
2175
|
]);
|
|
2293
|
-
WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "str_replace_file", "append_file"]);
|
|
2176
|
+
WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "str_replace_file", "append_file", "edit_file"]);
|
|
2294
2177
|
}
|
|
2295
2178
|
});
|
|
2296
2179
|
|
|
@@ -2838,9 +2721,9 @@ var init_device = __esm({
|
|
|
2838
2721
|
});
|
|
2839
2722
|
|
|
2840
2723
|
// src/index.ts
|
|
2841
|
-
import { readFileSync as
|
|
2724
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2842
2725
|
import { fileURLToPath } from "url";
|
|
2843
|
-
import { dirname as dirname4, join as
|
|
2726
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
2844
2727
|
|
|
2845
2728
|
// src/agent/run.ts
|
|
2846
2729
|
init_esm();
|
|
@@ -3000,6 +2883,9 @@ async function pickScope(options) {
|
|
|
3000
2883
|
init_theme();
|
|
3001
2884
|
init_render();
|
|
3002
2885
|
init_esm();
|
|
2886
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, renameSync as renameSync2, chmodSync as chmodSync2 } from "fs";
|
|
2887
|
+
import { join as join7 } from "path";
|
|
2888
|
+
import { homedir as homedir5 } from "os";
|
|
3003
2889
|
var READ_ONLY = /* @__PURE__ */ new Set([
|
|
3004
2890
|
"read_file",
|
|
3005
2891
|
"list_directory",
|
|
@@ -3040,6 +2926,40 @@ function permissionRules() {
|
|
|
3040
2926
|
}
|
|
3041
2927
|
return cachedRules;
|
|
3042
2928
|
}
|
|
2929
|
+
function reloadPermissionRules() {
|
|
2930
|
+
cachedRules = null;
|
|
2931
|
+
}
|
|
2932
|
+
function saveGlobalPermissionRule(rule) {
|
|
2933
|
+
const { HW_DIR: HW_DIR2 } = getHwDir();
|
|
2934
|
+
const file = join7(HW_DIR2, "permissions.json");
|
|
2935
|
+
let rules = [];
|
|
2936
|
+
try {
|
|
2937
|
+
if (existsSync7(file)) {
|
|
2938
|
+
const parsed = JSON.parse(readFileSync7(file, "utf8"));
|
|
2939
|
+
const arr = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.rules) ? parsed.rules : [];
|
|
2940
|
+
rules = arr.filter((r) => !!r && typeof r.tool === "string" && ["allow", "ask", "deny"].includes(r.effect));
|
|
2941
|
+
}
|
|
2942
|
+
} catch {
|
|
2943
|
+
rules = [];
|
|
2944
|
+
}
|
|
2945
|
+
const exists = rules.some((r) => r.tool === rule.tool && (r.match ?? "") === (rule.match ?? "") && r.effect === rule.effect);
|
|
2946
|
+
if (!exists) rules.push(rule);
|
|
2947
|
+
try {
|
|
2948
|
+
if (!existsSync7(HW_DIR2)) mkdirSync5(HW_DIR2, { recursive: true, mode: 448 });
|
|
2949
|
+
const tmp = join7(HW_DIR2, `permissions.json.tmp-${process.pid}`);
|
|
2950
|
+
writeFileSync5(tmp, JSON.stringify(rules, null, 2) + "\n", { mode: 384 });
|
|
2951
|
+
renameSync2(tmp, file);
|
|
2952
|
+
try {
|
|
2953
|
+
chmodSync2(file, 384);
|
|
2954
|
+
} catch {
|
|
2955
|
+
}
|
|
2956
|
+
} catch {
|
|
2957
|
+
}
|
|
2958
|
+
reloadPermissionRules();
|
|
2959
|
+
}
|
|
2960
|
+
function getHwDir() {
|
|
2961
|
+
return { HW_DIR: join7(homedir5(), ".hostwares") };
|
|
2962
|
+
}
|
|
3043
2963
|
function isProjectTrusted(cwd = process.cwd()) {
|
|
3044
2964
|
return (getConfig().trustedProjects ?? []).includes(cwd);
|
|
3045
2965
|
}
|
|
@@ -3084,15 +3004,11 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
|
|
|
3084
3004
|
const toolPath = String(tool.input.path ?? tool.input.localPath ?? "");
|
|
3085
3005
|
const rel = toolPath ? shortRel(toolPath) : "";
|
|
3086
3006
|
const dir = rel ? rel.split("/").slice(0, -1).join("/") || "." : "";
|
|
3087
|
-
const scopeOpts = [
|
|
3088
|
-
`Specific paths \u2192 ${rel || tool.name} ${c.dim("(this file only)")}`,
|
|
3089
|
-
`Complete directory \u2192 ${dir || "."} ${c.dim("(all files here)")}`,
|
|
3090
|
-
`Entire Tool \u2192 * ${c.dim(`(always allow '${tool.name}')`)}`
|
|
3091
|
-
];
|
|
3092
3007
|
const displayOpts = [
|
|
3093
3008
|
`Specific paths \u2192 ${rel || tool.name}`,
|
|
3094
3009
|
`Complete directory \u2192 ${dir || "."}`,
|
|
3095
|
-
`Entire Tool \u2192
|
|
3010
|
+
`Entire Tool \u2192 * (this session)`,
|
|
3011
|
+
`Always, everywhere \u2192 ${tool.name} (never ask again \u2014 saved)`
|
|
3096
3012
|
];
|
|
3097
3013
|
const choice = await pickScope(displayOpts);
|
|
3098
3014
|
if (choice === -1) return "abort";
|
|
@@ -3106,9 +3022,14 @@ ${c.yellow(glyph.caret)} ${describe(tool)}
|
|
|
3106
3022
|
addSessionRule({ tool: tool.name, match, effect: "allow" });
|
|
3107
3023
|
process.stdout.write(` ${c.dim(`Trusted '${tool.name}' for ${match} this session.`)}
|
|
3108
3024
|
`);
|
|
3109
|
-
} else {
|
|
3025
|
+
} else if (choice === 2) {
|
|
3110
3026
|
addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
|
|
3111
3027
|
process.stdout.write(` ${c.dim(`Trusted '${tool.name}' (*) this session.`)}
|
|
3028
|
+
`);
|
|
3029
|
+
} else {
|
|
3030
|
+
addSessionRule({ tool: tool.name, match: "*", effect: "allow" });
|
|
3031
|
+
saveGlobalPermissionRule({ tool: tool.name, match: "*", effect: "allow" });
|
|
3032
|
+
process.stdout.write(` ${c.green(glyph.tick)} ${c.dim(`Always allowing '${tool.name}' everywhere \u2014 saved to ~/.hostwares/permissions.json. You won't be asked again.`)}
|
|
3112
3033
|
`);
|
|
3113
3034
|
}
|
|
3114
3035
|
return "allow";
|
|
@@ -3406,13 +3327,13 @@ init_config();
|
|
|
3406
3327
|
// src/session/store.ts
|
|
3407
3328
|
init_config();
|
|
3408
3329
|
import { createHash } from "crypto";
|
|
3409
|
-
import { join as
|
|
3410
|
-
import { existsSync as
|
|
3411
|
-
var SESSION_DIR =
|
|
3330
|
+
import { join as join8 } from "path";
|
|
3331
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, unlinkSync as unlinkSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
3332
|
+
var SESSION_DIR = join8(HW_DIR, "sessions");
|
|
3412
3333
|
var SCHEMA_VERSION = 2;
|
|
3413
3334
|
function sessionPath(cwd) {
|
|
3414
3335
|
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 24);
|
|
3415
|
-
return
|
|
3336
|
+
return join8(SESSION_DIR, `${hash}.json`);
|
|
3416
3337
|
}
|
|
3417
3338
|
function newSession(cwd = process.cwd()) {
|
|
3418
3339
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3428,9 +3349,9 @@ function newSession(cwd = process.cwd()) {
|
|
|
3428
3349
|
}
|
|
3429
3350
|
function loadSession(cwd = process.cwd()) {
|
|
3430
3351
|
const file = sessionPath(cwd);
|
|
3431
|
-
if (!
|
|
3352
|
+
if (!existsSync8(file)) return null;
|
|
3432
3353
|
try {
|
|
3433
|
-
const data = JSON.parse(
|
|
3354
|
+
const data = JSON.parse(readFileSync8(file, "utf8"));
|
|
3434
3355
|
if (data.version !== SCHEMA_VERSION) return null;
|
|
3435
3356
|
if (data.cwd !== cwd) return null;
|
|
3436
3357
|
if (!Array.isArray(data.turns)) return null;
|
|
@@ -3452,7 +3373,7 @@ function isTurn(t) {
|
|
|
3452
3373
|
}
|
|
3453
3374
|
var MAX_LOCAL_TURNS = 200;
|
|
3454
3375
|
function saveSession(session) {
|
|
3455
|
-
|
|
3376
|
+
mkdirSync6(SESSION_DIR, { recursive: true, mode: 448 });
|
|
3456
3377
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3457
3378
|
if (session.turns.length > MAX_LOCAL_TURNS) {
|
|
3458
3379
|
session.turns = session.turns.slice(-MAX_LOCAL_TURNS);
|
|
@@ -3463,12 +3384,12 @@ function appendTurn(session, role, content) {
|
|
|
3463
3384
|
session.turns.push({ role, content, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3464
3385
|
}
|
|
3465
3386
|
function listSessions() {
|
|
3466
|
-
if (!
|
|
3387
|
+
if (!existsSync8(SESSION_DIR)) return [];
|
|
3467
3388
|
const out = [];
|
|
3468
3389
|
for (const name of readdirSync5(SESSION_DIR)) {
|
|
3469
3390
|
if (!name.endsWith(".json")) continue;
|
|
3470
3391
|
try {
|
|
3471
|
-
const data = JSON.parse(
|
|
3392
|
+
const data = JSON.parse(readFileSync8(join8(SESSION_DIR, name), "utf8"));
|
|
3472
3393
|
if (data.version !== SCHEMA_VERSION || !data.cwd) continue;
|
|
3473
3394
|
out.push({
|
|
3474
3395
|
cwd: data.cwd,
|
|
@@ -4011,7 +3932,7 @@ async function listSites() {
|
|
|
4011
3932
|
function readVersion() {
|
|
4012
3933
|
try {
|
|
4013
3934
|
const here = dirname4(fileURLToPath(import.meta.url));
|
|
4014
|
-
const pkg = JSON.parse(
|
|
3935
|
+
const pkg = JSON.parse(readFileSync9(join9(here, "..", "package.json"), "utf8"));
|
|
4015
3936
|
return pkg.version ?? "0.0.0";
|
|
4016
3937
|
} catch {
|
|
4017
3938
|
return "0.0.0";
|