toolroll 0.9.67 → 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 +36 -0
- 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.js +3 -0
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +31 -29
- package/dist/builder.js +3 -26
- 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/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 +0 -3
- package/dist/invoke.js +5 -3
- 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.js +2 -1
- 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.js +383 -750
- 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.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 +3 -0
- package/dist/release-adapters.js +15 -5
- 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 +1 -6
- 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.js +1 -13
- package/dist/scout-browser.js +2 -1
- package/dist/serve.js +2 -2
- package/dist/server/console.css +5 -133
- 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-results.d.ts +0 -6
- package/dist/server/render-results.js +3 -9
- package/dist/server/render-tasks.js +4 -5
- package/dist/server/route-table.d.ts +0 -4
- package/dist/server/route-table.js +0 -6
- package/dist/server/settings.js +23 -23
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +3 -3
- 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 +0 -73
- package/dist/store.js +3 -177
- 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/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-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/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/package.json +1 -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
|
@@ -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];
|
package/dist/contracts/cli.d.ts
CHANGED
|
@@ -54,37 +54,6 @@ export declare const commandRowSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
54
54
|
meaning: z.ZodString;
|
|
55
55
|
}, z.core.$loose>>>>>;
|
|
56
56
|
notableReasons: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
57
|
-
inputSchema: z.ZodOptional<z.ZodCustom<{
|
|
58
|
-
type: 'object';
|
|
59
|
-
additionalProperties: false;
|
|
60
|
-
required: readonly string[];
|
|
61
|
-
properties: Record<string, {
|
|
62
|
-
type: 'string' | 'integer' | readonly ['string', 'null'];
|
|
63
|
-
minLength?: number;
|
|
64
|
-
maxLength?: number;
|
|
65
|
-
pattern?: string;
|
|
66
|
-
minimum?: number;
|
|
67
|
-
maximum?: number;
|
|
68
|
-
maxBytes?: number;
|
|
69
|
-
nonblank?: boolean;
|
|
70
|
-
const?: number;
|
|
71
|
-
}>;
|
|
72
|
-
}, {
|
|
73
|
-
type: 'object';
|
|
74
|
-
additionalProperties: false;
|
|
75
|
-
required: readonly string[];
|
|
76
|
-
properties: Record<string, {
|
|
77
|
-
type: 'string' | 'integer' | readonly ['string', 'null'];
|
|
78
|
-
minLength?: number;
|
|
79
|
-
maxLength?: number;
|
|
80
|
-
pattern?: string;
|
|
81
|
-
minimum?: number;
|
|
82
|
-
maximum?: number;
|
|
83
|
-
maxBytes?: number;
|
|
84
|
-
nonblank?: boolean;
|
|
85
|
-
const?: number;
|
|
86
|
-
}>;
|
|
87
|
-
}>>;
|
|
88
57
|
remote: z.ZodEnum<{
|
|
89
58
|
no: "no";
|
|
90
59
|
"step-up": "step-up";
|
|
@@ -134,37 +103,6 @@ export declare const COMMAND_OUTPUTS: {
|
|
|
134
103
|
meaning: z.ZodString;
|
|
135
104
|
}, z.core.$loose>>>>>;
|
|
136
105
|
notableReasons: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
137
|
-
inputSchema: z.ZodOptional<z.ZodCustom<{
|
|
138
|
-
type: 'object';
|
|
139
|
-
additionalProperties: false;
|
|
140
|
-
required: readonly string[];
|
|
141
|
-
properties: Record<string, {
|
|
142
|
-
type: 'string' | 'integer' | readonly ['string', 'null'];
|
|
143
|
-
minLength?: number;
|
|
144
|
-
maxLength?: number;
|
|
145
|
-
pattern?: string;
|
|
146
|
-
minimum?: number;
|
|
147
|
-
maximum?: number;
|
|
148
|
-
maxBytes?: number;
|
|
149
|
-
nonblank?: boolean;
|
|
150
|
-
const?: number;
|
|
151
|
-
}>;
|
|
152
|
-
}, {
|
|
153
|
-
type: 'object';
|
|
154
|
-
additionalProperties: false;
|
|
155
|
-
required: readonly string[];
|
|
156
|
-
properties: Record<string, {
|
|
157
|
-
type: 'string' | 'integer' | readonly ['string', 'null'];
|
|
158
|
-
minLength?: number;
|
|
159
|
-
maxLength?: number;
|
|
160
|
-
pattern?: string;
|
|
161
|
-
minimum?: number;
|
|
162
|
-
maximum?: number;
|
|
163
|
-
maxBytes?: number;
|
|
164
|
-
nonblank?: boolean;
|
|
165
|
-
const?: number;
|
|
166
|
-
}>;
|
|
167
|
-
}>>;
|
|
168
106
|
remote: z.ZodEnum<{
|
|
169
107
|
no: "no";
|
|
170
108
|
"step-up": "step-up";
|
package/dist/contracts/cli.js
CHANGED
|
@@ -38,8 +38,6 @@ function answers(command, shape) {
|
|
|
38
38
|
export const commandFlagSchema = z.looseObject({ name: str, takesValue: z.boolean(), meaning: str }).readonly();
|
|
39
39
|
/** The remote policy every command row states; see `remote` below. */
|
|
40
40
|
export const REMOTE_POLICIES = ["yes", "no", "step-up"];
|
|
41
|
-
/** A session operation's executable input schema, as session-contract.ts states it. */
|
|
42
|
-
const sessionInputSchema = z.custom(value => typeof value === "object" && value !== null && !Array.isArray(value));
|
|
43
41
|
export const commandRowSchema = z.looseObject({
|
|
44
42
|
/** What you type after `toolroll` (subcommands included). */
|
|
45
43
|
invocation: str,
|
|
@@ -62,8 +60,6 @@ export const commandRowSchema = z.looseObject({
|
|
|
62
60
|
positionals: z.array(z.looseObject({ name: str, required: z.boolean(), meaning: str }).readonly()).readonly().optional(),
|
|
63
61
|
flags: z.array(commandFlagSchema).readonly().optional(),
|
|
64
62
|
notableReasons: z.array(str).readonly().optional(),
|
|
65
|
-
/** Session schemas are executable input contracts, not a grant of authority. */
|
|
66
|
-
inputSchema: sessionInputSchema.optional(),
|
|
67
63
|
/** Whether a person signed in to a central server with their own API token may run it there (`runOperateAs`):
|
|
68
64
|
* yes — runs as that person, within their token's scope and project access.
|
|
69
65
|
* no — this machine's own infrastructure, credentials or files; never run for a remote caller.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { readVersioned, versioned } from "./contract.js";
|
|
8
8
|
import { CODING_CONTEXT_VERSION, codingContextSchema } from "./coding-context.js";
|
|
9
|
+
import { isRecord } from "../is-record.js";
|
|
9
10
|
export const CODING_STATUSES = ["starting", "ready", "working", "needs-input", "stopping", "interrupted", "failed", "uncertain", "closed"];
|
|
10
11
|
export const codingSessionSchema = z.strictObject({
|
|
11
12
|
id: z.string(),
|
|
@@ -33,7 +34,6 @@ export const codingSessionSchema = z.strictObject({
|
|
|
33
34
|
});
|
|
34
35
|
export const CODING_SESSION_VERSION = 1;
|
|
35
36
|
export const codingSessionRecordSchema = versioned(CODING_SESSION_VERSION, codingSessionSchema.shape);
|
|
36
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
37
|
/**
|
|
38
38
|
* Unversioned records were read as a cast, including partial fields, nulls and unknown nested keys. Preserve that
|
|
39
39
|
* boundary exactly; only records explicitly written as version 1 are subject to its strict schema.
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { limitRule, TEXT_LIMITS } from "../text-limits.js";
|
|
11
|
+
import { isRecord } from "../is-record.js";
|
|
11
12
|
/**
|
|
12
13
|
* A string bounded by its TEXT_LIMITS entry, never a literal. The limit rule rides along as the field's description,
|
|
13
14
|
* so a model reads the bound before it writes. Zod counts UTF-16 code units; for a limit named in bytes this is the
|
|
@@ -42,7 +43,6 @@ function got(input) {
|
|
|
42
43
|
return "an array";
|
|
43
44
|
return typeof input === "object" ? "an object" : `a ${typeof input}`;
|
|
44
45
|
}
|
|
45
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
46
46
|
/** The schema at `path`, including whether that value may be omitted, following arrays, wrappers and unions. */
|
|
47
47
|
function nodeAt(schema, input, path) {
|
|
48
48
|
let node = schema, value = input;
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
export declare const FLOW_CARD_STATES: readonly ["active", "done", "cancelled"];
|
|
12
|
-
export type FlowCardState = (typeof FLOW_CARD_STATES)[number];
|
|
13
12
|
export declare const flowCardSchema: z.ZodObject<{
|
|
14
13
|
id: z.ZodInt;
|
|
15
14
|
flow: z.ZodInt;
|
|
@@ -320,6 +320,5 @@ export declare const proposeFlowInputSchema: z.ZodObject<{
|
|
|
320
320
|
timeZone: z.ZodOptional<z.ZodString>;
|
|
321
321
|
}, z.core.$strict>], "kind">>;
|
|
322
322
|
}, z.core.$strict>;
|
|
323
|
-
export type ProposeFlowInput = z.infer<typeof proposeFlowInputSchema>;
|
|
324
323
|
/** What the lead is told propose_flow takes: exactly the schema its call is read with. */
|
|
325
324
|
export declare const PROPOSE_FLOW_MODEL_SCHEMA: Readonly<Record<string, unknown>>;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { parseContract, readVersioned, versioned } from "./contract.js";
|
|
15
|
+
import { isRecord } from "../is-record.js";
|
|
15
16
|
/** A link the message carries: a page of Toolroll's (a button) or the one outside page, a pull request. */
|
|
16
17
|
export const flowSendLinkSchema = z.union([
|
|
17
18
|
z.strictObject({ label: z.string(), path: z.string().regex(/^\//, { error: "must be a path in Toolroll, starting with /" }) }),
|
|
@@ -51,7 +52,6 @@ export const flowChoosePayloadSchema = versioned(FLOW_SEND_VERSION, {
|
|
|
51
52
|
/** Whether a reply is taken instead (the zone has somewhere replies go). */
|
|
52
53
|
reply: z.boolean(),
|
|
53
54
|
});
|
|
54
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
55
|
/** A kept item as 0.9.36 read it: its five words (a web link), and a screenshot id only when it is a whole number. */
|
|
56
56
|
const savedItemSchema = z.object({ ...flowSendItemSchema.shape, shot: z.unknown() });
|
|
57
57
|
export function readFlowItems(raw) {
|
|
@@ -371,7 +371,6 @@ export declare const GATEWAY_TOOL_OUTPUTS: {
|
|
|
371
371
|
admission: z.ZodString;
|
|
372
372
|
}, z.core.$loose>;
|
|
373
373
|
};
|
|
374
|
-
export type GatewayToolOutput<N extends GatewayToolName> = z.infer<(typeof GATEWAY_TOOL_OUTPUTS)[N]>;
|
|
375
374
|
export declare const PERSON_TOOL_INPUTS: {
|
|
376
375
|
readonly status: z.ZodObject<{}, z.core.$strict>;
|
|
377
376
|
readonly list_tasks: z.ZodObject<{
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
import { parseContract } from "./contract.js";
|
|
17
|
+
import { isRecord } from "../is-record.js";
|
|
17
18
|
/** RFC 6749 scope tokens. */
|
|
18
19
|
export const SCOPE_TOKEN = /^[\x21\x23-\x5b\x5d-\x7e]{1,128}$/;
|
|
19
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
20
|
/** A field's schema without its optional wrapper. */
|
|
21
21
|
const inner = (field) => (field instanceof z.ZodOptional ? field.unwrap() : field);
|
|
22
22
|
/**
|
|
@@ -2003,7 +2003,6 @@ export declare const LEAD_TOOL_OUTPUTS: {
|
|
|
2003
2003
|
awaiting: z.ZodString;
|
|
2004
2004
|
}, z.core.$loose>;
|
|
2005
2005
|
};
|
|
2006
|
-
export type LeadToolOutput<N extends LeadToolName> = z.infer<(typeof LEAD_TOOL_OUTPUTS)[N]>;
|
|
2007
2006
|
/**
|
|
2008
2007
|
* Report a result that disagrees with its output schema without hiding the actual result, including under Vitest.
|
|
2009
2008
|
* Contract tests assert output shapes directly; executing a tool always writes the diagnostic and returns its result.
|
|
@@ -131,7 +131,6 @@ export declare const memoryEvidenceSchema: z.ZodArray<z.ZodObject<{
|
|
|
131
131
|
session: z.ZodString;
|
|
132
132
|
quote: z.ZodString;
|
|
133
133
|
}, z.core.$strict>>;
|
|
134
|
-
export type MemoryEvidence = z.infer<typeof memoryEvidenceSchema>;
|
|
135
134
|
/** A proposal as its row reads; its evidence is a JSON list (an older runtime reads it as one, so it has no envelope). */
|
|
136
135
|
export declare const memoryProposalSchema: z.ZodObject<{
|
|
137
136
|
id: z.ZodInt;
|