toolroll 0.9.65 → 0.9.68
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/CHANGELOG.md +93 -0
- package/README.md +20 -2
- package/dist/acp-client.js +1 -3
- package/dist/agent-board.d.ts +0 -1
- package/dist/agent-board.js +0 -1
- package/dist/agent-fence.d.ts +69 -2
- package/dist/agent-fence.js +294 -11
- package/dist/agentconfig.js +4 -1
- package/dist/batch-checks.js +3 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
- package/dist/browser/terminal.js +134 -23
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -29
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +56 -37
- package/dist/canonical-json.d.ts +14 -0
- package/dist/canonical-json.js +36 -0
- package/dist/chat-actions.js +1 -2
- package/dist/chat-admission.d.ts +19 -0
- package/dist/chat-admission.js +22 -0
- package/dist/chat-channel.js +1 -1
- package/dist/chat-delivery.js +0 -1
- package/dist/chat-follow.d.ts +17 -0
- package/dist/chat-follow.js +32 -0
- package/dist/chat-messages.js +0 -1
- package/dist/chat-settings-page.d.ts +23 -0
- package/dist/chat-settings-page.js +41 -0
- package/dist/claim.js +3 -6
- package/dist/claude-print.d.ts +12 -0
- package/dist/claude-print.js +34 -0
- package/dist/cli-http.d.ts +0 -7
- package/dist/cli-http.js +0 -21
- package/dist/cli.d.ts +3 -10
- package/dist/cli.js +15 -24
- package/dist/coding-provider.js +2 -1
- package/dist/coding-workspace.js +1 -1
- package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
- package/dist/connect-cli.js +331 -0
- package/dist/containment.d.ts +0 -2
- package/dist/containment.js +2 -6
- package/dist/contracts/chat-actions.d.ts +0 -1
- package/dist/contracts/chat-content.d.ts +0 -4
- package/dist/contracts/chat-content.js +0 -2
- package/dist/contracts/chat-tables.d.ts +0 -1
- package/dist/contracts/cli.d.ts +0 -62
- package/dist/contracts/cli.js +0 -4
- package/dist/contracts/coding-workspace.js +1 -1
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/contract.js +1 -1
- package/dist/contracts/flow-card.d.ts +0 -1
- package/dist/contracts/flow-propose.d.ts +0 -1
- package/dist/contracts/flow-send.js +1 -1
- package/dist/contracts/gateway-tools.d.ts +0 -1
- package/dist/contracts/integration-metadata.js +1 -1
- package/dist/contracts/lead-tools.d.ts +0 -1
- package/dist/contracts/memory-pass.d.ts +0 -1
- package/dist/contracts/memory-pass.js +1 -1
- package/dist/contracts/plan.js +1 -1
- package/dist/contracts/project-knowledge.js +1 -1
- package/dist/contracts/project-skills.d.ts +0 -1
- package/dist/contracts/proof.js +1 -1
- package/dist/contracts/recipes.js +1 -1
- package/dist/contracts/scope.js +1 -1
- package/dist/contracts/scout-report.js +1 -1
- package/dist/contracts/teams-callback.d.ts +0 -1
- package/dist/converse.d.ts +0 -6
- package/dist/converse.js +0 -19
- package/dist/daemon.js +3 -2
- package/dist/decision.d.ts +1 -3
- package/dist/deprecations.d.ts +12 -4
- package/dist/deprecations.js +27 -4
- package/dist/desktop-update-recovery.js +1 -1
- package/dist/desktop-update.js +3 -2
- package/dist/discord-chat.js +4 -15
- package/dist/discord-settings.d.ts +2 -6
- package/dist/discord-settings.js +10 -38
- package/dist/discord.js +12 -23
- package/dist/evidence.d.ts +0 -1
- package/dist/evidence.js +0 -3
- package/dist/exec.d.ts +0 -9
- package/dist/exec.js +3 -81
- package/dist/flow-actions.js +0 -3
- package/dist/flow-draft.d.ts +1 -3
- package/dist/flow-draft.js +15 -28
- package/dist/flow-scripts.d.ts +1 -8
- package/dist/flow-scripts.js +0 -13
- package/dist/flow-send.js +1 -1
- package/dist/flow-share.d.ts +0 -2
- package/dist/flow-share.js +1 -6
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +0 -1
- package/dist/flow-triggers.js +1 -2
- package/dist/flow-unattended.js +1 -12
- package/dist/flows.js +1 -1
- package/dist/health-cli.d.ts +2 -2
- package/dist/health-cli.js +1 -1
- package/dist/invoke.d.ts +18 -3
- package/dist/invoke.js +142 -17
- package/dist/is-record.d.ts +2 -0
- package/dist/is-record.js +2 -0
- package/dist/lead-doors.d.ts +1 -5
- package/dist/lead-doors.js +0 -4
- package/dist/lead-tools.js +3 -6
- package/dist/live-detail.d.ts +0 -1
- package/dist/live-detail.js +0 -6
- package/dist/live-terminal.d.ts +24 -13
- package/dist/live-terminal.js +81 -43
- package/dist/live.d.ts +0 -1
- package/dist/live.js +1 -2
- package/dist/mcp-core.js +0 -2
- package/dist/memory-pass.js +1 -1
- package/dist/model-catalog.js +1 -1
- package/dist/modes.js +1 -12
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +436 -745
- package/dist/password-bearer.d.ts +0 -2
- package/dist/password-bearer.js +0 -4
- package/dist/peek-cli.js +2 -1
- package/dist/peek.d.ts +2 -26
- package/dist/peek.js +2 -168
- package/dist/phase-routing.d.ts +0 -4
- package/dist/phase-routing.js +2 -21
- package/dist/planner-source.d.ts +2 -1
- package/dist/planner-source.js +2 -12
- package/dist/planner.js +2 -14
- package/dist/process-tree.d.ts +0 -2
- package/dist/process-tree.js +0 -10
- package/dist/project-delete-ui.d.ts +6 -0
- package/dist/project-delete-ui.js +20 -1
- package/dist/project-delete.d.ts +0 -2
- package/dist/project-delete.js +1 -3
- package/dist/project-learning.d.ts +0 -14
- package/dist/project-skills.d.ts +0 -1
- package/dist/project-skills.js +0 -14
- package/dist/project-tools.js +2 -10
- package/dist/provider.d.ts +0 -17
- package/dist/provider.js +0 -26
- package/dist/pulls.d.ts +0 -2
- package/dist/pulls.js +0 -9
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +81 -4
- package/dist/release.d.ts +7 -1
- package/dist/release.js +20 -7
- package/dist/remote-exec.d.ts +3 -2
- package/dist/remote-exec.js +3 -2
- package/dist/repos.d.ts +0 -5
- package/dist/repos.js +2 -10
- package/dist/repository-context.d.ts +0 -2
- package/dist/repository-context.js +0 -4
- package/dist/restart-certification.d.ts +0 -2
- package/dist/restart-certification.js +1 -5
- package/dist/result-follow-ups.d.ts +1 -4
- package/dist/result-follow-ups.js +4 -8
- package/dist/result-review.d.ts +0 -2
- package/dist/result-review.js +0 -2
- package/dist/retention.d.ts +0 -1
- package/dist/retention.js +0 -1
- package/dist/review-context.d.ts +0 -1
- package/dist/review-context.js +1 -12
- package/dist/reviewer.d.ts +0 -16
- package/dist/reviewer.js +0 -18
- package/dist/run-closeout.js +2 -1
- package/dist/scope.d.ts +13 -0
- package/dist/scope.js +11 -18
- package/dist/scout-browser.js +2 -1
- package/dist/serve.js +3 -2
- package/dist/server/chrome.d.ts +10 -1
- package/dist/server/chrome.js +45 -4
- package/dist/server/console.css +60 -137
- package/dist/server/flows.js +2 -2
- package/dist/server/http.d.ts +2 -0
- package/dist/server/http.js +5 -0
- package/dist/server/pages.js +2 -2
- package/dist/server/people-tokens.js +3 -4
- package/dist/server/remote.js +1 -6
- package/dist/server/render-chat.d.ts +9 -2
- package/dist/server/render-chat.js +53 -7
- package/dist/server/render-pages.d.ts +28 -3
- package/dist/server/render-pages.js +78 -9
- package/dist/server/render-results.d.ts +0 -6
- package/dist/server/render-results.js +3 -9
- package/dist/server/render-tasks.js +6 -6
- package/dist/server/route-table.d.ts +0 -4
- package/dist/server/route-table.js +1 -6
- package/dist/server/settings.js +43 -22
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +10 -7
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills.d.ts +0 -6
- package/dist/skills.js +0 -8
- package/dist/slack-chat.d.ts +1 -7
- package/dist/slack-chat.js +5 -18
- package/dist/slack-settings.d.ts +2 -6
- package/dist/slack-settings.js +10 -38
- package/dist/slack.js +12 -23
- package/dist/sleep.d.ts +2 -0
- package/dist/sleep.js +2 -0
- package/dist/store.d.ts +11 -74
- package/dist/store.js +36 -179
- package/dist/structured-output.d.ts +0 -1
- package/dist/structured-output.js +0 -1
- package/dist/subagent-work.d.ts +0 -2
- package/dist/subagent-work.js +0 -2
- package/dist/subagents.d.ts +1 -3
- package/dist/subagents.js +20 -33
- package/dist/subscription-chat.d.ts +1 -3
- package/dist/subscription-chat.js +2 -3
- package/dist/task-sizing.d.ts +1 -4
- package/dist/task-sizing.js +3 -10
- package/dist/teams-chat.js +4 -11
- package/dist/teams.js +12 -19
- package/dist/teardown-git.d.ts +22 -0
- package/dist/teardown-git.js +136 -2
- package/dist/telegram-lead.js +2 -2
- package/dist/telegram.js +1 -1
- package/dist/token-notices.d.ts +0 -11
- package/dist/token-notices.js +1 -5
- package/dist/tool-broker.d.ts +12 -0
- package/dist/tool-broker.js +66 -2
- package/dist/tool-secret-store.d.ts +0 -3
- package/dist/tool-secret-store.js +2 -11
- package/dist/toolroll-update.d.ts +0 -2
- package/dist/toolroll-update.js +2 -2
- package/dist/unfenced-test-support.d.ts +2 -0
- package/dist/unfenced-test-support.js +11 -0
- package/dist/verification-evidence.d.ts +0 -1
- package/dist/verification-evidence.js +0 -1
- package/dist/webhooks.d.ts +0 -1
- package/dist/webhooks.js +0 -1
- package/dist/workspace-ui.d.ts +0 -8
- package/dist/workspace-ui.js +1 -6
- package/dist/writes-mode.d.ts +9 -0
- package/dist/writes-mode.js +8 -0
- package/package.json +5 -1
- package/dist/contracts/native-census.d.ts +0 -42
- package/dist/contracts/native-census.js +0 -36
- package/dist/process-recovery-anchor.d.ts +0 -75
- package/dist/process-recovery-anchor.js +0 -188
- package/dist/process-recovery-coalition.d.ts +0 -25
- package/dist/process-recovery-coalition.js +0 -105
- package/dist/process-recovery-eligibility.d.ts +0 -179
- package/dist/process-recovery-eligibility.js +0 -174
- package/dist/process-recovery-native.d.ts +0 -161
- package/dist/process-recovery-native.js +0 -595
- package/dist/process-recovery-provenance.d.ts +0 -163
- package/dist/process-recovery-provenance.js +0 -292
- package/dist/process-recovery-services.d.ts +0 -56
- package/dist/process-recovery-services.js +0 -191
- package/dist/process-recovery-settlement.d.ts +0 -32
- package/dist/process-recovery-settlement.js +0 -133
- package/dist/process-recovery.d.ts +0 -41
- package/dist/process-recovery.js +0 -91
- package/dist/session-brief.d.ts +0 -8
- package/dist/session-brief.js +0 -24
- package/dist/session-cli.d.ts +0 -42
- package/dist/session-cli.js +0 -271
- package/dist/session-contract.d.ts +0 -183
- package/dist/session-contract.js +0 -122
- package/dist/session-remote.d.ts +0 -19
- package/dist/session-remote.js +0 -86
- package/dist/session-service.d.ts +0 -22
- package/dist/session-service.js +0 -157
- package/dist/team-cli.js +0 -692
package/dist/cli-http.js
CHANGED
|
@@ -123,8 +123,6 @@ export async function handleCliHttp(request, response, options) {
|
|
|
123
123
|
const parsed = cliRequest(value);
|
|
124
124
|
if ('problem' in parsed)
|
|
125
125
|
return reject(parsed.tooLarge ? 413 : 400, 'invalid-request', parsed.problem);
|
|
126
|
-
if (parsed.argv[0] === 'session')
|
|
127
|
-
return runSession(response, options, principal, parsed);
|
|
128
126
|
const row = (options.modeOf ?? contractRow)(parsed.argv);
|
|
129
127
|
if (row?.mode === 'step-up')
|
|
130
128
|
return reject(403, 'step-up', STEP_UP_MESSAGE);
|
|
@@ -161,22 +159,3 @@ export async function handleCliHttp(request, response, options) {
|
|
|
161
159
|
send(response, 200, { exitCode, stdout: out.join(''), stderr: '' });
|
|
162
160
|
return true;
|
|
163
161
|
}
|
|
164
|
-
/** `session …`: the prompt is the request's one file; the session owner answers one session envelope on stdout. */
|
|
165
|
-
async function runSession(response, options, principal, parsed) {
|
|
166
|
-
const refuse = (status, code, message) => { send(response, status, { ok: false, code, message }); return true; };
|
|
167
|
-
if (options.session === undefined)
|
|
168
|
-
return refuse(501, 'remote-unavailable', 'This server cannot run session commands. Update it, or use the console.');
|
|
169
|
-
const at = parsed.argv.indexOf('--file'), file = at < 0 ? undefined : parsed.argv[at + 1];
|
|
170
|
-
if (Object.keys(parsed.files).some(path => path !== file))
|
|
171
|
-
return refuse(400, 'invalid-request', 'Each file must name a declared input file argument.');
|
|
172
|
-
const out = [];
|
|
173
|
-
let exitCode;
|
|
174
|
-
try {
|
|
175
|
-
exitCode = await options.session(principal, parsed.argv, parsed.files, line => { out.push(`${line}\n`); });
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
return refuse(500, 'command-failed', 'The command stopped unexpectedly on the server. Check its state before running it again.');
|
|
179
|
-
}
|
|
180
|
-
send(response, 200, { exitCode, stdout: out.join(''), stderr: '' });
|
|
181
|
-
return true;
|
|
182
|
-
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { previewGithubRepo, cloneGithubRepo } from "./onboard.js";
|
|
14
14
|
import { type QuickStartSeams } from "./quick-start.js";
|
|
15
|
-
import { type
|
|
16
|
-
import { type TeamCliOptions } from "./team-cli.js";
|
|
15
|
+
import { type ConnectOptions } from "./connect-cli.js";
|
|
17
16
|
import { type RemoteExecOptions } from "./remote-exec.js";
|
|
18
17
|
import type { UpdateCliDeps } from "./toolroll-update-cli.js";
|
|
19
18
|
import type { ReleaseCliDeps } from "./release-cli.js";
|
|
@@ -47,19 +46,13 @@ type Write = (line: string) => void;
|
|
|
47
46
|
export { HELP };
|
|
48
47
|
export declare function parseArgs(argv: readonly string[]): ParseResult;
|
|
49
48
|
/** The commands that operate the queue rather than report on the world. */
|
|
50
|
-
/** Every verb the CLI routes OUTSIDE runOperate. Exported (with
|
|
51
|
-
* OPERATE_COMMANDS) so the declared command guide's drift tests compare
|
|
52
|
-
* exact sets instead of regex-reading source. "" is the no-verb report,
|
|
53
|
-
* which answers as `scan`. */
|
|
54
|
-
export declare const TOP_LEVEL_COMMANDS: readonly string[];
|
|
55
49
|
export declare const OPERATE_COMMANDS: Set<string>;
|
|
56
50
|
/** binSource exists so tests can exercise linking without depending on a build. */
|
|
57
51
|
export type MainOptions = {
|
|
58
52
|
binSource?: string;
|
|
59
53
|
operate?: OperateOptions;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/** Injected by tests: the remote CLI's profile, fetch and output streams (defaults to `team`'s profile seams). */
|
|
54
|
+
connect?: ConnectOptions;
|
|
55
|
+
/** Injected by tests: the remote CLI's profile, fetch and output streams (defaults to `connect`'s profile seams). */
|
|
63
56
|
remote?: RemoteExecOptions;
|
|
64
57
|
/** Injected by tests: the gh-facing halves of `repos add-from-github` —
|
|
65
58
|
* the verb's parsing, gating, and enrollment are what CLI tests prove;
|
package/dist/cli.js
CHANGED
|
@@ -26,9 +26,8 @@ import { parseGithubRepo, previewGithubRepo, cloneGithubRepo, isLargeRepo } from
|
|
|
26
26
|
import { run as execRun } from "./exec.js";
|
|
27
27
|
import { isBareInvocation, runQuickStart } from "./quick-start.js";
|
|
28
28
|
import { COMMAND_GUIDE, SURFACE_NOTES, SURFACE_SCHEMA_VERSION } from "./surface.js";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { deprecationWarning } from "./deprecations.js";
|
|
29
|
+
import { hasCentralProfile, maybeRunConnectCommand } from "./connect-cli.js";
|
|
30
|
+
import { removedCommand, removedMessage } from "./deprecations.js";
|
|
32
31
|
import { maybeRunRemoteCommand } from "./remote-exec.js";
|
|
33
32
|
import { discover, inspectAll } from "./discover.js";
|
|
34
33
|
import { readPulls } from "./pulls.js";
|
|
@@ -107,16 +106,6 @@ export function parseArgs(argv) {
|
|
|
107
106
|
};
|
|
108
107
|
}
|
|
109
108
|
/** The commands that operate the queue rather than report on the world. */
|
|
110
|
-
/** Every verb the CLI routes OUTSIDE runOperate. Exported (with
|
|
111
|
-
* OPERATE_COMMANDS) so the declared command guide's drift tests compare
|
|
112
|
-
* exact sets instead of regex-reading source. "" is the no-verb report,
|
|
113
|
-
* which answers as `scan`. */
|
|
114
|
-
export const TOP_LEVEL_COMMANDS = [
|
|
115
|
-
"", "scan", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github",
|
|
116
|
-
"link", "unlink", "update", "release", "contract", "skills list", "skills get", "skills install", "demo",
|
|
117
|
-
...SESSION_CLI_ACTIONS.map(action => `session ${action}`),
|
|
118
|
-
...TEAM_CLI_ACTIONS,
|
|
119
|
-
];
|
|
120
109
|
export const OPERATE_COMMANDS = new Set([
|
|
121
110
|
"up",
|
|
122
111
|
// Only `lead token` reaches here; the central service's lead verbs answer first.
|
|
@@ -310,21 +299,29 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
310
299
|
// hidden from help and the contract, before anything remote or local opens, changing nothing.
|
|
311
300
|
if (argv[0] === "routine")
|
|
312
301
|
return routinesMoved(argv, write);
|
|
302
|
+
// D5: the central team CLI and native sessions are gone; each old invocation says so and changes nothing.
|
|
303
|
+
const connectOptions = mainOptions.connect ?? mainOptions.remote;
|
|
304
|
+
const removed = removedCommand(argv, () => hasCentralProfile(connectOptions ?? {}));
|
|
305
|
+
if (removed !== null) {
|
|
306
|
+
const message = removedMessage(removed.named, removed.kind);
|
|
307
|
+
write(argv.includes("--json") ? envelopeJson({ ok: false, command: removed.named, reason: "removed", message }) : message);
|
|
308
|
+
return 2;
|
|
309
|
+
}
|
|
313
310
|
// A saved API token sends commands marked remote to the central server, before anything local opens.
|
|
314
311
|
// Command families below load when their command runs, so one command never pays for the others.
|
|
315
312
|
if (argv[0] === 'health')
|
|
316
|
-
return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.
|
|
313
|
+
return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.connect ?? mainOptions.remote);
|
|
317
314
|
// This computer's own service, read here and never on a central server.
|
|
318
315
|
if (argv[0] === 'doctor')
|
|
319
316
|
return (await import("./doctor.js")).runDoctorCommand(argv.slice(1), write);
|
|
320
|
-
const central = await maybeRunRemoteCommand(argv, mainOptions.remote ?? mainOptions.
|
|
317
|
+
const central = await maybeRunRemoteCommand(argv, mainOptions.remote ?? mainOptions.connect);
|
|
321
318
|
if (typeof central === "number")
|
|
322
319
|
return central;
|
|
323
320
|
if (central !== null)
|
|
324
321
|
argv = central.local;
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
return
|
|
322
|
+
const connected = await maybeRunConnectCommand(argv, write, mainOptions.connect);
|
|
323
|
+
if (connected !== null)
|
|
324
|
+
return connected;
|
|
328
325
|
if ((argv[0] === "chat" || argv[0] === "brief") && argv.includes("--local"))
|
|
329
326
|
argv = argv.filter(arg => arg !== "--local");
|
|
330
327
|
const [first, ...rest] = argv;
|
|
@@ -345,12 +342,6 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
345
342
|
}
|
|
346
343
|
if (first === "contract")
|
|
347
344
|
return runContractCommand(rest, write);
|
|
348
|
-
if (first === "session") {
|
|
349
|
-
// D5: native coding sessions are deprecated. They still run this release; each use says so once, on stderr.
|
|
350
|
-
(mainOptions.session?.stderr ?? (line => process.stderr.write(`${line}\n`)))(deprecationWarning(`session ${rest.find(arg => !arg.startsWith("-")) ?? ""}`.trim(), "session"));
|
|
351
|
-
// D7: it runs on the saved connection over /api/cli, signed in with that profile's API token.
|
|
352
|
-
return runSessionCommand(rest, write, { ...(mainOptions.remote ?? mainOptions.team), ...mainOptions.session });
|
|
353
|
-
}
|
|
354
345
|
if (first === "demo")
|
|
355
346
|
return runDemoCommand(rest, write);
|
|
356
347
|
if (first === "skills")
|
package/dist/coding-provider.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createContainer, currentContainment } from "./containment.js";
|
|
|
6
6
|
import { processMayBeAlive } from "./process-liveness.js";
|
|
7
7
|
import { observeProcessTree, sampleProcessTree, stopProcessTree } from "./process-tree.js";
|
|
8
8
|
import { isOwnEnv } from "./names.js";
|
|
9
|
+
import { sleep } from "./sleep.js";
|
|
9
10
|
/** A response from the server, distinct from an ambiguous transport failure. */
|
|
10
11
|
export class CodingProviderRequestError extends Error {
|
|
11
12
|
code;
|
|
@@ -374,7 +375,7 @@ export function createCodexCodingProvider(options = {}) {
|
|
|
374
375
|
const absent = () => (owned.pid === undefined || !processMayBeAlive(owned.pid, process.platform !== "win32")) && [...descendants.values()].every(({ pid, group }) => !processMayBeAlive(pid, group));
|
|
375
376
|
const until = Date.now() + EXIT_GRACE_MS;
|
|
376
377
|
while (!ancestryUnknown && !absent() && Date.now() < until)
|
|
377
|
-
await
|
|
378
|
+
await sleep(25);
|
|
378
379
|
clean = clean && !ancestryUnknown && absent();
|
|
379
380
|
}
|
|
380
381
|
if (!clean)
|
package/dist/coding-workspace.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdirSync, chmodSync, realpathSync } from 'node:fs';
|
|
3
|
-
import { dirname, join
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
4
|
import { hostname } from 'node:os';
|
|
5
5
|
import { provenDeadByBootChange } from './boot-identity.js';
|
|
6
6
|
import { containerEmptiness } from './container-state.js';
|
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type ConnectOptions = {
|
|
2
2
|
fetch?: typeof fetch;
|
|
3
3
|
env?: NodeJS.ProcessEnv;
|
|
4
4
|
home?: string;
|
|
5
5
|
profileFile?: string;
|
|
6
6
|
readStdin?: () => Promise<string>;
|
|
7
|
-
readLine?: (prompt: string) => Promise<string | null>;
|
|
8
|
-
isTTY?: boolean;
|
|
9
7
|
stderr?: (line: string) => void;
|
|
10
8
|
signal?: AbortSignal;
|
|
11
|
-
sleep?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
|
|
12
9
|
};
|
|
13
|
-
/** D5: deprecated (deprecations.ts). Each still runs this release, hidden from help and the contract, with one warning on stderr. */
|
|
14
|
-
export declare const TEAM_CLI_ACTIONS: readonly ['connect', 'lead list', 'lead create', 'lead update', 'lead member', 'lead transfer', 'conversation list', 'conversation create', 'conversation show', 'conversation member', 'conversation edit', 'conversation withdraw', 'conversation read', 'conversation follow', 'conversation stop'];
|
|
15
10
|
export declare class UsageError extends Error {
|
|
16
11
|
}
|
|
17
12
|
/** Credentials are never sent to a URL with embedded authority, an API path, query, fragment or an insecure remote
|
|
@@ -26,13 +21,14 @@ type Profile = {
|
|
|
26
21
|
export declare const bearerOf: (profile: Profile) => string;
|
|
27
22
|
/** The saved central profile remote commands use: the named one (or the active one), and whether it holds an API token.
|
|
28
23
|
* null when nothing is saved. Reads only the private profile file — never a local login or the database. */
|
|
29
|
-
export declare function
|
|
24
|
+
export declare function hasCentralProfile(options: ConnectOptions): boolean;
|
|
25
|
+
export declare function centralProfile(options: ConnectOptions, name?: string): {
|
|
30
26
|
name: string;
|
|
31
27
|
origin: string;
|
|
32
28
|
account: string;
|
|
33
29
|
token: string;
|
|
34
30
|
apiToken: boolean;
|
|
35
31
|
} | null;
|
|
36
|
-
/**
|
|
37
|
-
export declare function
|
|
32
|
+
/** `toolroll connect …`; null for every other command. Nothing here opens a database. */
|
|
33
|
+
export declare function maybeRunConnectCommand(argv: readonly string[], write: (line: string) => void, options?: ConnectOptions): Promise<number | null>;
|
|
38
34
|
export {};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `toolroll connect`: saves a central server's origin and your API token in a private profile file. Remote commands
|
|
3
|
+
* (remote-exec.ts) and `toolroll health` sign in with it; nothing here opens the local database except to find the
|
|
4
|
+
* existing local sign-in for --local-login.
|
|
5
|
+
*/
|
|
6
|
+
import { constants, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { envelopeJson } from './envelope.js';
|
|
11
|
+
import { databasePath } from './store.js';
|
|
12
|
+
import { configBase, namedPath } from './names.js';
|
|
13
|
+
import { parseApiToken } from './api-tokens.js';
|
|
14
|
+
import { deprecationWarning } from './deprecations.js';
|
|
15
|
+
import { PASSWORD_PROFILE_WARNING } from './password-bearer.js';
|
|
16
|
+
const HELP = `toolroll connect <HTTPS-origin> --as <account> --token-stdin
|
|
17
|
+
Pipe your API token (so_…, from Settings → API tokens) or use --token-file <private-file>;
|
|
18
|
+
--local-login uses your existing local sign-in.
|
|
19
|
+
With an API token saved, other commands run on that server as you (--local runs here).
|
|
20
|
+
Optional: --profile <name> (saved as the active profile).`;
|
|
21
|
+
export class UsageError extends Error {
|
|
22
|
+
}
|
|
23
|
+
/** Credentials are never sent to a URL with embedded authority, an API path, query, fragment or an insecure remote
|
|
24
|
+
* host. Fetch cannot follow redirects. */
|
|
25
|
+
export function serviceOrigin(raw) {
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new UsageError('The service origin must be HTTPS or loopback HTTP.');
|
|
32
|
+
}
|
|
33
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== '/')
|
|
34
|
+
throw new UsageError('The service origin must contain only the origin, with no credentials, path, query or fragment.');
|
|
35
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname)))
|
|
36
|
+
throw new UsageError('The service origin requires HTTPS except for loopback HTTP.');
|
|
37
|
+
return url.origin;
|
|
38
|
+
}
|
|
39
|
+
const value = (flags, name) => typeof flags[name] === 'string' ? flags[name] : '';
|
|
40
|
+
const text = (raw) => raw.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
|
|
41
|
+
const record = (input) => !!input && typeof input === 'object' && !Array.isArray(input);
|
|
42
|
+
const strings = (input) => Array.isArray(input) && input.every(one => typeof one === 'string');
|
|
43
|
+
const accountValid = (input) => !!input && input.length <= 200 && !/[\s:\x00-\x1f\x7f]/.test(input);
|
|
44
|
+
const tokenValid = (input) => !!input && input.length <= 8192 && !/[\s\x00-\x1f\x7f]/.test(input);
|
|
45
|
+
const profilePath = (options) => options.profileFile ?? namedPath(configBase(options.env ?? process.env, options.home ?? homedir()), ['remote', 'profiles.json']);
|
|
46
|
+
/** Open the file itself without following a symlink; nonblocking also rejects FIFOs safely. */
|
|
47
|
+
function privateFile(path, limit) {
|
|
48
|
+
let fd;
|
|
49
|
+
try {
|
|
50
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
51
|
+
const stat = fstatSync(fd);
|
|
52
|
+
if (!stat.isFile() || (stat.mode & 0o777) !== 0o600 || (process.getuid && stat.uid !== process.getuid()) || stat.size > limit)
|
|
53
|
+
throw new Error();
|
|
54
|
+
const bytes = readFileSync(fd);
|
|
55
|
+
if (bytes.length > limit)
|
|
56
|
+
throw new Error();
|
|
57
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new UsageError('The credential file must be an owned, private (chmod 600), regular UTF-8 file.');
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (fd !== undefined)
|
|
64
|
+
closeSync(fd);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function profiles(options) {
|
|
68
|
+
const path = profilePath(options);
|
|
69
|
+
// lstat distinguishes a dangling symlink from a missing profile.
|
|
70
|
+
try {
|
|
71
|
+
lstatSync(path);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code === 'ENOENT')
|
|
75
|
+
return null;
|
|
76
|
+
throw new UsageError('The remote profile cannot be read.');
|
|
77
|
+
}
|
|
78
|
+
const raw = privateFile(path, 128 * 1024);
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (!record(parsed) || parsed.version !== 1 || typeof parsed.active !== 'string' || !record(parsed.profiles))
|
|
82
|
+
throw new Error();
|
|
83
|
+
for (const [name, one] of Object.entries(parsed.profiles)) {
|
|
84
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(name) || !record(one) || typeof one.origin !== 'string' || serviceOrigin(one.origin) !== one.origin || typeof one.account !== 'string' || !accountValid(one.account) || typeof one.token !== 'string' || !tokenValid(one.token))
|
|
85
|
+
throw new Error();
|
|
86
|
+
}
|
|
87
|
+
if (!Object.hasOwn(parsed.profiles, parsed.active))
|
|
88
|
+
throw new Error();
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new UsageError('The remote profile is invalid. Connect again using a private credential source.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function saveProfile(options, name, profile) {
|
|
96
|
+
const path = profilePath(options), directory = dirname(path);
|
|
97
|
+
const saved = profiles(options) ?? { version: 1, active: name, profiles: {} };
|
|
98
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
99
|
+
const parent = lstatSync(directory);
|
|
100
|
+
if (!parent.isDirectory() || (parent.mode & 0o777) !== 0o700 || (process.getuid && parent.uid !== process.getuid()))
|
|
101
|
+
throw new UsageError('The remote profile directory must be owned and private (chmod 700).');
|
|
102
|
+
saved.active = name;
|
|
103
|
+
Object.defineProperty(saved.profiles, name, { value: profile, enumerable: true, configurable: true, writable: true });
|
|
104
|
+
const temporary = join(directory, `.profile-${randomBytes(16).toString('hex')}`);
|
|
105
|
+
let fd;
|
|
106
|
+
try {
|
|
107
|
+
fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
108
|
+
writeFileSync(fd, `${JSON.stringify(saved)}\n`);
|
|
109
|
+
fsyncSync(fd);
|
|
110
|
+
closeSync(fd);
|
|
111
|
+
fd = undefined;
|
|
112
|
+
renameSync(temporary, path);
|
|
113
|
+
fd = openSync(directory, constants.O_RDONLY);
|
|
114
|
+
fsyncSync(fd);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
if (fd !== undefined)
|
|
118
|
+
closeSync(fd);
|
|
119
|
+
if (existsSync(temporary))
|
|
120
|
+
unlinkSync(temporary);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** An API token (so_…) is sent bare; an older saved sign-in keeps its account:secret form. */
|
|
124
|
+
export const bearerOf = (profile) => parseApiToken(profile.token) !== null ? `Bearer ${profile.token}` : `Bearer ${profile.account}:${profile.token}`;
|
|
125
|
+
/** The saved central profile remote commands use: the named one (or the active one), and whether it holds an API token.
|
|
126
|
+
* null when nothing is saved. Reads only the private profile file — never a local login or the database. */
|
|
127
|
+
export function hasCentralProfile(options) {
|
|
128
|
+
// An unreadable or invalid profile file still names a central service: the caller refuses rather than run locally.
|
|
129
|
+
try {
|
|
130
|
+
return profiles(options) !== null;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function centralProfile(options, name) {
|
|
137
|
+
const saved = profiles(options);
|
|
138
|
+
if (saved === null)
|
|
139
|
+
return null;
|
|
140
|
+
const chosen = name ?? saved.active;
|
|
141
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(chosen) || !Object.hasOwn(saved.profiles, chosen))
|
|
142
|
+
throw new UsageError(`No saved connection named ${chosen}. Use connect with your API token first.`);
|
|
143
|
+
const profile = saved.profiles[chosen];
|
|
144
|
+
return { name: chosen, ...profile, apiToken: parseApiToken(profile.token) !== null };
|
|
145
|
+
}
|
|
146
|
+
async function stdinSecret() {
|
|
147
|
+
if (process.stdin.isTTY)
|
|
148
|
+
throw new UsageError('Pipe your sign-in secret into --token-stdin, or use --token-file.');
|
|
149
|
+
const chunks = [];
|
|
150
|
+
let size = 0;
|
|
151
|
+
for await (const part of process.stdin) {
|
|
152
|
+
const chunk = Buffer.from(part);
|
|
153
|
+
size += chunk.length;
|
|
154
|
+
if (size > 8192)
|
|
155
|
+
throw new UsageError('The sign-in secret is too large.');
|
|
156
|
+
chunks.push(chunk);
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
throw new UsageError('The sign-in secret must be UTF-8.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function responseBody(response) {
|
|
166
|
+
if (!response.body)
|
|
167
|
+
throw new Error();
|
|
168
|
+
const reader = response.body.getReader(), chunks = [];
|
|
169
|
+
let size = 0;
|
|
170
|
+
try {
|
|
171
|
+
while (true) {
|
|
172
|
+
const chunk = await reader.read();
|
|
173
|
+
if (chunk.done)
|
|
174
|
+
break;
|
|
175
|
+
size += chunk.value.byteLength;
|
|
176
|
+
if (size > 2 * 1024 * 1024) {
|
|
177
|
+
await reader.cancel();
|
|
178
|
+
throw new Error();
|
|
179
|
+
}
|
|
180
|
+
chunks.push(chunk.value);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
reader.releaseLock();
|
|
185
|
+
}
|
|
186
|
+
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
|
|
187
|
+
}
|
|
188
|
+
function parse(argv) {
|
|
189
|
+
const allowed = new Set(['profile', 'json', 'help', 'as', 'token-stdin', 'token-file', 'local-login', 'login-file']);
|
|
190
|
+
const switches = new Set(['json', 'help', 'token-stdin', 'local-login']);
|
|
191
|
+
const flags = {}, positions = [];
|
|
192
|
+
for (let index = 1; index < argv.length; index++) {
|
|
193
|
+
const arg = argv[index];
|
|
194
|
+
if (!arg.startsWith('--')) {
|
|
195
|
+
positions.push(arg);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const name = arg.slice(2);
|
|
199
|
+
if (!allowed.has(name))
|
|
200
|
+
throw new UsageError('Unsupported option. Use --help for this command; secrets must come from stdin or a private file.');
|
|
201
|
+
if (Object.hasOwn(flags, name))
|
|
202
|
+
throw new UsageError(`Use --${name} only once.`);
|
|
203
|
+
if (switches.has(name))
|
|
204
|
+
flags[name] = true;
|
|
205
|
+
else {
|
|
206
|
+
const next = argv[++index];
|
|
207
|
+
if (!next || next.startsWith('--'))
|
|
208
|
+
throw new UsageError(`--${name} needs a value.`);
|
|
209
|
+
flags[name] = next;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (positions.length !== 1 && !flags.help)
|
|
213
|
+
throw new UsageError('Name one service origin.');
|
|
214
|
+
return { flags, positions };
|
|
215
|
+
}
|
|
216
|
+
function isMessage(one) {
|
|
217
|
+
return record(one) && Number.isSafeInteger(one.id) && typeof one.author === 'string' && ['operator', 'assistant'].includes(String(one.role)) && typeof one.text === 'string' && ['queued', 'running', 'answered', 'failed', 'cancelled', 'uncertain'].includes(String(one.status)) && Number.isSafeInteger(one.revision) && typeof one.createdAt === 'string' && (one.requestId === null || typeof one.requestId === 'string') && (one.turnId === null || Number.isSafeInteger(one.turnId)) && (one.error === null || typeof one.error === 'string');
|
|
218
|
+
}
|
|
219
|
+
/** The listing a central service answers `list` with; a reply without a well-formed one is no proof of a service. */
|
|
220
|
+
function isSnapshot(one) {
|
|
221
|
+
if (!record(one) || !Array.isArray(one.leads) || !one.leads.every(lead => record(lead) && typeof lead.id === 'string' && typeof lead.name === 'string') || !Array.isArray(one.conversations) || !one.conversations.every(conversation => record(conversation) && typeof conversation.id === 'string' && typeof conversation.leadId === 'string' && typeof conversation.title === 'string') || !(one.selected === null || record(one.selected) && typeof one.selected.id === 'string' && typeof one.selected.leadId === 'string' && typeof one.selected.title === 'string') || !Array.isArray(one.participants) || !one.participants.every(participant => record(participant) && typeof participant.account === 'string' && ['viewer', 'contributor', 'manager'].includes(String(participant.role)) && typeof participant.active === 'boolean') || !Array.isArray(one.messages) || !one.messages.every(isMessage) || typeof one.canManage !== 'boolean' || typeof one.canSend !== 'boolean' || !Number.isSafeInteger(one.cursor) || typeof one.truncated !== 'boolean' || !strings(one.projects) || !strings(one.accounts))
|
|
222
|
+
return false;
|
|
223
|
+
if (one.chatAuthorization !== undefined) {
|
|
224
|
+
const grant = one.chatAuthorization;
|
|
225
|
+
if (!record(grant) || typeof grant.enabled !== 'boolean' || !(grant.provider === null || typeof grant.provider === 'string') || !(grant.model === null || typeof grant.model === 'string') || !Number.isSafeInteger(grant.dailyTurns) || ![grant.weeklyCeilingUsd, grant.conversationCeilingUsd].every(amount => amount === null || typeof amount === 'number' && Number.isFinite(amount) && amount >= 0) || typeof grant.termsDigest !== 'string')
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (one.tasks !== undefined) {
|
|
229
|
+
const tasks = one.tasks;
|
|
230
|
+
if (!record(tasks) || !record(tasks.totals) || !['all', 'needs-you', 'running', 'completed'].every(key => Number.isSafeInteger(tasks.totals[key])) || !Array.isArray(tasks.items) || !tasks.items.every(task => record(task) && typeof task.rootId === 'string' && typeof task.activeTaskId === 'string' && typeof task.title === 'string' && record(task.status) && typeof task.status.label === 'string') || !(tasks.nextCursor === null || typeof tasks.nextCursor === 'string'))
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (one.proposals !== undefined && (!Array.isArray(one.proposals) || !one.proposals.every(proposal => record(proposal) && Number.isSafeInteger(proposal.id) && typeof proposal.title === 'string' && typeof proposal.state === 'string' && typeof proposal.href === 'string')))
|
|
234
|
+
return false;
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
/** The server must accept the credential before it is saved: one read of POST /api/team, never retried. A success
|
|
238
|
+
* proves itself with a well-formed listing, as the central `list` it is. */
|
|
239
|
+
async function verify(profile, options) {
|
|
240
|
+
const unconfirmed = { version: 1, ok: false, code: 'service-unavailable', message: 'The service response could not be read.' };
|
|
241
|
+
try {
|
|
242
|
+
const response = await (options.fetch ?? fetch)(`${profile.origin}/api/team`, { method: 'POST', redirect: 'manual', credentials: 'omit', signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000), headers: { authorization: bearerOf(profile), 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ operation: 'list', args: {} }) });
|
|
243
|
+
if (response.status >= 300 && response.status < 400) {
|
|
244
|
+
await response.body?.cancel();
|
|
245
|
+
return unconfirmed;
|
|
246
|
+
}
|
|
247
|
+
if (response.status === 429) {
|
|
248
|
+
await response.body?.cancel();
|
|
249
|
+
return { ...unconfirmed, code: 'rate-limited', message: 'Request limit reached. Try again shortly.' };
|
|
250
|
+
}
|
|
251
|
+
const payload = await responseBody(response);
|
|
252
|
+
if (!record(payload) || payload.version !== 1 || typeof payload.ok !== 'boolean' || typeof payload.code !== 'string' || typeof payload.message !== 'string' || payload.snapshot !== undefined && !isSnapshot(payload.snapshot) || payload.ok && !response.ok)
|
|
253
|
+
return unconfirmed;
|
|
254
|
+
if (payload.ok && payload.snapshot === undefined)
|
|
255
|
+
return unconfirmed;
|
|
256
|
+
return { version: 1, ok: payload.ok, code: payload.code, message: payload.message };
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return unconfirmed;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** `toolroll connect …`; null for every other command. Nothing here opens a database. */
|
|
263
|
+
export async function maybeRunConnectCommand(argv, write, options = {}) {
|
|
264
|
+
if (argv[0] !== 'connect')
|
|
265
|
+
return null;
|
|
266
|
+
const json = argv.includes('--json');
|
|
267
|
+
let secret = '';
|
|
268
|
+
const stderr = (line) => (options.stderr ?? (line => process.stderr.write(`${line}\n`)))(text(secret ? line.replaceAll(secret, '[redacted]') : line));
|
|
269
|
+
const emit = (reply) => {
|
|
270
|
+
// A server's words can echo the credential it was sent: every string it says is redacted before it is shown.
|
|
271
|
+
const safe = JSON.parse(JSON.stringify(reply, (_key, one) => typeof one === 'string' && secret ? one.replaceAll(secret, '[redacted]') : one));
|
|
272
|
+
write(json ? envelopeJson({ ...safe, command: 'team connect' }) : text(safe.message));
|
|
273
|
+
};
|
|
274
|
+
try {
|
|
275
|
+
// D5: `connect` belongs to the deprecated central team service; it still saves the profile remote commands use.
|
|
276
|
+
stderr(deprecationWarning('connect', 'team'));
|
|
277
|
+
if (argv.includes('--help')) {
|
|
278
|
+
write(HELP);
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
const { flags, positions } = parse(argv);
|
|
282
|
+
if (flags.help) {
|
|
283
|
+
write(HELP);
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
const profileName = value(flags, 'profile') || 'default';
|
|
287
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(profileName))
|
|
288
|
+
throw new UsageError('Profile names use 1–64 letters, digits, underscores or hyphens.');
|
|
289
|
+
let origin;
|
|
290
|
+
try {
|
|
291
|
+
origin = serviceOrigin(positions[0]);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
throw new UsageError('Use an HTTPS service origin (loopback HTTP is allowed), without credentials, paths, query or fragment.');
|
|
295
|
+
}
|
|
296
|
+
if (['token-stdin', 'token-file', 'local-login'].filter(name => flags[name]).length !== 1)
|
|
297
|
+
throw new UsageError('Choose one credential source: --token-stdin, --token-file or --local-login.');
|
|
298
|
+
if (flags['login-file'] && !flags['local-login'])
|
|
299
|
+
throw new UsageError('--login-file requires --local-login.');
|
|
300
|
+
let account = value(flags, 'as');
|
|
301
|
+
if (flags['local-login']) {
|
|
302
|
+
const login = privateFile(value(flags, 'login-file') || join(dirname(databasePath(options.env ?? process.env, options.home ?? homedir())), 'up-login.txt'), 8192).trim().split(/\s+/);
|
|
303
|
+
if (login.length !== 2 || account && account !== login[0])
|
|
304
|
+
throw new UsageError('The local sign-in does not match the requested account.');
|
|
305
|
+
account = login[0];
|
|
306
|
+
secret = login[1];
|
|
307
|
+
}
|
|
308
|
+
else
|
|
309
|
+
secret = (flags['token-file'] ? privateFile(value(flags, 'token-file'), 8192) : await (options.readStdin ?? stdinSecret)()).trim();
|
|
310
|
+
if (!accountValid(account) || !tokenValid(secret))
|
|
311
|
+
throw new UsageError('Provide an account name and a valid sign-in secret from your selected source.');
|
|
312
|
+
if (parseApiToken(secret) === null)
|
|
313
|
+
stderr(PASSWORD_PROFILE_WARNING);
|
|
314
|
+
const profile = { origin, account, token: secret }, checked = await verify(profile, options);
|
|
315
|
+
if (!checked.ok) {
|
|
316
|
+
emit(checked);
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
saveProfile(options, profileName, profile);
|
|
320
|
+
emit({ version: 1, ok: true, code: 'connected', message: `Connected to ${origin} as ${account}.`, result: { profile: profileName, origin, account } });
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const reply = { version: 1, ok: false, code: 'usage', message: error instanceof UsageError ? error.message : 'The connection could not be prepared. Check the private profile and credential files.' };
|
|
325
|
+
if (json)
|
|
326
|
+
emit(reply);
|
|
327
|
+
else
|
|
328
|
+
stderr(text(reply.message));
|
|
329
|
+
return 2;
|
|
330
|
+
}
|
|
331
|
+
}
|
package/dist/containment.d.ts
CHANGED
|
@@ -198,5 +198,3 @@ export declare function cgroup2Container(capability: ContainmentCapability, labe
|
|
|
198
198
|
* the CI windows job exercises it.
|
|
199
199
|
*/
|
|
200
200
|
export declare function jobObjectContainer(capability: ContainmentCapability, label: string): Container;
|
|
201
|
-
/** Where the Windows helper lives beside this module (dist or src). */
|
|
202
|
-
export declare function containmentModuleDir(): string;
|
package/dist/containment.js
CHANGED
|
@@ -36,9 +36,10 @@ import { randomBytes } from "node:crypto";
|
|
|
36
36
|
import { existsSync, mkdirSync, readFileSync, rmdirSync, writeFileSync } from "node:fs";
|
|
37
37
|
import { captureCgroupIdentity, cgroupEmptiness, containerEmptiness } from "./container-state.js";
|
|
38
38
|
import { createServer } from "node:net";
|
|
39
|
-
import {
|
|
39
|
+
import { join } from "node:path";
|
|
40
40
|
import { fileURLToPath } from "node:url";
|
|
41
41
|
import { envValue } from "./names.js";
|
|
42
|
+
import { sleep } from "./sleep.js";
|
|
42
43
|
export const CONTAINMENT_POLICIES = ["observed", "preferred", "required"];
|
|
43
44
|
/** The environment name the controller reads when no flag names a policy. */
|
|
44
45
|
export const CONTAINMENT_ENV = "TOOLROLL_CONTAINMENT";
|
|
@@ -278,7 +279,6 @@ export function createContainer(effective, label) {
|
|
|
278
279
|
return { refused: `native containment could not be established for this spawn (${error instanceof Error ? error.message : String(error)}) — nothing ran` };
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
282
282
|
/**
|
|
283
283
|
* The cgroup2 prelude: join, confirm on fd 3, close it, exec the target
|
|
284
284
|
* with argv exactly as given (positional parameters, never re-parsed).
|
|
@@ -562,7 +562,3 @@ export function jobObjectContainer(capability, label) {
|
|
|
562
562
|
},
|
|
563
563
|
};
|
|
564
564
|
}
|
|
565
|
-
/** Where the Windows helper lives beside this module (dist or src). */
|
|
566
|
-
export function containmentModuleDir() {
|
|
567
|
-
return dirname(fileURLToPath(import.meta.url));
|
|
568
|
-
}
|
|
@@ -267,7 +267,6 @@ export declare const chatActionRequestSchemas: {
|
|
|
267
267
|
};
|
|
268
268
|
export type ChatActionOperation = keyof typeof chatActionRequestSchemas;
|
|
269
269
|
export declare const CHAT_ACTION_OPERATIONS: [ChatActionOperation, ...ChatActionOperation[]];
|
|
270
|
-
export type ChatActionRequest<O extends ChatActionOperation = ChatActionOperation> = z.infer<(typeof chatActionRequestSchemas)[O]>;
|
|
271
270
|
/** The fields each action takes, from its schema. */
|
|
272
271
|
export declare const CHAT_ACTION_FIELDS: Record<ChatActionOperation, readonly string[]>;
|
|
273
272
|
/** One action's data, refused by path (`card: required`, `payload: unknown key 'cards' (did you mean card?)`). */
|
|
@@ -335,10 +335,6 @@ export declare const chatActionBodySchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
335
335
|
version: z.ZodLiteral<1>;
|
|
336
336
|
problem: z.ZodString;
|
|
337
337
|
}, z.core.$strict>]>;
|
|
338
|
-
/** A notice carries its parts, not a body. */
|
|
339
|
-
export declare const chatNoticeBodySchema: z.ZodObject<{
|
|
340
|
-
version: z.ZodLiteral<1>;
|
|
341
|
-
}, z.core.$strict>;
|
|
342
338
|
export type ChatPairBody = z.infer<typeof chatPairBodySchema>;
|
|
343
339
|
export type ChatMessageBody = z.infer<typeof chatMessageBodySchema>;
|
|
344
340
|
export type ChatActionBody = z.infer<typeof chatActionBodySchema>;
|
|
@@ -125,8 +125,6 @@ export const chatMessageBodySchema = versioned(CHAT_EVENT_VERSION, {
|
|
|
125
125
|
export const chatTokenBodySchema = versioned(CHAT_EVENT_VERSION, { token: z.string().regex(CHAT_BUTTON_TOKEN, { error: "must be a Toolroll button token" }) });
|
|
126
126
|
export const chatProblemBodySchema = versioned(CHAT_EVENT_VERSION, { problem: z.string().min(1) });
|
|
127
127
|
export const chatActionBodySchema = z.union([chatTokenBodySchema, chatProblemBodySchema]);
|
|
128
|
-
/** A notice carries its parts, not a body. */
|
|
129
|
-
export const chatNoticeBodySchema = versioned(CHAT_EVENT_VERSION, {});
|
|
130
128
|
export function savedChatEventBody(body) {
|
|
131
129
|
return JSON.stringify({ version: CHAT_EVENT_VERSION, ...body });
|
|
132
130
|
}
|
|
@@ -10,4 +10,3 @@ export declare const isChatProvider: (value: unknown) => value is ChatProvider;
|
|
|
10
10
|
export declare const CHAT_SCHEMA: string;
|
|
11
11
|
/** Every shared chat table, parents before children. */
|
|
12
12
|
export declare const CHAT_TABLES: readonly ["chat_binding", "chat_pair", "chat_event", "chat_part", "chat_action", "chat_progress", "chat_runtime", "chat_room", "chat_meta", "chat_flow_action", "chat_flow_prompt", "chat_flow_choice", "chat_flow_note", "chat_question_action", "chat_question_prompt", "chat_ask_action", "chat_message_ref", "chat_note_draft", "chat_digest"];
|
|
13
|
-
export type ChatTable = (typeof CHAT_TABLES)[number];
|