@scopebond/agent 0.4.6 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -6
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +4 -4
- package/dist/agent.js.map +1 -1
- package/dist/allowance-sender.d.ts +8 -0
- package/dist/allowance-sender.d.ts.map +1 -0
- package/dist/allowance-sender.js +80 -0
- package/dist/allowance-sender.js.map +1 -0
- package/dist/autostart.d.ts +13 -0
- package/dist/autostart.d.ts.map +1 -1
- package/dist/autostart.js +60 -10
- package/dist/autostart.js.map +1 -1
- package/dist/cli-main.d.ts +3 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +218 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/cli.js +4 -185
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/ipc.d.ts +7 -1
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +64 -12
- package/dist/ipc.js.map +1 -1
- package/dist/log-file.d.ts +6 -0
- package/dist/log-file.d.ts.map +1 -0
- package/dist/log-file.js +40 -0
- package/dist/log-file.js.map +1 -0
- package/dist/native-update.d.ts +51 -0
- package/dist/native-update.d.ts.map +1 -0
- package/dist/native-update.js +120 -0
- package/dist/native-update.js.map +1 -0
- package/dist/prompt.d.ts +11 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +57 -23
- package/dist/prompt.js.map +1 -1
- package/dist/reconnect.d.ts +19 -0
- package/dist/reconnect.d.ts.map +1 -0
- package/dist/reconnect.js +60 -0
- package/dist/reconnect.js.map +1 -0
- package/dist/self.d.ts +5 -0
- package/dist/self.d.ts.map +1 -0
- package/dist/self.js +15 -0
- package/dist/self.js.map +1 -0
- package/dist/service.d.ts +50 -4
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +292 -18
- package/dist/service.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +4 -9
- package/dist/setup.js.map +1 -1
- package/dist/summary.d.ts +16 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +47 -0
- package/dist/summary.js.map +1 -0
- package/dist/tray-model.d.ts +91 -0
- package/dist/tray-model.d.ts.map +1 -0
- package/dist/tray-model.js +159 -0
- package/dist/tray-model.js.map +1 -0
- package/dist/tray.d.ts.map +1 -1
- package/dist/tray.js +103 -29
- package/dist/tray.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +7 -4
- package/dist/update.js.map +1 -1
- 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
|
-
|
|
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(
|
|
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,
|
|
75
|
+
$l.SetBounds(12, 10, 616, 130)
|
|
65
76
|
$r = New-Object Windows.Forms.TextBox
|
|
66
|
-
$r.SetBounds(12, 145,
|
|
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
|
-
$
|
|
77
|
-
$
|
|
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
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)}`,
|
|
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 {
|
|
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) {
|
package/dist/prompt.js.map
CHANGED
|
@@ -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
|
package/dist/self.js.map
ADDED
|
@@ -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
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
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
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AASA,OAAO,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|