hilos-agent 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.mjs CHANGED
@@ -57,12 +57,42 @@ const DEFAULTS = {
57
57
  // it still won't run arbitrary commands. Override in hilos-agent.json if you
58
58
  // want a stricter (or `--dangerously-skip-permissions`) command.
59
59
  codingCmd: "claude -p --permission-mode acceptEdits",
60
- // ACP transport opt-in (0759): drive the coding CLI over the Agent Client
61
- // Protocol (JSON-RPC on stdio) instead of argv + stdout scraping. Covers
62
- // opencode (`opencode acp`) and cursor (`cursor-agent acp`), and only for
63
- // runs the workspace already gates with runtime permissions; every other
64
- // run keeps the existing paths. Also enabled by HILOS_ACP=1.
65
- acpTransport: false,
60
+ // ACP transport (0759): drive the coding CLI over the Agent Client Protocol
61
+ // (JSON-RPC on stdio) instead of argv + stdout scraping. Covers opencode
62
+ // (`opencode acp`) and cursor (`cursor-agent acp`), and only for runs the
63
+ // workspace already gates with runtime permissions; every other run keeps
64
+ // the existing paths.
65
+ //
66
+ // ON by default since 0778. Both vendors' adapters are proven live (0759
67
+ // slices 1–2, `scripts/verify-acp.mjs`), and leaving it off meant a
68
+ // workspace that had explicitly granted runtime permissions still got no
69
+ // approval card — the gate existed and nobody was behind it. Resume no
70
+ // longer disqualifies a run either (both agents advertise ACP `loadSession`).
71
+ // Set `acpTransport: false` in hilos-agent.json to opt a machine back out;
72
+ // HILOS_ACP=0 does the same from the environment.
73
+ acpTransport: true,
74
+ // Run transcripts (0792): after a coding run reports, upload the tail of the
75
+ // CLI's own stream so the room keeps a record of what the agent did — the
76
+ // same redacted-NDJSON-in-a-private-bucket a hosted run gets, read only
77
+ // through /api/run-transcript and never by workspace guests.
78
+ //
79
+ // OFF by default, and it stays off until the operator says otherwise. Every
80
+ // other flag here changes how work is DONE on this machine; this one moves
81
+ // bytes off it. Whatever the CLI printed — file paths, command output, the
82
+ // shape of a private codebase — is the operator's, and the honest default for
83
+ // "may I keep a copy of your terminal" is no. The daemon redacts before
84
+ // sending and the server redacts again, but neither is a substitute for
85
+ // being asked.
86
+ //
87
+ // Set `uploadTranscripts: true` in hilos-agent.json to turn it on;
88
+ // HILOS_UPLOAD_TRANSCRIPTS=1 does the same from the environment (=0 forces it
89
+ // back off). Live-reloadable, so it can be revoked without a restart.
90
+ uploadTranscripts: false,
91
+ // Replies in an exact thread bound by an opted-in local-session hook resume
92
+ // that same provider session (0847). The hook install is the consent gate;
93
+ // this switch lets an operator pause inbound pickup without uninstalling it.
94
+ // No transcript is read or uploaded by this path.
95
+ replyBridge: true,
66
96
  // Environment handed to the coding/chat CLI. hilos's own token (HILOS_*) is
67
97
  // ALWAYS stripped either way. "inherit" (default) passes the rest of your
68
98
  // shell env so the coding tool behaves exactly as if you ran it yourself.
@@ -130,7 +160,24 @@ export function resolveConfig({ flags = {}, join: joinPayload } = {}) {
130
160
  chatTimeoutMs: process.env.HILOS_CHAT_TIMEOUT_MS ? Number(process.env.HILOS_CHAT_TIMEOUT_MS) : undefined,
131
161
  backfill: process.env.HILOS_BACKFILL === "1" ? true : undefined,
132
162
  once: process.env.HILOS_ONCE === "1" ? true : undefined,
133
- acpTransport: process.env.HILOS_ACP === "1" ? true : undefined,
163
+ // Now that the default is ON (0778), the env var has to be able to say NO
164
+ // as well as yes — otherwise a machine could only opt out by editing a file.
165
+ acpTransport:
166
+ process.env.HILOS_ACP === "1" ? true : process.env.HILOS_ACP === "0" ? false : undefined,
167
+ // Both directions for the same reason acpTransport has both: a machine
168
+ // whose config file says yes must be able to say no from the environment.
169
+ uploadTranscripts:
170
+ process.env.HILOS_UPLOAD_TRANSCRIPTS === "1"
171
+ ? true
172
+ : process.env.HILOS_UPLOAD_TRANSCRIPTS === "0"
173
+ ? false
174
+ : undefined,
175
+ replyBridge:
176
+ /^(1|on|true)$/i.test(process.env.HILOS_REPLY_BRIDGE || "")
177
+ ? true
178
+ : /^(0|off|false)$/i.test(process.env.HILOS_REPLY_BRIDGE || "")
179
+ ? false
180
+ : undefined,
134
181
  };
135
182
  const merged = { ...DEFAULTS, ...file };
136
183
  for (const [k, v] of Object.entries(env)) if (v !== undefined && v !== "") merged[k] = v;
@@ -163,6 +210,11 @@ export function resolveConfig({ flags = {}, join: joinPayload } = {}) {
163
210
  const LIVE_FIELDS = [
164
211
  "codingCmd",
165
212
  "acpTransport",
213
+ // Consent must be revocable without a restart: edit the file, and the next
214
+ // run stops uploading. Listed here for the record — the actual live merge is
215
+ // special-cased below, because for this field a DELETED key has to mean no.
216
+ "uploadTranscripts",
217
+ "replyBridge",
166
218
  "codingModel",
167
219
  "chatCmd",
168
220
  "codingEnv",
@@ -210,6 +262,30 @@ export function reloadConfig(prev) {
210
262
  const changed = (k) => JSON.stringify(file[k]) !== JSON.stringify(snap[k]);
211
263
  for (const k of LIVE_FIELDS) if (file[k] !== undefined && changed(k)) next[k] = file[k];
212
264
  next.fileSnapshot = file;
265
+ // Consent is the one field where ABSENCE is an answer. Every other live field
266
+ // follows the 0576 rule — only a CHANGED key is applied, so a value that
267
+ // merely pre-existed can't claw back a launch flag — and under that rule
268
+ // DELETING `uploadTranscripts: true` did nothing at all: the key is gone, so
269
+ // `file[k] !== undefined` is false, so the old `true` survived forever. The
270
+ // one way to withdraw permission was to kill the daemon.
271
+ //
272
+ // So this field is recomputed from the file every reload: opted in only while
273
+ // the file still says so. Nothing is lost by the special case — no CLI flag
274
+ // sets it, so the file and the environment are its only sources, and the
275
+ // environment still wins below.
276
+ next.uploadTranscripts = file.uploadTranscripts === true;
277
+ // The reply bridge is safe-on only after the user has installed hooks. A
278
+ // live file edit may pause it with false; removing that edited key returns to
279
+ // the normal on state. An explicit launch flag remains pinned when the file
280
+ // was never changed, preserving the 0576 precedence rule above.
281
+ if (changed("replyBridge")) next.replyBridge = file.replyBridge !== false;
282
+ // The environment stays the operator's override on reload, in both
283
+ // directions — a machine that opted out with =0 must not be opted back in by
284
+ // a file edit (0792).
285
+ if (process.env.HILOS_UPLOAD_TRANSCRIPTS === "1") next.uploadTranscripts = true;
286
+ if (process.env.HILOS_UPLOAD_TRANSCRIPTS === "0") next.uploadTranscripts = false;
287
+ if (/^(1|on|true)$/i.test(process.env.HILOS_REPLY_BRIDGE || "")) next.replyBridge = true;
288
+ if (/^(0|off|false)$/i.test(process.env.HILOS_REPLY_BRIDGE || "")) next.replyBridge = false;
213
289
  if (process.env.CODING_CMD) next.codingCmd = process.env.CODING_CMD;
214
290
  if (process.env.HILOS_CODING_MODEL) next.codingModel = process.env.HILOS_CODING_MODEL;
215
291
  if (process.env.HILOS_CHAT_CMD) next.chatCmd = process.env.HILOS_CHAT_CMD;