@scopebond/agent 0.4.6 → 0.5.0

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.
Files changed (69) hide show
  1. package/README.md +35 -6
  2. package/dist/agent.d.ts +2 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +4 -4
  5. package/dist/agent.js.map +1 -1
  6. package/dist/allowance-sender.d.ts +8 -0
  7. package/dist/allowance-sender.d.ts.map +1 -0
  8. package/dist/allowance-sender.js +80 -0
  9. package/dist/allowance-sender.js.map +1 -0
  10. package/dist/autostart.d.ts +13 -0
  11. package/dist/autostart.d.ts.map +1 -1
  12. package/dist/autostart.js +60 -10
  13. package/dist/autostart.js.map +1 -1
  14. package/dist/cli-main.d.ts +3 -0
  15. package/dist/cli-main.d.ts.map +1 -0
  16. package/dist/cli-main.js +218 -0
  17. package/dist/cli-main.js.map +1 -0
  18. package/dist/cli.js +4 -185
  19. package/dist/cli.js.map +1 -1
  20. package/dist/index.d.ts +7 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/ipc.d.ts +7 -1
  25. package/dist/ipc.d.ts.map +1 -1
  26. package/dist/ipc.js +64 -12
  27. package/dist/ipc.js.map +1 -1
  28. package/dist/log-file.d.ts +6 -0
  29. package/dist/log-file.d.ts.map +1 -0
  30. package/dist/log-file.js +40 -0
  31. package/dist/log-file.js.map +1 -0
  32. package/dist/native-update.d.ts +51 -0
  33. package/dist/native-update.d.ts.map +1 -0
  34. package/dist/native-update.js +120 -0
  35. package/dist/native-update.js.map +1 -0
  36. package/dist/prompt.d.ts +11 -1
  37. package/dist/prompt.d.ts.map +1 -1
  38. package/dist/prompt.js +57 -23
  39. package/dist/prompt.js.map +1 -1
  40. package/dist/reconnect.d.ts +19 -0
  41. package/dist/reconnect.d.ts.map +1 -0
  42. package/dist/reconnect.js +60 -0
  43. package/dist/reconnect.js.map +1 -0
  44. package/dist/self.d.ts +5 -0
  45. package/dist/self.d.ts.map +1 -0
  46. package/dist/self.js +15 -0
  47. package/dist/self.js.map +1 -0
  48. package/dist/service.d.ts +50 -4
  49. package/dist/service.d.ts.map +1 -1
  50. package/dist/service.js +292 -18
  51. package/dist/service.js.map +1 -1
  52. package/dist/setup.d.ts.map +1 -1
  53. package/dist/setup.js +4 -9
  54. package/dist/setup.js.map +1 -1
  55. package/dist/summary.d.ts +16 -0
  56. package/dist/summary.d.ts.map +1 -0
  57. package/dist/summary.js +47 -0
  58. package/dist/summary.js.map +1 -0
  59. package/dist/tray-model.d.ts +91 -0
  60. package/dist/tray-model.d.ts.map +1 -0
  61. package/dist/tray-model.js +159 -0
  62. package/dist/tray-model.js.map +1 -0
  63. package/dist/tray.d.ts.map +1 -1
  64. package/dist/tray.js +103 -29
  65. package/dist/tray.js.map +1 -1
  66. package/dist/update.d.ts.map +1 -1
  67. package/dist/update.js +7 -4
  68. package/dist/update.js.map +1 -1
  69. package/package.json +4 -4
package/dist/prompt.js CHANGED
@@ -17,13 +17,24 @@ export function parseQuestion(raw) {
17
17
  const timeout = Number(r.timeout_ms);
18
18
  if (!Number.isInteger(reasonMin) || reasonMin < 10 || reasonMin > 200)
19
19
  return null;
20
+ const mode = r.mode === "ask" ? "ask" : "override";
21
+ const o = (r.offers && typeof r.offers === "object" ? r.offers : {});
22
+ // A hook before D144 sends no offers: the window offers "Allow once", as it always did.
23
+ const offers = r.offers === undefined ? { allow: true, always: false, ask: false }
24
+ : { allow: mode === "override" && o.allow === true, always: mode === "override" && o.always === true, ask: o.ask === true,
25
+ // A hook that does not say offers 15 minutes wherever it offers "always" (both came with allowances).
26
+ fifteen: mode === "override" && (o.fifteen === true || (o.fifteen === undefined && o.always === true)) };
20
27
  return {
21
28
  action_id: r.action_id, rule: r.rule, title: clip(r.title, 120) || r.rule, summary: clip(r.summary, 200), reason_min: reasonMin,
22
29
  lasts: clip(r.lasts, 80) || "this action only", timeout_ms: Number.isFinite(timeout) ? Math.min(Math.max(timeout, 5_000), 55_000) : 45_000,
30
+ mode, offers,
23
31
  };
24
32
  }
25
33
  export function questionText(q) {
26
- return `Your workspace's rule "${q.title}" blocked this action:\n\n${q.summary}\n\nYou may allow it (${q.lasts}). Say why, in at least ${q.reason_min} characters. Your name, the reason and the action are recorded in your workspace.`;
34
+ const head = `Scopebond blocked this. Your workspace's rule "${q.title}" blocked this action:\n\n${q.summary}\n\n`;
35
+ if (!q.offers.allow)
36
+ return `${head}You may ask an admin to allow it. Say why, in at least ${q.reason_min} characters. The action stays blocked until an admin answers; your name, the reason and the action are recorded in your workspace.`;
37
+ return `${head}You may allow it. Say why, in at least ${q.reason_min} characters. Your name, the reason and the action are recorded in your workspace.`;
27
38
  }
28
39
  function run(command, args, timeoutMs) {
29
40
  return new Promise((resolve) => {
@@ -58,33 +69,41 @@ $f.StartPosition = 'CenterScreen'
58
69
  $f.FormBorderStyle = 'FixedDialog'
59
70
  $f.MaximizeBox = $false
60
71
  $f.MinimizeBox = $false
61
- $f.ClientSize = New-Object Drawing.Size(460, 250)
72
+ $f.ClientSize = New-Object Drawing.Size(640, 250)
62
73
  $l = New-Object Windows.Forms.Label
63
74
  $l.Text = (T '${b64(questionText(q))}')
64
- $l.SetBounds(12, 10, 436, 130)
75
+ $l.SetBounds(12, 10, 616, 130)
65
76
  $r = New-Object Windows.Forms.TextBox
66
- $r.SetBounds(12, 145, 436, 24)
67
- $a = New-Object Windows.Forms.Button
68
- $a.Text = (T '${b64("Allow once")}')
69
- $a.SetBounds(256, 200, 92, 30)
70
- $a.Enabled = $false
71
- $d = New-Object Windows.Forms.Button
72
- $d.Text = (T '${b64("Don't allow")}')
73
- $d.SetBounds(356, 200, 92, 30)
74
- $r.Add_TextChanged({ $a.Enabled = ($r.Text.Trim().Length -ge $min) })
77
+ $r.SetBounds(12, 145, 616, 24)
75
78
  $script:answer = 'deny'
76
- $a.Add_Click({ $script:answer = 'allow'; $f.Close() })
77
- $d.Add_Click({ $f.Close() })
79
+ $script:lasts = 'once'
80
+ $needReason = @()
81
+ $x = 628
82
+ function Btn($label, $width, $decision, $lasts, $needs) {
83
+ $b = New-Object Windows.Forms.Button
84
+ $b.Text = (T $label)
85
+ $script:x0 = $x - $width
86
+ $b.SetBounds($x - $width, 200, $width, 30)
87
+ $b.Add_Click({ $script:answer = $decision; $script:lasts = $lasts; $f.Close() }.GetNewClosure())
88
+ if ($needs) { $b.Enabled = $false; $script:needReason += $b }
89
+ $f.Controls.Add($b)
90
+ $b
91
+ }
92
+ $d = Btn '${b64("Don't allow")}' 100 'deny' 'once' $false; $x -= 108
93
+ ${q.offers.ask ? `[void](Btn '${b64("Ask an admin")}' 110 'ask' 'once' $true); $x -= 118` : ""}
94
+ ${q.offers.always ? `[void](Btn '${b64("Always allow this here…")}' 150 'allow' 'always' $true); $x -= 158` : ""}
95
+ ${q.offers.allow && q.offers.fifteen ? `[void](Btn '${b64("Allow for 15 min")}' 120 'allow' '15m' $true); $x -= 128; ` : ""}${q.offers.allow ? `[void](Btn '${b64("Allow once")}' 92 'allow' 'once' $true)` : ""}
96
+ $r.Add_TextChanged({ foreach ($b in $script:needReason) { $b.Enabled = ($r.Text.Trim().Length -ge $min) } })
78
97
  $f.AcceptButton = $d
79
98
  $f.CancelButton = $d
80
- $f.Controls.AddRange(@($l, $r, $a, $d))
99
+ $f.Controls.AddRange(@($l, $r))
81
100
  $t = New-Object Windows.Forms.Timer
82
101
  $t.Interval = ${q.timeout_ms}
83
102
  $t.Add_Tick({ $t.Stop(); $f.Close() })
84
103
  $t.Start()
85
104
  $f.Add_Shown({ $f.Activate(); $r.Focus() })
86
105
  [void]$f.ShowDialog()
87
- $out = @{ decision = $script:answer; reason = $(if ($script:answer -eq 'allow') { $r.Text.Trim() } else { '' }) }
106
+ $out = @{ decision = $script:answer; lasts = $script:lasts; reason = $(if ($script:answer -ne 'deny') { $r.Text.Trim() } else { '' }) }
88
107
  [Console]::Out.Write((ConvertTo-Json $out -Compress))
89
108
  `;
90
109
  }
@@ -95,7 +114,12 @@ async function promptWindows(q) {
95
114
  return { decision: "unavailable" };
96
115
  try {
97
116
  const parsed = JSON.parse(stdout.trim());
98
- return parsed.decision === "allow" ? { decision: "allow", reason: String(parsed.reason ?? "") } : { decision: "deny" };
117
+ const lasts = parsed.lasts === "15m" || parsed.lasts === "always" ? parsed.lasts : "once";
118
+ if (parsed.decision === "allow" && q.offers.allow && (lasts !== "always" || q.offers.always) && (lasts !== "15m" || q.offers.fifteen))
119
+ return { decision: "allow", reason: String(parsed.reason ?? ""), lasts };
120
+ if (parsed.decision === "ask" && q.offers.ask)
121
+ return { decision: "ask", reason: String(parsed.reason ?? "") };
122
+ return { decision: "deny" };
99
123
  }
100
124
  catch {
101
125
  return { decision: "unavailable" };
@@ -111,31 +135,41 @@ const MAC_SCRIPT = [
111
135
  "end run",
112
136
  ];
113
137
  async function promptMac(q) {
114
- const args = MAC_SCRIPT.flatMap((line) => ["-e", line]);
138
+ // macOS dialogs take three buttons: "Allow once" where allowing is offered, otherwise "Ask an admin".
139
+ const asking = !q.offers.allow && q.offers.ask;
140
+ if (!q.offers.allow && !asking)
141
+ return { decision: "unavailable" };
142
+ const script = asking ? MAC_SCRIPT.map((line) => line.replace(/Allow once/g, "Ask an admin").replace('return "allow"', 'return "ask"')) : MAC_SCRIPT;
143
+ const args = script.flatMap((line) => ["-e", line]);
115
144
  const { code, stdout } = await run("osascript", [...args, questionText(q), String(Math.round(q.timeout_ms / 1000))], q.timeout_ms);
116
145
  if (code === null)
117
146
  return { decision: "unavailable" };
118
147
  if (code !== 0)
119
148
  return { decision: "deny" }; // "Don't allow" is the cancel button
120
149
  const [first, ...rest] = stdout.replace(/\r?\n$/, "").split("\n");
121
- return first === "allow" ? { decision: "allow", reason: rest.join("\n") } : { decision: "deny" };
150
+ if (first === "ask")
151
+ return { decision: "ask", reason: rest.join("\n") };
152
+ return first === "allow" ? { decision: "allow", reason: rest.join("\n"), lasts: "once" } : { decision: "deny" };
122
153
  }
123
154
  async function promptLinux(q) {
155
+ const asking = !q.offers.allow && q.offers.ask;
156
+ if (!q.offers.allow && !asking)
157
+ return { decision: "unavailable" };
124
158
  const { code, stdout } = await run("zenity", [
125
- "--entry", "--title=Scopebond", `--text=${questionText(q)}`, "--ok-label=Allow once", "--cancel-label=Don't allow",
159
+ "--entry", "--title=Scopebond", `--text=${questionText(q)}`, `--ok-label=${asking ? "Ask an admin" : "Allow once"}`, "--cancel-label=Don't allow",
126
160
  `--timeout=${Math.round(q.timeout_ms / 1000)}`,
127
161
  ], q.timeout_ms);
128
162
  if (code === null)
129
163
  return { decision: "unavailable" };
130
164
  if (code === 0)
131
- return { decision: "allow", reason: stdout.replace(/\r?\n$/, "") };
165
+ return asking ? { decision: "ask", reason: stdout.replace(/\r?\n$/, "") } : { decision: "allow", reason: stdout.replace(/\r?\n$/, ""), lasts: "once" };
132
166
  // 1: Don't allow; 5: timed out; anything else (no display, not installed): no window.
133
167
  return code === 1 || code === 5 ? { decision: "deny" } : { decision: "unavailable" };
134
168
  }
135
169
  /** The window for this operating system. A reason shorter than the workspace accepts is refused here as well. */
136
170
  export const systemPrompter = async (q) => {
137
171
  const answer = process.platform === "win32" ? await promptWindows(q) : process.platform === "darwin" ? await promptMac(q) : await promptLinux(q);
138
- if (answer.decision !== "allow")
172
+ if (answer.decision !== "allow" && answer.decision !== "ask")
139
173
  return answer;
140
174
  const reason = (answer.reason ?? "").trim();
141
175
  if (reason.length < q.reason_min)
@@ -145,7 +179,7 @@ export const systemPrompter = async (q) => {
145
179
  os_user = userInfo().username;
146
180
  }
147
181
  catch { /* not known */ }
148
- return { decision: "allow", reason: reason.slice(0, 500), ...(os_user ? { os_user } : {}) };
182
+ return { ...answer, reason: reason.slice(0, 500), ...(os_user ? { os_user } : {}) };
149
183
  };
150
184
  /** One window at a time: a second request waits for the first, and gives up as no answer if it would wait too long. */
151
185
  export function serialized(prompter) {
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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","sourcesContent":["// The Scopebond window: asks the person at the computer whether to allow one action a workspace rule blocks, with a reason.\n// It is shown by this resident agent with the operating system's own tools (Windows PowerShell with Windows Forms, macOS\n// osascript, Linux zenity), never by the coding agent, and the answer comes only from the window. Text reaches each tool as\n// data (base64 for PowerShell, arguments for osascript and zenity), never spliced into code.\n\nimport { spawn } from \"node:child_process\";\nimport { userInfo } from \"node:os\";\n\nexport interface OverrideQuestion {\n action_id: string;\n rule: string;\n title: string;\n summary: string;\n reason_min: number;\n lasts: string;\n timeout_ms: number;\n}\nexport interface OverrideAnswer { decision: \"allow\" | \"deny\" | \"unavailable\"; reason?: string; os_user?: string }\nexport type Prompter = (question: OverrideQuestion) => Promise<OverrideAnswer>;\n\nconst ID = /^[A-Za-z0-9._:-]{16,200}$/;\nconst clip = (v: unknown, max: number) => (typeof v === \"string\" ? v.replace(/[\\u0000-\\u001f\\u007f]/g, \" \").slice(0, max) : \"\");\n\n/** Check a request from the local channel. Anything malformed is refused before a window opens. */\nexport function parseQuestion(raw: unknown): OverrideQuestion | null {\n if (!raw || typeof raw !== \"object\") return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.action_id !== \"string\" || !ID.test(r.action_id) || typeof r.rule !== \"string\" || !/^[a-z0-9-]{1,64}$/.test(r.rule)) return null;\n const reasonMin = Number(r.reason_min);\n const timeout = Number(r.timeout_ms);\n if (!Number.isInteger(reasonMin) || reasonMin < 10 || reasonMin > 200) return null;\n return {\n action_id: r.action_id, rule: r.rule, title: clip(r.title, 120) || r.rule, summary: clip(r.summary, 200), reason_min: reasonMin,\n lasts: clip(r.lasts, 80) || \"this action only\", timeout_ms: Number.isFinite(timeout) ? Math.min(Math.max(timeout, 5_000), 55_000) : 45_000,\n };\n}\n\nexport function questionText(q: OverrideQuestion): string {\n return `Your workspace's rule \"${q.title}\" blocked this action:\\n\\n${q.summary}\\n\\nYou may allow it (${q.lasts}). Say why, in at least ${q.reason_min} characters. Your name, the reason and the action are recorded in your workspace.`;\n}\n\nfunction run(command: string, args: string[], timeoutMs: number): Promise<{ code: number | null; stdout: string }> {\n return new Promise((resolve) => {\n let stdout = \"\";\n let child;\n try { child = spawn(command, args, { windowsHide: false, stdio: [\"ignore\", \"pipe\", \"ignore\"] }); }\n catch { resolve({ code: null, stdout: \"\" }); return; }\n const timer = setTimeout(() => { child.kill(); resolve({ code: null, stdout }); }, timeoutMs + 3_000);\n child.stdout?.on(\"data\", (d) => { stdout += d; });\n child.on(\"error\", () => { clearTimeout(timer); resolve({ code: null, stdout: \"\" }); });\n child.on(\"close\", (code) => { clearTimeout(timer); resolve({ code, stdout }); });\n });\n}\n\nconst b64 = (s: string) => Buffer.from(s, \"utf8\").toString(\"base64\");\n\n/** Windows: a small topmost form from Windows' own signed PowerShell. Closes itself at the timeout. */\nexport function windowsScript(q: OverrideQuestion): string {\n return `\n$ErrorActionPreference = 'Stop'\nAdd-Type -AssemblyName System.Windows.Forms\nAdd-Type -AssemblyName System.Drawing\nfunction T($s) { [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($s)) }\n$min = ${q.reason_min}\n$f = New-Object Windows.Forms.Form\n$f.Text = 'Scopebond'\n$f.TopMost = $true\n$f.StartPosition = 'CenterScreen'\n$f.FormBorderStyle = 'FixedDialog'\n$f.MaximizeBox = $false\n$f.MinimizeBox = $false\n$f.ClientSize = New-Object Drawing.Size(460, 250)\n$l = New-Object Windows.Forms.Label\n$l.Text = (T '${b64(questionText(q))}')\n$l.SetBounds(12, 10, 436, 130)\n$r = New-Object Windows.Forms.TextBox\n$r.SetBounds(12, 145, 436, 24)\n$a = New-Object Windows.Forms.Button\n$a.Text = (T '${b64(\"Allow once\")}')\n$a.SetBounds(256, 200, 92, 30)\n$a.Enabled = $false\n$d = New-Object Windows.Forms.Button\n$d.Text = (T '${b64(\"Don't allow\")}')\n$d.SetBounds(356, 200, 92, 30)\n$r.Add_TextChanged({ $a.Enabled = ($r.Text.Trim().Length -ge $min) })\n$script:answer = 'deny'\n$a.Add_Click({ $script:answer = 'allow'; $f.Close() })\n$d.Add_Click({ $f.Close() })\n$f.AcceptButton = $d\n$f.CancelButton = $d\n$f.Controls.AddRange(@($l, $r, $a, $d))\n$t = New-Object Windows.Forms.Timer\n$t.Interval = ${q.timeout_ms}\n$t.Add_Tick({ $t.Stop(); $f.Close() })\n$t.Start()\n$f.Add_Shown({ $f.Activate(); $r.Focus() })\n[void]$f.ShowDialog()\n$out = @{ decision = $script:answer; reason = $(if ($script:answer -eq 'allow') { $r.Text.Trim() } else { '' }) }\n[Console]::Out.Write((ConvertTo-Json $out -Compress))\n`;\n}\n\nasync function promptWindows(q: OverrideQuestion): Promise<OverrideAnswer> {\n const encoded = Buffer.from(windowsScript(q), \"utf16le\").toString(\"base64\");\n const { code, stdout } = await run(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-STA\", \"-WindowStyle\", \"Hidden\", \"-EncodedCommand\", encoded], q.timeout_ms);\n if (code !== 0) return { decision: \"unavailable\" };\n try {\n const parsed = JSON.parse(stdout.trim()) as { decision?: string; reason?: string };\n return parsed.decision === \"allow\" ? { decision: \"allow\", reason: String(parsed.reason ?? \"\") } : { decision: \"deny\" };\n } catch { return { decision: \"unavailable\" }; }\n}\n\nconst MAC_SCRIPT = [\n \"on run argv\",\n \"set q to item 1 of argv\",\n \"set secs to (item 2 of argv) as integer\",\n \"set r to display dialog q default answer \\\"\\\" with title \\\"Scopebond\\\" buttons {\\\"Don't allow\\\", \\\"Allow once\\\"} default button \\\"Don't allow\\\" cancel button \\\"Don't allow\\\" giving up after secs\",\n \"if gave up of r then return \\\"timeout\\\"\",\n \"return \\\"allow\\\" & linefeed & (text returned of r)\",\n \"end run\",\n];\n\nasync function promptMac(q: OverrideQuestion): Promise<OverrideAnswer> {\n const args = MAC_SCRIPT.flatMap((line) => [\"-e\", line]);\n const { code, stdout } = await run(\"osascript\", [...args, questionText(q), String(Math.round(q.timeout_ms / 1000))], q.timeout_ms);\n if (code === null) return { decision: \"unavailable\" };\n if (code !== 0) return { decision: \"deny\" }; // \"Don't allow\" is the cancel button\n const [first, ...rest] = stdout.replace(/\\r?\\n$/, \"\").split(\"\\n\");\n return first === \"allow\" ? { decision: \"allow\", reason: rest.join(\"\\n\") } : { decision: \"deny\" };\n}\n\nasync function promptLinux(q: OverrideQuestion): Promise<OverrideAnswer> {\n const { code, stdout } = await run(\"zenity\", [\n \"--entry\", \"--title=Scopebond\", `--text=${questionText(q)}`, \"--ok-label=Allow once\", \"--cancel-label=Don't allow\",\n `--timeout=${Math.round(q.timeout_ms / 1000)}`,\n ], q.timeout_ms);\n if (code === null) return { decision: \"unavailable\" };\n if (code === 0) return { decision: \"allow\", reason: stdout.replace(/\\r?\\n$/, \"\") };\n // 1: Don't allow; 5: timed out; anything else (no display, not installed): no window.\n return code === 1 || code === 5 ? { decision: \"deny\" } : { decision: \"unavailable\" };\n}\n\n/** The window for this operating system. A reason shorter than the workspace accepts is refused here as well. */\nexport const systemPrompter: Prompter = async (q) => {\n const answer = process.platform === \"win32\" ? await promptWindows(q) : process.platform === \"darwin\" ? await promptMac(q) : await promptLinux(q);\n if (answer.decision !== \"allow\") return answer;\n const reason = (answer.reason ?? \"\").trim();\n if (reason.length < q.reason_min) return { decision: \"deny\" };\n let os_user: string | undefined;\n try { os_user = userInfo().username; } catch { /* not known */ }\n return { decision: \"allow\", reason: reason.slice(0, 500), ...(os_user ? { os_user } : {}) };\n};\n\n/** One window at a time: a second request waits for the first, and gives up as no answer if it would wait too long. */\nexport function serialized(prompter: Prompter): Prompter {\n let chain: Promise<unknown> = Promise.resolve();\n return (q) => {\n const queuedAt = Date.now();\n const next = chain.then(() => (Date.now() - queuedAt > q.timeout_ms ? { decision: \"unavailable\" as const } : prompter(q)));\n chain = next.catch(() => undefined);\n return next;\n };\n}\n"]}
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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","sourcesContent":["// The Scopebond window: asks the person at the computer whether to allow one action a workspace rule blocks, with a reason.\n// It is shown by this resident agent with the operating system's own tools (Windows PowerShell with Windows Forms, macOS\n// osascript, Linux zenity), never by the coding agent, and the answer comes only from the window. Text reaches each tool as\n// data (base64 for PowerShell, arguments for osascript and zenity), never spliced into code.\n\nimport { spawn } from \"node:child_process\";\nimport { userInfo } from \"node:os\";\n\nexport interface OverrideQuestion {\n action_id: string;\n rule: string;\n title: string;\n summary: string;\n reason_min: number;\n lasts: string;\n timeout_ms: number;\n /** D144: override = \"Block, person may allow\"; ask = \"Block, person may ask\" (only Ask an admin). */\n mode: \"override\" | \"ask\";\n /** Which choices the workspace allows: allow (once), fifteen (for 15 minutes), always (an allowance), ask (Ask an admin). */\n offers: { allow: boolean; always: boolean; ask: boolean; fifteen?: boolean };\n}\nexport interface OverrideAnswer { decision: \"allow\" | \"deny\" | \"unavailable\" | \"ask\"; reason?: string; os_user?: string; lasts?: \"once\" | \"15m\" | \"always\" }\nexport type Prompter = (question: OverrideQuestion) => Promise<OverrideAnswer>;\n\nconst ID = /^[A-Za-z0-9._:-]{16,200}$/;\nconst clip = (v: unknown, max: number) => (typeof v === \"string\" ? v.replace(/[\\u0000-\\u001f\\u007f]/g, \" \").slice(0, max) : \"\");\n\n/** Check a request from the local channel. Anything malformed is refused before a window opens. */\nexport function parseQuestion(raw: unknown): OverrideQuestion | null {\n if (!raw || typeof raw !== \"object\") return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.action_id !== \"string\" || !ID.test(r.action_id) || typeof r.rule !== \"string\" || !/^[a-z0-9-]{1,64}$/.test(r.rule)) return null;\n const reasonMin = Number(r.reason_min);\n const timeout = Number(r.timeout_ms);\n if (!Number.isInteger(reasonMin) || reasonMin < 10 || reasonMin > 200) return null;\n const mode = r.mode === \"ask\" ? \"ask\" : \"override\";\n const o = (r.offers && typeof r.offers === \"object\" ? r.offers : {}) as Record<string, unknown>;\n // A hook before D144 sends no offers: the window offers \"Allow once\", as it always did.\n const offers = r.offers === undefined ? { allow: true, always: false, ask: false }\n : { allow: mode === \"override\" && o.allow === true, always: mode === \"override\" && o.always === true, ask: o.ask === true,\n // A hook that does not say offers 15 minutes wherever it offers \"always\" (both came with allowances).\n fifteen: mode === \"override\" && (o.fifteen === true || (o.fifteen === undefined && o.always === true)) };\n return {\n action_id: r.action_id, rule: r.rule, title: clip(r.title, 120) || r.rule, summary: clip(r.summary, 200), reason_min: reasonMin,\n lasts: clip(r.lasts, 80) || \"this action only\", timeout_ms: Number.isFinite(timeout) ? Math.min(Math.max(timeout, 5_000), 55_000) : 45_000,\n mode, offers,\n };\n}\n\nexport function questionText(q: OverrideQuestion): string {\n const head = `Scopebond blocked this. Your workspace's rule \"${q.title}\" blocked this action:\\n\\n${q.summary}\\n\\n`;\n if (!q.offers.allow) return `${head}You may ask an admin to allow it. Say why, in at least ${q.reason_min} characters. The action stays blocked until an admin answers; your name, the reason and the action are recorded in your workspace.`;\n return `${head}You may allow it. Say why, in at least ${q.reason_min} characters. Your name, the reason and the action are recorded in your workspace.`;\n}\n\nfunction run(command: string, args: string[], timeoutMs: number): Promise<{ code: number | null; stdout: string }> {\n return new Promise((resolve) => {\n let stdout = \"\";\n let child;\n try { child = spawn(command, args, { windowsHide: false, stdio: [\"ignore\", \"pipe\", \"ignore\"] }); }\n catch { resolve({ code: null, stdout: \"\" }); return; }\n const timer = setTimeout(() => { child.kill(); resolve({ code: null, stdout }); }, timeoutMs + 3_000);\n child.stdout?.on(\"data\", (d) => { stdout += d; });\n child.on(\"error\", () => { clearTimeout(timer); resolve({ code: null, stdout: \"\" }); });\n child.on(\"close\", (code) => { clearTimeout(timer); resolve({ code, stdout }); });\n });\n}\n\nconst b64 = (s: string) => Buffer.from(s, \"utf8\").toString(\"base64\");\n\n/** Windows: a small topmost form from Windows' own signed PowerShell. Closes itself at the timeout. */\nexport function windowsScript(q: OverrideQuestion): string {\n return `\n$ErrorActionPreference = 'Stop'\nAdd-Type -AssemblyName System.Windows.Forms\nAdd-Type -AssemblyName System.Drawing\nfunction T($s) { [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($s)) }\n$min = ${q.reason_min}\n$f = New-Object Windows.Forms.Form\n$f.Text = 'Scopebond'\n$f.TopMost = $true\n$f.StartPosition = 'CenterScreen'\n$f.FormBorderStyle = 'FixedDialog'\n$f.MaximizeBox = $false\n$f.MinimizeBox = $false\n$f.ClientSize = New-Object Drawing.Size(640, 250)\n$l = New-Object Windows.Forms.Label\n$l.Text = (T '${b64(questionText(q))}')\n$l.SetBounds(12, 10, 616, 130)\n$r = New-Object Windows.Forms.TextBox\n$r.SetBounds(12, 145, 616, 24)\n$script:answer = 'deny'\n$script:lasts = 'once'\n$needReason = @()\n$x = 628\nfunction Btn($label, $width, $decision, $lasts, $needs) {\n $b = New-Object Windows.Forms.Button\n $b.Text = (T $label)\n $script:x0 = $x - $width\n $b.SetBounds($x - $width, 200, $width, 30)\n $b.Add_Click({ $script:answer = $decision; $script:lasts = $lasts; $f.Close() }.GetNewClosure())\n if ($needs) { $b.Enabled = $false; $script:needReason += $b }\n $f.Controls.Add($b)\n $b\n}\n$d = Btn '${b64(\"Don't allow\")}' 100 'deny' 'once' $false; $x -= 108\n${q.offers.ask ? `[void](Btn '${b64(\"Ask an admin\")}' 110 'ask' 'once' $true); $x -= 118` : \"\"}\n${q.offers.always ? `[void](Btn '${b64(\"Always allow this here…\")}' 150 'allow' 'always' $true); $x -= 158` : \"\"}\n${q.offers.allow && q.offers.fifteen ? `[void](Btn '${b64(\"Allow for 15 min\")}' 120 'allow' '15m' $true); $x -= 128; ` : \"\"}${q.offers.allow ? `[void](Btn '${b64(\"Allow once\")}' 92 'allow' 'once' $true)` : \"\"}\n$r.Add_TextChanged({ foreach ($b in $script:needReason) { $b.Enabled = ($r.Text.Trim().Length -ge $min) } })\n$f.AcceptButton = $d\n$f.CancelButton = $d\n$f.Controls.AddRange(@($l, $r))\n$t = New-Object Windows.Forms.Timer\n$t.Interval = ${q.timeout_ms}\n$t.Add_Tick({ $t.Stop(); $f.Close() })\n$t.Start()\n$f.Add_Shown({ $f.Activate(); $r.Focus() })\n[void]$f.ShowDialog()\n$out = @{ decision = $script:answer; lasts = $script:lasts; reason = $(if ($script:answer -ne 'deny') { $r.Text.Trim() } else { '' }) }\n[Console]::Out.Write((ConvertTo-Json $out -Compress))\n`;\n}\n\nasync function promptWindows(q: OverrideQuestion): Promise<OverrideAnswer> {\n const encoded = Buffer.from(windowsScript(q), \"utf16le\").toString(\"base64\");\n const { code, stdout } = await run(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-STA\", \"-WindowStyle\", \"Hidden\", \"-EncodedCommand\", encoded], q.timeout_ms);\n if (code !== 0) return { decision: \"unavailable\" };\n try {\n const parsed = JSON.parse(stdout.trim()) as { decision?: string; reason?: string; lasts?: string };\n const lasts = parsed.lasts === \"15m\" || parsed.lasts === \"always\" ? parsed.lasts : \"once\";\n if (parsed.decision === \"allow\" && q.offers.allow && (lasts !== \"always\" || q.offers.always) && (lasts !== \"15m\" || q.offers.fifteen)) return { decision: \"allow\", reason: String(parsed.reason ?? \"\"), lasts };\n if (parsed.decision === \"ask\" && q.offers.ask) return { decision: \"ask\", reason: String(parsed.reason ?? \"\") };\n return { decision: \"deny\" };\n } catch { return { decision: \"unavailable\" }; }\n}\n\nconst MAC_SCRIPT = [\n \"on run argv\",\n \"set q to item 1 of argv\",\n \"set secs to (item 2 of argv) as integer\",\n \"set r to display dialog q default answer \\\"\\\" with title \\\"Scopebond\\\" buttons {\\\"Don't allow\\\", \\\"Allow once\\\"} default button \\\"Don't allow\\\" cancel button \\\"Don't allow\\\" giving up after secs\",\n \"if gave up of r then return \\\"timeout\\\"\",\n \"return \\\"allow\\\" & linefeed & (text returned of r)\",\n \"end run\",\n];\n\nasync function promptMac(q: OverrideQuestion): Promise<OverrideAnswer> {\n // macOS dialogs take three buttons: \"Allow once\" where allowing is offered, otherwise \"Ask an admin\".\n const asking = !q.offers.allow && q.offers.ask;\n if (!q.offers.allow && !asking) return { decision: \"unavailable\" };\n const script = asking ? MAC_SCRIPT.map((line) => line.replace(/Allow once/g, \"Ask an admin\").replace('return \"allow\"', 'return \"ask\"')) : MAC_SCRIPT;\n const args = script.flatMap((line) => [\"-e\", line]);\n const { code, stdout } = await run(\"osascript\", [...args, questionText(q), String(Math.round(q.timeout_ms / 1000))], q.timeout_ms);\n if (code === null) return { decision: \"unavailable\" };\n if (code !== 0) return { decision: \"deny\" }; // \"Don't allow\" is the cancel button\n const [first, ...rest] = stdout.replace(/\\r?\\n$/, \"\").split(\"\\n\");\n if (first === \"ask\") return { decision: \"ask\", reason: rest.join(\"\\n\") };\n return first === \"allow\" ? { decision: \"allow\", reason: rest.join(\"\\n\"), lasts: \"once\" } : { decision: \"deny\" };\n}\n\nasync function promptLinux(q: OverrideQuestion): Promise<OverrideAnswer> {\n const asking = !q.offers.allow && q.offers.ask;\n if (!q.offers.allow && !asking) return { decision: \"unavailable\" };\n const { code, stdout } = await run(\"zenity\", [\n \"--entry\", \"--title=Scopebond\", `--text=${questionText(q)}`, `--ok-label=${asking ? \"Ask an admin\" : \"Allow once\"}`, \"--cancel-label=Don't allow\",\n `--timeout=${Math.round(q.timeout_ms / 1000)}`,\n ], q.timeout_ms);\n if (code === null) return { decision: \"unavailable\" };\n if (code === 0) return asking ? { decision: \"ask\", reason: stdout.replace(/\\r?\\n$/, \"\") } : { decision: \"allow\", reason: stdout.replace(/\\r?\\n$/, \"\"), lasts: \"once\" };\n // 1: Don't allow; 5: timed out; anything else (no display, not installed): no window.\n return code === 1 || code === 5 ? { decision: \"deny\" } : { decision: \"unavailable\" };\n}\n\n/** The window for this operating system. A reason shorter than the workspace accepts is refused here as well. */\nexport const systemPrompter: Prompter = async (q) => {\n const answer = process.platform === \"win32\" ? await promptWindows(q) : process.platform === \"darwin\" ? await promptMac(q) : await promptLinux(q);\n if (answer.decision !== \"allow\" && answer.decision !== \"ask\") return answer;\n const reason = (answer.reason ?? \"\").trim();\n if (reason.length < q.reason_min) return { decision: \"deny\" };\n let os_user: string | undefined;\n try { os_user = userInfo().username; } catch { /* not known */ }\n return { ...answer, reason: reason.slice(0, 500), ...(os_user ? { os_user } : {}) };\n};\n\n/** One window at a time: a second request waits for the first, and gives up as no answer if it would wait too long. */\nexport function serialized(prompter: Prompter): Prompter {\n let chain: Promise<unknown> = Promise.resolve();\n return (q) => {\n const queuedAt = Date.now();\n const next = chain.then(() => (Date.now() - queuedAt > q.timeout_ms ? { decision: \"unavailable\" as const } : prompter(q)));\n chain = next.catch(() => undefined);\n return next;\n };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+ export interface ReconnectStart {
3
+ user_code: string;
4
+ verification_url: string;
5
+ }
6
+ /** The code and approval link in the sign-in's output, or null. */
7
+ export declare function parseLoginPrompt(output: string): ReconnectStart | null;
8
+ export interface Reconnecting {
9
+ child: ChildProcess;
10
+ started: ReconnectStart;
11
+ }
12
+ /** Start the sign-in. Resolves with the code once the sign-in prints it (or an error); `onDone` runs when it finishes. */
13
+ export declare function startReconnect(dir: string, onDone: (ok: boolean) => void, spawnImpl?: typeof spawn, open?: (url: string) => void): Promise<{
14
+ started: ReconnectStart;
15
+ child: ChildProcess;
16
+ } | {
17
+ error: string;
18
+ }>;
19
+ //# sourceMappingURL=reconnect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconnect.d.ts","sourceRoot":"","sources":["../src/reconnect.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI9D,MAAM,WAAW,cAAc;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE;AAE/E,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAItE;AAED,MAAM,WAAW,YAAY;IAAG,KAAK,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE;AAE9E,0HAA0H;AAC1H,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,EAAE,SAAS,GAAE,OAAO,KAAa,EAAE,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAoB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BtO"}
@@ -0,0 +1,60 @@
1
+ // SB392 (D143): "Reconnect…" from the tray, with no terminal. The agent runs the hook's own sign-in (`login <workspace>
2
+ // --no-install`) for the workspace this computer is already connected to, never a URL from a link or a notification. It reads
3
+ // the code the sign-in prints, opens the approval page on that workspace, and the sign-in keeps waiting for the approval in
4
+ // the background. The workspace lets the same computer keep its key, so records waiting on it are delivered as they are.
5
+ import { spawn } from "node:child_process";
6
+ import { hookSelfCommand, loadConnection } from "@scopebond/hook";
7
+ import { openInBrowser, sameOrigin } from "./summary.js";
8
+ /** The code and approval link in the sign-in's output, or null. */
9
+ export function parseLoginPrompt(output) {
10
+ const url = /To connect this computer, open:\s+(\S+)/.exec(output)?.[1];
11
+ const code = /shows the code\s+([A-Z]{4}-[A-Z]{4})/.exec(output)?.[1];
12
+ return url && code ? { user_code: code, verification_url: url } : null;
13
+ }
14
+ /** Start the sign-in. Resolves with the code once the sign-in prints it (or an error); `onDone` runs when it finishes. */
15
+ export function startReconnect(dir, onDone, spawnImpl = spawn, open = openInBrowser) {
16
+ const connection = loadConnection(dir);
17
+ if (!connection)
18
+ return Promise.resolve({ error: "this computer was never connected to a workspace; sign in from a terminal first" });
19
+ const origin = new URL(connection.url).origin;
20
+ // The hook this agent carries: its cli.js from npm, or this same file in the single executable.
21
+ const [program, args] = hookSelfCommand(["login", origin, "--no-install"]);
22
+ return new Promise((resolve) => {
23
+ let output = "";
24
+ let settled = false;
25
+ const child = spawnImpl(program, args, {
26
+ env: { ...process.env, SCOPEBOND_HOME: dir }, stdio: ["ignore", "pipe", "pipe"], windowsHide: true,
27
+ });
28
+ const timer = setTimeout(() => { if (!settled) {
29
+ settled = true;
30
+ resolve({ error: "the workspace did not start a sign-in in time" });
31
+ } }, 30_000);
32
+ const read = (chunk) => {
33
+ output += chunk.toString("utf8");
34
+ const started = parseLoginPrompt(output);
35
+ if (started && !settled) {
36
+ settled = true;
37
+ clearTimeout(timer);
38
+ if (sameOrigin(started.verification_url, origin))
39
+ open(started.verification_url);
40
+ resolve({ started, child });
41
+ }
42
+ };
43
+ child.stdout?.on("data", read);
44
+ child.stderr?.on("data", read);
45
+ child.on("error", (error) => { if (!settled) {
46
+ settled = true;
47
+ clearTimeout(timer);
48
+ resolve({ error: error.message });
49
+ } });
50
+ child.on("exit", (code) => {
51
+ clearTimeout(timer);
52
+ if (!settled) {
53
+ settled = true;
54
+ resolve({ error: output.trim().split("\n").at(-1)?.slice(0, 200) || `the sign-in stopped (exit ${code})` });
55
+ }
56
+ onDone(code === 0);
57
+ });
58
+ });
59
+ }
60
+ //# sourceMappingURL=reconnect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconnect.js","sourceRoot":"","sources":["../src/reconnect.ts"],"names":[],"mappings":"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","sourcesContent":["// SB392 (D143): \"Reconnect…\" from the tray, with no terminal. The agent runs the hook's own sign-in (`login <workspace>\n// --no-install`) for the workspace this computer is already connected to, never a URL from a link or a notification. It reads\n// the code the sign-in prints, opens the approval page on that workspace, and the sign-in keeps waiting for the approval in\n// the background. The workspace lets the same computer keep its key, so records waiting on it are delivered as they are.\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { hookSelfCommand, loadConnection } from \"@scopebond/hook\";\nimport { openInBrowser, sameOrigin } from \"./summary.js\";\n\nexport interface ReconnectStart { user_code: string; verification_url: string }\n\n/** The code and approval link in the sign-in's output, or null. */\nexport function parseLoginPrompt(output: string): ReconnectStart | null {\n const url = /To connect this computer, open:\\s+(\\S+)/.exec(output)?.[1];\n const code = /shows the code\\s+([A-Z]{4}-[A-Z]{4})/.exec(output)?.[1];\n return url && code ? { user_code: code, verification_url: url } : null;\n}\n\nexport interface Reconnecting { child: ChildProcess; started: ReconnectStart }\n\n/** Start the sign-in. Resolves with the code once the sign-in prints it (or an error); `onDone` runs when it finishes. */\nexport function startReconnect(dir: string, onDone: (ok: boolean) => void, spawnImpl: typeof spawn = spawn, open: (url: string) => void = openInBrowser): Promise<{ started: ReconnectStart; child: ChildProcess } | { error: string }> {\n const connection = loadConnection(dir);\n if (!connection) return Promise.resolve({ error: \"this computer was never connected to a workspace; sign in from a terminal first\" });\n const origin = new URL(connection.url).origin;\n // The hook this agent carries: its cli.js from npm, or this same file in the single executable.\n const [program, args] = hookSelfCommand([\"login\", origin, \"--no-install\"]);\n return new Promise((resolve) => {\n let output = \"\";\n let settled = false;\n const child = spawnImpl(program, args, {\n env: { ...process.env, SCOPEBOND_HOME: dir }, stdio: [\"ignore\", \"pipe\", \"pipe\"], windowsHide: true,\n });\n const timer = setTimeout(() => { if (!settled) { settled = true; resolve({ error: \"the workspace did not start a sign-in in time\" }); } }, 30_000);\n const read = (chunk: Buffer) => {\n output += chunk.toString(\"utf8\");\n const started = parseLoginPrompt(output);\n if (started && !settled) {\n settled = true; clearTimeout(timer);\n if (sameOrigin(started.verification_url, origin)) open(started.verification_url);\n resolve({ started, child });\n }\n };\n child.stdout?.on(\"data\", read);\n child.stderr?.on(\"data\", read);\n child.on(\"error\", (error) => { if (!settled) { settled = true; clearTimeout(timer); resolve({ error: error.message }); } });\n child.on(\"exit\", (code) => {\n clearTimeout(timer);\n if (!settled) { settled = true; resolve({ error: output.trim().split(\"\\n\").at(-1)?.slice(0, 200) || `the sign-in stopped (exit ${code})` }); }\n onDone(code === 0);\n });\n });\n}\n"]}
package/dist/self.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** The file autostart and a handover start: this package's cli.js, or the single executable itself. */
2
+ export declare function agentCliPath(): string;
3
+ /** The program and arguments that run this agent's command line with `args`, in either build. */
4
+ export declare function agentSelfCommand(args: readonly string[]): [string, string[]];
5
+ //# sourceMappingURL=self.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self.d.ts","sourceRoot":"","sources":["../src/self.ts"],"names":[],"mappings":"AAQA,uGAAuG;AACvG,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAE5E"}
package/dist/self.js ADDED
@@ -0,0 +1,15 @@
1
+ // Where this agent's own program is: Node running this package's cli.js when installed from npm, or the single executable
2
+ // (one signed file that is both the agent and the hook). Read when needed, never at load: in the single executable there
3
+ // is no cli.js to find.
4
+ import { realpathSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import { isSingleExecutable } from "@scopebond/hook";
7
+ /** The file autostart and a handover start: this package's cli.js, or the single executable itself. */
8
+ export function agentCliPath() {
9
+ return isSingleExecutable() ? process.execPath : realpathSync(fileURLToPath(new URL("./cli.js", import.meta.url)));
10
+ }
11
+ /** The program and arguments that run this agent's command line with `args`, in either build. */
12
+ export function agentSelfCommand(args) {
13
+ return isSingleExecutable() ? [process.execPath, [...args]] : [process.execPath, ["--disable-warning=ExperimentalWarning", agentCliPath(), ...args]];
14
+ }
15
+ //# sourceMappingURL=self.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self.js","sourceRoot":"","sources":["../src/self.ts"],"names":[],"mappings":"AAAA,0HAA0H;AAC1H,yHAAyH;AACzH,wBAAwB;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,uGAAuG;AACvG,MAAM,UAAU,YAAY;IAC1B,OAAO,kBAAkB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACrH,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,gBAAgB,CAAC,IAAuB;IACtD,OAAO,kBAAkB,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,uCAAuC,EAAE,YAAY,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;AACvJ,CAAC","sourcesContent":["// Where this agent's own program is: Node running this package's cli.js when installed from npm, or the single executable\n// (one signed file that is both the agent and the hook). Read when needed, never at load: in the single executable there\n// is no cli.js to find.\n\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isSingleExecutable } from \"@scopebond/hook\";\n\n/** The file autostart and a handover start: this package's cli.js, or the single executable itself. */\nexport function agentCliPath(): string {\n return isSingleExecutable() ? process.execPath : realpathSync(fileURLToPath(new URL(\"./cli.js\", import.meta.url)));\n}\n\n/** The program and arguments that run this agent's command line with `args`, in either build. */\nexport function agentSelfCommand(args: readonly string[]): [string, string[]] {\n return isSingleExecutable() ? [process.execPath, [...args]] : [process.execPath, [\"--disable-warning=ExperimentalWarning\", agentCliPath(), ...args]];\n}\n"]}
package/dist/service.d.ts CHANGED
@@ -1,9 +1,21 @@
1
- import { type Harness } from "@scopebond/hook";
1
+ import { runStoreUpkeep, type Harness } from "@scopebond/hook";
2
2
  import { type CycleResult } from "./agent.js";
3
3
  import { runSelfCheck } from "./selfcheck.js";
4
4
  import { type Prompter } from "./prompt.js";
5
5
  export declare const AGENT_VERSION: string;
6
6
  export declare const AFTER_PID_ENV = "SCOPEBOND_AGENT_AFTER_PID";
7
+ /** The person's tray settings (D143: notifications about problems by default, none about blocks). */
8
+ export declare const TRAY_SETTINGS_FILE = "agent-settings.json";
9
+ export type NotificationSetting = "all" | "problems" | "off";
10
+ export interface TraySettings {
11
+ notifications: NotificationSetting;
12
+ }
13
+ export declare function readTraySettings(dir: string): TraySettings;
14
+ export declare function writeTraySettings(dir: string, patch: unknown): TraySettings;
15
+ /** Set for a replacement started directly because the launcher is an older one: it rewrites the launcher once the old agent is gone. */
16
+ export declare const REFRESH_LAUNCHER_ENV = "SCOPEBOND_AGENT_REFRESH_LAUNCHER";
17
+ /** The exit code that asks a service manager to start the agent again (systemd's Restart=on-failure), used where a detached replacement would not survive. */
18
+ export declare const RESTART_EXIT_CODE = 75;
7
19
  export interface ServiceOptions {
8
20
  dir: string;
9
21
  intervalMs?: number;
@@ -36,6 +48,8 @@ export interface MaintenanceResult {
36
48
  reason: string;
37
49
  }>;
38
50
  selfCheck: Awaited<ReturnType<typeof runSelfCheck>>;
51
+ /** The local store's upkeep this pass (D144): rows rewritten, receipts past retention removed, space returned. */
52
+ store?: ReturnType<typeof runStoreUpkeep>;
39
53
  error: string | null;
40
54
  }
41
55
  /** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is
@@ -44,9 +58,39 @@ export declare function repairHookEntries(harnesses?: Harness[]): Array<{
44
58
  harness: Harness;
45
59
  file: string;
46
60
  }>;
47
- /** Start a fresh agent (the updated one) that waits for this process to exit, through the
48
- * autostart launcher when there is one so it finds Node the same way sign-in does. */
49
- export declare function spawnReplacement(dir: string): void;
61
+ /** How the updated agent takes over from this one. */
62
+ export type Handover = {
63
+ kind: "spawn";
64
+ command: string;
65
+ args: string[];
66
+ env: Record<string, string | undefined>;
67
+ verbatim: boolean;
68
+ } | {
69
+ kind: "service-restart";
70
+ };
71
+ /** Decide how to start the updated agent. Through the autostart launcher when there is one, so it finds Node the same way
72
+ * sign-in does and is restarted after a crash. On Windows an older launcher cannot do it: it redirects the agent's output
73
+ * into agent.log, this agent's children inherit that handle, and the new launcher's own redirect then fails, which cmd
74
+ * takes for a clean exit. So under an older launcher the updated agent starts directly and rewrites the launcher once
75
+ * this one is gone. Under systemd a detached child is stopped with the service, so the service manager restarts it. */
76
+ export declare function handoverPlan(o: {
77
+ dir: string;
78
+ cli: string;
79
+ pid: number;
80
+ execPath: string;
81
+ platform: NodeJS.Platform;
82
+ /** The single executable: the program is the agent itself, with no cli.js and no Node options. */
83
+ singleExecutable?: boolean;
84
+ env: Record<string, string | undefined>;
85
+ launcherText: string | null;
86
+ }): Handover;
87
+ /** Start the updated agent, which waits for this process to exit. Returns false when a service manager restarts it instead
88
+ * (this process then exits with RESTART_EXIT_CODE). */
89
+ export declare function spawnReplacement(dir: string, cli?: string): boolean;
90
+ /** The first thing a starting agent does: wait for the agent it replaces to exit, then, when asked, rewrite the older launcher
91
+ * that agent ran under (by now nothing runs it). The handover variables are dropped, so a later restart waits for nothing. */
92
+ export declare function takeOver(dir: string, cli: string, waitMs?: number): Promise<void>;
93
+ export declare function waitForExit(pid: number, timeoutMs: number): Promise<void>;
50
94
  export declare const AGENT_LOCK = "agent.lock";
51
95
  /** How long a lock whose agent never answered on its local channel still counts as an agent starting up. */
52
96
  export declare const AGENT_LOCK_STARTING_MS = 60000;
@@ -59,4 +103,6 @@ export declare const AGENT_LOCK_STARTING_MS = 60000;
59
103
  export declare function acquireAgentLock(dir: string, now?: number): string | null;
60
104
  export declare function releaseAgentLock(file: string): void;
61
105
  export declare function startService(options: ServiceOptions): Promise<Service>;
106
+ /** What the tray says after a person acted on an earlier block. */
107
+ export declare function blockedText(outcome: "allowed" | "proposed" | "asked" | "declined" | "gone"): string;
62
108
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AASA,OAAO,EAAoE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACjH,OAAO,EAAmE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAG/G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAA6C,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAKvF,eAAO,MAAM,aAAa,QAA4B,CAAC;AAKvD,eAAO,MAAM,aAAa,8BAA8B,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzF,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yFAAyF;IACzF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,wGAAwG;IACxG,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IACjC,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;IAC3C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;IACpD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;sFACsF;AACtF,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,EAAwB,GAAG,KAAK,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAOvH;AAED;uFACuF;AACvF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CASlD;AAED,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC,4GAA4G;AAC5G,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;;mEAKmE;AACnE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,GAAG,IAAI,CAmB7E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAuI5E"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}