@intentic/sandbox-contract 1.245.0 → 1.246.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/command-classes.d.ts +6 -3
- package/dist/command-classes.d.ts.map +1 -1
- package/dist/command-classes.js +43 -18
- package/dist/command-classes.js.map +1 -1
- package/dist/contracts/{cursor.contract.d.ts → accounts.contract.d.ts} +102 -3
- package/dist/contracts/accounts.contract.d.ts.map +1 -0
- package/dist/contracts/accounts.contract.js +61 -0
- package/dist/contracts/accounts.contract.js.map +1 -0
- package/dist/contracts/agents.contract.d.ts +121 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +4 -4
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts +2 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.d.ts +35 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +3 -2
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/personas.contract.d.ts +4 -2
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +2 -2
- package/dist/contracts/settings.contract.d.ts +2 -51
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +80 -30
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +26 -17
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/definition.d.ts +4 -8
- package/dist/definition.d.ts.map +1 -1
- package/dist/documents.d.ts +0 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1 -2
- package/dist/documents.js.map +1 -1
- package/dist/embed.d.ts +23 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +84 -0
- package/dist/embed.js.map +1 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +5 -2
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +2 -0
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +354 -335
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -13
- package/dist/index.js.map +1 -1
- package/dist/peer-dial.d.ts +33 -0
- package/dist/peer-dial.d.ts.map +1 -0
- package/dist/peer-dial.js +79 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/peer-mcp-server.d.ts +36 -0
- package/dist/peer-mcp-server.d.ts.map +1 -0
- package/dist/peer-mcp-server.js +71 -0
- package/dist/peer-mcp-server.js.map +1 -0
- package/dist/provider-specs.d.ts +38 -20
- package/dist/provider-specs.d.ts.map +1 -1
- package/dist/provider-specs.js +39 -13
- package/dist/provider-specs.js.map +1 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +2 -1
- package/dist/quick-model.js.map +1 -1
- package/dist/runtime-state.d.ts +1 -1
- package/dist/runtime-state.js +1 -1
- package/dist/runtime-state.js.map +1 -1
- package/dist/safety-policy.d.ts +12 -3
- package/dist/safety-policy.d.ts.map +1 -1
- package/dist/safety-policy.js +30 -5
- package/dist/safety-policy.js.map +1 -1
- package/dist/schemas/agent.d.ts +6 -6
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +6 -2
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +42 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +25 -4
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +11 -2
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js +1 -1
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/ci.d.ts +6 -0
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +3 -2
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/context.d.ts +30 -0
- package/dist/schemas/context.d.ts.map +1 -0
- package/dist/schemas/context.js +34 -0
- package/dist/schemas/context.js.map +1 -0
- package/dist/schemas/{computers.d.ts → devices.d.ts} +154 -60
- package/dist/schemas/devices.d.ts.map +1 -0
- package/dist/schemas/devices.js +157 -0
- package/dist/schemas/devices.js.map +1 -0
- package/dist/schemas/hosts.d.ts +12 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +1 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/issues.d.ts +0 -5
- package/dist/schemas/issues.d.ts.map +1 -1
- package/dist/schemas/issues.js +0 -1
- package/dist/schemas/issues.js.map +1 -1
- package/dist/schemas/personas.d.ts +5 -3
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +3 -2
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/provider-oauth.d.ts +48 -16
- package/dist/schemas/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/provider-oauth.js +22 -20
- package/dist/schemas/provider-oauth.js.map +1 -1
- package/dist/schemas/settings.d.ts +1 -52
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +8 -11
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/terminal.js +9 -9
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/usage.d.ts +0 -2
- package/dist/schemas/usage.d.ts.map +1 -1
- package/dist/schemas/usage.js +0 -2
- package/dist/schemas/usage.js.map +1 -1
- package/dist/shell-regions.d.ts +4 -0
- package/dist/shell-regions.d.ts.map +1 -0
- package/dist/shell-regions.js +156 -0
- package/dist/shell-regions.js.map +1 -0
- package/dist/workspace-state.d.ts +8 -0
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +13 -5
- package/dist/workspace-state.js.map +1 -1
- package/package.json +37 -4
- package/src/agent-catalog.ts +1 -1
- package/src/arrival.ts +3 -3
- package/src/command-classes.test.ts +195 -71
- package/src/command-classes.ts +148 -46
- package/src/contracts/accounts.contract.ts +94 -0
- package/src/contracts/agents.contract.ts +4 -3
- package/src/contracts/exit.contract.ts +2 -2
- package/src/contracts/host.contract.ts +17 -5
- package/src/contracts/settings.contract.ts +1 -1
- package/src/contracts/system.contract.ts +43 -24
- package/src/contracts/vpn.contract.ts +2 -2
- package/src/documents.test.ts +2 -1
- package/src/documents.ts +7 -11
- package/src/embed.test.ts +68 -0
- package/src/embed.ts +164 -0
- package/src/events.ts +30 -3
- package/src/history-state.ts +12 -3
- package/src/host-protocol.ts +2 -2
- package/src/index.ts +6 -14
- package/src/model-order.ts +1 -1
- package/src/peer-dial.test.ts +203 -0
- package/src/peer-dial.ts +163 -0
- package/src/peer-mcp-server.test.ts +104 -0
- package/src/peer-mcp-server.ts +144 -0
- package/src/provider-specs.test.ts +44 -17
- package/src/provider-specs.ts +147 -67
- package/src/quick-model.ts +10 -3
- package/src/routes.test.ts +6 -3
- package/src/runner-protocol.ts +1 -1
- package/src/runtime-state.ts +2 -2
- package/src/safety-policy.test.ts +88 -0
- package/src/safety-policy.ts +84 -14
- package/src/schemas/agent.ts +41 -13
- package/src/schemas/agents.ts +65 -4
- package/src/schemas/automations.ts +6 -4
- package/src/schemas/capabilities.ts +4 -4
- package/src/schemas/ci.ts +23 -6
- package/src/schemas/context.ts +87 -0
- package/src/schemas/{computers.ts → devices.ts} +190 -107
- package/src/schemas/hosts.ts +5 -1
- package/src/schemas/issues.ts +0 -4
- package/src/schemas/personas.ts +8 -3
- package/src/schemas/provider-oauth.ts +49 -52
- package/src/schemas/settings.ts +30 -54
- package/src/schemas/terminal.ts +12 -12
- package/src/schemas/usage.ts +3 -27
- package/src/schemas/version-seam.test.ts +0 -1
- package/src/shell-regions.ts +289 -0
- package/src/versions.ts +2 -2
- package/src/webext-links.ts +2 -2
- package/src/webext-protocol.ts +2 -2
- package/src/workspace-state.test.ts +48 -1
- package/src/workspace-state.ts +48 -11
- package/dist/contracts/claude.contract.d.ts +0 -91
- package/dist/contracts/claude.contract.d.ts.map +0 -1
- package/dist/contracts/claude.contract.js +0 -50
- package/dist/contracts/claude.contract.js.map +0 -1
- package/dist/contracts/cursor.contract.d.ts.map +0 -1
- package/dist/contracts/cursor.contract.js +0 -50
- package/dist/contracts/cursor.contract.js.map +0 -1
- package/dist/contracts/grok.contract.d.ts +0 -36
- package/dist/contracts/grok.contract.d.ts.map +0 -1
- package/dist/contracts/grok.contract.js +0 -31
- package/dist/contracts/grok.contract.js.map +0 -1
- package/dist/contracts/keys.contract.d.ts +0 -81
- package/dist/contracts/keys.contract.d.ts.map +0 -1
- package/dist/contracts/keys.contract.js +0 -51
- package/dist/contracts/keys.contract.js.map +0 -1
- package/dist/schemas/computers.d.ts.map +0 -1
- package/dist/schemas/computers.js +0 -135
- package/dist/schemas/computers.js.map +0 -1
- package/src/contracts/claude.contract.ts +0 -71
- package/src/contracts/cursor.contract.ts +0 -74
- package/src/contracts/grok.contract.ts +0 -41
- package/src/contracts/keys.contract.ts +0 -79
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/* WHICH PARTS OF A COMMAND ARE TEXT RATHER THAN A PROGRAM, so the one tier that cannot be argued with stops
|
|
2
|
+
* firing on a mention of a dangerous verb.
|
|
3
|
+
*
|
|
4
|
+
* WHAT THIS IS FOR, precisely, because it bounds how good it has to be. The triage catalog next door
|
|
5
|
+
* (command-classes.ts) is deliberately over-inclusive: a match only means a judge should look, and a false
|
|
6
|
+
* positive there costs one model call. That economy holds for every class except the hard-ruled one, where a
|
|
7
|
+
* match is an interruption no policy and no verdict can waive (safety-policy.ts hardRuleClasses). So
|
|
8
|
+
* `echo "rm -rf /" >> notes.md` and `rg 'rm -rf /'` raised un-waivable cards over a string being written to a
|
|
9
|
+
* file and a search of the tree — the exact failure the judge redesign was built to end, surviving in the one
|
|
10
|
+
* tier the judge cannot reach.
|
|
11
|
+
*
|
|
12
|
+
* This says where a fragment sits. A fragment inside a region below is still REPORTED (the class holds, the
|
|
13
|
+
* judge still reads it, the card still marks it); it just does not trip the hard rule.
|
|
14
|
+
*
|
|
15
|
+
* WHICH IS WHY A REGEX-LEVEL SCANNER IS ENOUGH, and this is the design argument rather than an excuse. Both
|
|
16
|
+
* ways of being wrong are cheap:
|
|
17
|
+
*
|
|
18
|
+
* · MISS a region (call text a program) ⇒ one judge call. Exactly today's behaviour, which is the floor.
|
|
19
|
+
* · INVENT a region (call a program text) ⇒ the class is still reported and the judge still rules on it.
|
|
20
|
+
* Only the un-waivable tier is skipped, and the judge is the tier that reads the owner's policy.
|
|
21
|
+
*
|
|
22
|
+
* Precision has to be good enough to skip tier 1½, never good enough to skip tier 1. Nothing here is a
|
|
23
|
+
* boundary, for the same reason nothing in command-classes.ts is: `sh -c "$CMD"` and a path assembled from a
|
|
24
|
+
* variable walk past all of it. The boundaries are structural and elsewhere.
|
|
25
|
+
*
|
|
26
|
+
* THREE KINDS OF REGION, and each is a place a shell will not run what it holds:
|
|
27
|
+
*
|
|
28
|
+
* 1 A COMMENT, `#` to end of line.
|
|
29
|
+
* 2 A HEREDOC BODY, the usual way an agent writes a script it is not running yet.
|
|
30
|
+
* 3 A QUOTED ARGUMENT OF A VERB THAT CANNOT EXECUTE ONE — echo, printf, and the searchers. Plus a quoted
|
|
31
|
+
* commit message after -m, whatever the verb, because message text never runs.
|
|
32
|
+
*
|
|
33
|
+
* `sed`, `awk` and `perl` are deliberately NOT in that verb list, though they are the obvious next entries:
|
|
34
|
+
* each can run a shell out of its own quoted program (`awk 'BEGIN{system("…")}'`, `perl -e`, GNU sed's `s///e`),
|
|
35
|
+
* so their quoted argument is a program and calling it text would be wrong rather than merely imprecise.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import type { CommandSpan } from "./command-classes.js";
|
|
39
|
+
|
|
40
|
+
/* Verbs whose quoted arguments this scanner will call text. Every one of them either prints its argument or
|
|
41
|
+
* matches with it, and none has a documented way to execute it. Widening this list is safe in the sense the
|
|
42
|
+
* header sets out, but each entry should be able to answer "how would this run its argument?" with "it cannot". */
|
|
43
|
+
const QUOTING_VERBS: ReadonlySet<string> = new Set([
|
|
44
|
+
"echo",
|
|
45
|
+
"printf",
|
|
46
|
+
"rg",
|
|
47
|
+
"grep",
|
|
48
|
+
"egrep",
|
|
49
|
+
"fgrep",
|
|
50
|
+
"ack",
|
|
51
|
+
"ag",
|
|
52
|
+
"ripgrep",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/* A flag whose value is a message: git's -m, and the long spelling. A quoted string here is prose that reaches
|
|
56
|
+
* a commit, a tag or a PR, and `git commit -m "rm -rf the old build dir"` is one of the more ordinary ways to
|
|
57
|
+
* write a dangerous-looking command that is not one. Read on the WORD BEFORE a quoted argument, so it applies
|
|
58
|
+
* whatever the verb is. */
|
|
59
|
+
const MESSAGE_FLAGS: ReadonlySet<string> = new Set(["-m", "--message", "-am", "--body", "-b"]);
|
|
60
|
+
|
|
61
|
+
// Where an unquoted word ends: whitespace, a pipeline or list operator, a redirect, a subshell paren.
|
|
62
|
+
const WORD_END = /[\s;|&<>()]/;
|
|
63
|
+
|
|
64
|
+
// A word that is a variable assignment prefixing a command (`FOO=bar cmd …`), skipped when looking for the verb.
|
|
65
|
+
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
66
|
+
|
|
67
|
+
/* `<<EOF`, `<<-EOF`, `<<'EOF'`, `<<"EOF"`. The delimiter's quoting only decides whether the body expands, which
|
|
68
|
+
* changes nothing here: an expanded body is still a body being written somewhere rather than run. */
|
|
69
|
+
const HEREDOC_OPEN = /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1/g;
|
|
70
|
+
|
|
71
|
+
/* The heredoc bodies in a command, as spans. Found first and in their own pass, because a body is line-oriented
|
|
72
|
+
* and can hold anything at all — an odd number of quotes in it would otherwise throw the word scanner out of
|
|
73
|
+
* step for the rest of the command. */
|
|
74
|
+
const heredocBodies = (command: string): CommandSpan[] => {
|
|
75
|
+
const bodies: CommandSpan[] = [];
|
|
76
|
+
for (const open of command.matchAll(HEREDOC_OPEN)) {
|
|
77
|
+
const indented = command.slice(open.index, open.index + 3).startsWith("<<-");
|
|
78
|
+
const newline = command.indexOf("\n", open.index + open[0].length);
|
|
79
|
+
if (newline === -1) {
|
|
80
|
+
// `cat <<EOF` with nothing after it: an unterminated heredoc, so there is no body to mark.
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const start = newline + 1;
|
|
84
|
+
const terminator = new RegExp(`^${indented ? "[ \\t]*" : ""}${open[2] as string}[ \\t]*$`, "m");
|
|
85
|
+
const rest = command.slice(start);
|
|
86
|
+
const end = terminator.exec(rest)?.index;
|
|
87
|
+
// An unterminated body runs to the end of the command, which is what the shell would read too.
|
|
88
|
+
bodies.push({ start, end: end === undefined ? command.length : start + end });
|
|
89
|
+
}
|
|
90
|
+
return bodies;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Is this offset inside one of the spans already found? Heredoc bodies are skipped wholesale by the word scan.
|
|
94
|
+
const within = (spans: readonly CommandSpan[], offset: number): CommandSpan | undefined =>
|
|
95
|
+
spans.find((span) => offset >= span.start && offset < span.end);
|
|
96
|
+
|
|
97
|
+
// A substitution inside double quotes, the one thing that makes a quoted argument a program again.
|
|
98
|
+
const EXPANDS = /\$\(|`/;
|
|
99
|
+
|
|
100
|
+
/* One quoted segment inside a word: the span covers the quotes as well as what is between them, so a region
|
|
101
|
+
* handed back from here contains the whole `"rm -rf /"` and a match on any part of it reads as contained. */
|
|
102
|
+
interface QuotedSegment {
|
|
103
|
+
readonly span: CommandSpan;
|
|
104
|
+
/* Does a shell expand anything in here? `"$(rm -rf /)"` inside an echo is a real delete whose output is
|
|
105
|
+
* printed, so a double-quoted segment carrying a substitution is NOT text and never becomes a region. Single
|
|
106
|
+
* quotes expand nothing, so they never set this. */
|
|
107
|
+
readonly expands: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface ShellWord {
|
|
111
|
+
readonly start: number;
|
|
112
|
+
// The word with its quoting removed, which is what a verb name and a flag are compared against.
|
|
113
|
+
readonly text: string;
|
|
114
|
+
readonly quoted: readonly QuotedSegment[];
|
|
115
|
+
// Does this word begin a simple command? True for the first word after a separator or at the start.
|
|
116
|
+
readonly opensCommand: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A verb by its bare name: `/bin/echo` and `./echo` are echo, and the path in front of it says nothing new.
|
|
120
|
+
const bareVerb = (text: string): string => text.slice(Math.max(text.lastIndexOf("/"), text.lastIndexOf("\\")) + 1);
|
|
121
|
+
|
|
122
|
+
// Every list and pipeline operator: past one of these the next word is a verb again.
|
|
123
|
+
const SEPARATORS = new Set(["\n", ";", "|", "&", "(", ")"]);
|
|
124
|
+
const BLANKS = new Set([" ", "\t", "\r"]);
|
|
125
|
+
|
|
126
|
+
/* One quoted run, from its opening quote to its closing one. An unbalanced quote takes the rest of the
|
|
127
|
+
* command, which is what a shell waiting for more input would do and keeps the caller advancing. */
|
|
128
|
+
const readQuoted = (command: string, open: number): QuotedSegment & { readonly text: string; readonly end: number } => {
|
|
129
|
+
const quote = command[open] as string;
|
|
130
|
+
const close = command.indexOf(quote, open + 1);
|
|
131
|
+
const end = close === -1 ? command.length : close + 1;
|
|
132
|
+
const text = command.slice(open + 1, close === -1 ? command.length : close);
|
|
133
|
+
return { span: { start: open, end }, expands: quote === '"' && EXPANDS.test(text), text, end };
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/* One word, from `start` to whatever ends it, with the quoted runs inside it kept as spans. `end === start`
|
|
137
|
+
* cannot happen: the caller only enters here on a character that is neither blank nor a separator. */
|
|
138
|
+
const readWord = (command: string, start: number): { readonly word: Omit<ShellWord, "opensCommand">; readonly end: number } => {
|
|
139
|
+
const quoted: QuotedSegment[] = [];
|
|
140
|
+
let text = "";
|
|
141
|
+
let index = start;
|
|
142
|
+
while (index < command.length && !WORD_END.test(command[index] as string)) {
|
|
143
|
+
const char = command[index] as string;
|
|
144
|
+
if (char === "'" || char === '"') {
|
|
145
|
+
const segment = readQuoted(command, index);
|
|
146
|
+
quoted.push({ span: segment.span, expands: segment.expands });
|
|
147
|
+
text += segment.text;
|
|
148
|
+
index = segment.end;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (char === "\\") {
|
|
152
|
+
text += command[index + 1] ?? "";
|
|
153
|
+
index += 2;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
text += char;
|
|
157
|
+
index += 1;
|
|
158
|
+
}
|
|
159
|
+
return { word: { start, text, quoted }, end: index };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/* The command taken apart into words, with each word's quoted segments and whether it opens a simple command.
|
|
163
|
+
* Deliberately a scanner rather than a parser: it tracks quoting and the separators that start a new command,
|
|
164
|
+
* and it knows nothing about control flow, functions or expansion. Everything it gets wrong is bounded by the
|
|
165
|
+
* header's argument. */
|
|
166
|
+
const scanWords = (command: string, skip: readonly CommandSpan[]): ShellWord[] => {
|
|
167
|
+
const words: ShellWord[] = [];
|
|
168
|
+
let index = 0;
|
|
169
|
+
let opensCommand = true;
|
|
170
|
+
while (index < command.length) {
|
|
171
|
+
const skipped = within(skip, index);
|
|
172
|
+
if (skipped !== undefined) {
|
|
173
|
+
index = skipped.end;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const char = command[index] as string;
|
|
177
|
+
if (SEPARATORS.has(char)) {
|
|
178
|
+
opensCommand = true;
|
|
179
|
+
index += 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (BLANKS.has(char)) {
|
|
183
|
+
index += 1;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (char === "#") {
|
|
187
|
+
// A comment runs to the end of the line. Only reached at a word boundary, so `a#b` is one word.
|
|
188
|
+
const newline = command.indexOf("\n", index);
|
|
189
|
+
index = newline === -1 ? command.length : newline;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const { word, end } = readWord(command, index);
|
|
193
|
+
// A character that is neither a separator nor part of a word (a stray redirect): step over it so the
|
|
194
|
+
// loop always advances.
|
|
195
|
+
index = end === index ? index + 1 : end;
|
|
196
|
+
if (end !== word.start) {
|
|
197
|
+
words.push({ ...word, opensCommand });
|
|
198
|
+
opensCommand = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return words;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/* WHERE A COMMAND HOLDS TEXT RATHER THAN A PROGRAM, as spans over the command, unsorted and possibly
|
|
205
|
+
* overlapping — callers ask containment questions of them rather than rendering them.
|
|
206
|
+
*
|
|
207
|
+
* Exported for the classifier, which asks it once per command and hands the answer to every table. */
|
|
208
|
+
export const inertRegions = (command: string): CommandSpan[] => {
|
|
209
|
+
const bodies = heredocBodies(command);
|
|
210
|
+
const regions: CommandSpan[] = [...bodies];
|
|
211
|
+
const words = scanWords(command, bodies);
|
|
212
|
+
// Comments are consumed by the scanner rather than reported, so they are found again here: the scan is
|
|
213
|
+
// where the quoting state lives, and a `#` inside a quoted string is not a comment.
|
|
214
|
+
let quotingVerb = false;
|
|
215
|
+
let previous: ShellWord | undefined;
|
|
216
|
+
for (const word of words) {
|
|
217
|
+
if (word.opensCommand) {
|
|
218
|
+
quotingVerb = QUOTING_VERBS.has(bareVerb(word.text));
|
|
219
|
+
previous = undefined;
|
|
220
|
+
}
|
|
221
|
+
/* An env assignment in front of the verb (`LC_ALL=C grep …`) is not the verb. Re-read the next word as
|
|
222
|
+
* one instead of giving up on the command. */
|
|
223
|
+
if (word.opensCommand && ASSIGNMENT.test(word.text) && word.quoted.length === 0) {
|
|
224
|
+
quotingVerb = false;
|
|
225
|
+
previous = undefined;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const afterMessageFlag = previous !== undefined && MESSAGE_FLAGS.has(previous.text);
|
|
229
|
+
if (quotingVerb || afterMessageFlag) {
|
|
230
|
+
regions.push(...word.quoted.filter((segment) => !segment.expands).map((segment) => segment.span));
|
|
231
|
+
}
|
|
232
|
+
previous = word;
|
|
233
|
+
}
|
|
234
|
+
regions.push(...commentRegions(command, bodies));
|
|
235
|
+
return regions;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/* Comments, on the same scan discipline as the words: a `#` only opens one where a word could have started, so
|
|
239
|
+
* `sha#1` is not a comment, and a quoted `"# …"` is not one either.
|
|
240
|
+
*
|
|
241
|
+
* Its own walk rather than a by-product of scanWords, because the two want different things from a quote — the
|
|
242
|
+
* word scan needs what is INSIDE one, this needs only to be past it. Sharing readQuoted keeps them agreeing on
|
|
243
|
+
* where one ends, which is the only fact they both depend on. */
|
|
244
|
+
const commentRegions = (command: string, skip: readonly CommandSpan[]): CommandSpan[] => {
|
|
245
|
+
const comments: CommandSpan[] = [];
|
|
246
|
+
let index = 0;
|
|
247
|
+
while (index < command.length) {
|
|
248
|
+
const skipped = within(skip, index);
|
|
249
|
+
if (skipped !== undefined) {
|
|
250
|
+
index = skipped.end;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const char = command[index] as string;
|
|
254
|
+
if (char === "'" || char === '"') {
|
|
255
|
+
index = readQuoted(command, index).end;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (char === "\\") {
|
|
259
|
+
index += 2;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// A word boundary in front is what makes it a comment rather than part of a word.
|
|
263
|
+
if (char === "#" && (index === 0 || WORD_END.test(command[index - 1] as string))) {
|
|
264
|
+
const newline = command.indexOf("\n", index);
|
|
265
|
+
const end = newline === -1 ? command.length : newline;
|
|
266
|
+
comments.push({ start: index, end });
|
|
267
|
+
index = end;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
index += 1;
|
|
271
|
+
}
|
|
272
|
+
return comments;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/* Is this fragment somewhere a shell would RUN it? The question the hard rule asks of every span triage matched.
|
|
276
|
+
*
|
|
277
|
+
* JUDGED ON WHERE THE FRAGMENT STARTS, not on whether a region contains the whole of it, and the difference is
|
|
278
|
+
* not a relaxation — it is the only reading that answers the question asked. Every pattern in the catalog is
|
|
279
|
+
* written to BEGIN at the verb (`rm`, `docker volume rm`, `mkfs`, `git push`), so the span's first character is
|
|
280
|
+
* where the dangerous thing was found. Whether the match then ran on past a closing quote says something about
|
|
281
|
+
* the regex, not about the command: `rm`'s own parser reads to the end of the invocation and `>` is not a
|
|
282
|
+
* terminator, so `echo "rm -rf /" >> notes.md` produces a span covering `rm -rf /" >> notes.md`. Requiring
|
|
283
|
+
* containment made that command live — which is exactly the card this whole change exists to stop raising.
|
|
284
|
+
*
|
|
285
|
+
* The conservative direction is preserved where it matters: a fragment whose verb sits OUTSIDE every region is
|
|
286
|
+
* live no matter what it runs into afterwards, so `echo "tidying" && rm -rf /` and `rm -rf "$(cat list)" # ok`
|
|
287
|
+
* both stay live. */
|
|
288
|
+
export const isLive = (span: CommandSpan, regions: readonly CommandSpan[]): boolean =>
|
|
289
|
+
!regions.some((region) => span.start >= region.start && span.start < region.end);
|
package/src/versions.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* COMPARING THE VERSIONS THIS SYSTEM STAMPS ON WHAT IT SHIPS, the daemon, the sandbox image, and the two agents
|
|
2
|
-
* that run on a user's own
|
|
2
|
+
* that run on a user's own device. One release stamps all of them to the SAME version, so "is this one behind
|
|
3
3
|
* that one" is one question with one answer, and it lives here because both ends ask it: the daemon compares its
|
|
4
|
-
* own build against the latest published release, and the browser compares a
|
|
4
|
+
* own build against the latest published release, and the browser compares a device's agent against the same.
|
|
5
5
|
*
|
|
6
6
|
* Shared rather than copied because the two copies would not disagree until the day it mattered: 1.9.0 against
|
|
7
7
|
* 1.10.0 is where a hand-rolled comparator goes wrong, and it goes wrong by reporting "up to date". */
|
package/src/webext-links.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const webextLendUrl = (sandboxUrl: string): string => `${sandboxUrl.repla
|
|
|
35
35
|
|
|
36
36
|
/* ---- the pairing code: the one string that travels from the sandbox's card into the extension ----
|
|
37
37
|
*
|
|
38
|
-
* A connected
|
|
38
|
+
* A connected device is paired by a shell one-liner, which can carry two values in two environment variables
|
|
39
39
|
* because a terminal is a place where long strings are normal. A browser extension's popup is not: what a
|
|
40
40
|
* person will actually do there is paste ONE thing, once, and anything that asks them to copy a URL into one
|
|
41
41
|
* box and a token into another is a flow that fails on the second box.
|
|
@@ -43,7 +43,7 @@ export const webextLendUrl = (sandboxUrl: string): string => `${sandboxUrl.repla
|
|
|
43
43
|
* So both halves ride in one code. It is not encryption and does not pretend to be — base64url of two fields,
|
|
44
44
|
* so that the thing on the clipboard is opaque enough not to be edited by hand, short enough to paste, and
|
|
45
45
|
* carries its own sandbox address, which is the field a person could not possibly be expected to type. The
|
|
46
|
-
* secret in it is the pairing token, which is single-use and expires in ten minutes (
|
|
46
|
+
* secret in it is the pairing token, which is single-use and expires in ten minutes (the daemon's peer store).
|
|
47
47
|
*
|
|
48
48
|
* The prefix is a version marker, and it is here so that a code from an older sandbox meets a clear "this code
|
|
49
49
|
* is from a different version" in the extension rather than a JSON parse error. */
|
package/src/webext-protocol.ts
CHANGED
|
@@ -2,13 +2,13 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
/* The handshake on /system/webext/connect, the ONE message on that socket that is not oRPC.
|
|
4
4
|
*
|
|
5
|
-
* Same two-phase shape as a connected
|
|
5
|
+
* Same two-phase shape as a connected device's (host-protocol.ts) and for the same reason: the daemon has
|
|
6
6
|
* nothing to call until it knows whose socket this is, so the proof cannot itself be an oRPC call. The browser
|
|
7
7
|
* extension's first frame carries its enrollment token, the daemon resolves WHICH capability it belongs to, and
|
|
8
8
|
* from that message on every byte is `webextContract` with the EXTENSION serving.
|
|
9
9
|
*
|
|
10
10
|
* WHY A SEPARATE PROTOCOL FROM host's, when the frame is the same two fields: because the thing on the other
|
|
11
|
-
* end is not a
|
|
11
|
+
* end is not a device. It has no shell, no filesystem and no screen; what it has is tabs, origins the person
|
|
12
12
|
* granted one at a time, and a human watching every click. Sharing the host's schema would have meant a card of
|
|
13
13
|
* switches that mean nothing (`roots`, `sandboxRemove`) and an agent told about a home directory it cannot
|
|
14
14
|
* reach. The two connectors are siblings, not one connector with a flag. */
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
isLockedWorkspacePath,
|
|
7
7
|
isReportedManifest,
|
|
8
8
|
isReviewableLockedPath,
|
|
9
|
+
LOCKED_STATE_ENTRIES,
|
|
10
|
+
lockedWorkspaceEntry,
|
|
11
|
+
PLAN_DOCUMENTS_DIR,
|
|
9
12
|
REPORTED_MANIFEST_PATHS,
|
|
10
13
|
SEARCHABLE_STATE_PATHS,
|
|
11
14
|
SHARED_STATE_PATHS,
|
|
@@ -244,6 +247,47 @@ describe(`isLockedWorkspacePath`, () => {
|
|
|
244
247
|
expect(isLockedWorkspacePath(`.intentic\\secrets\\auth\\codex`)).toBe(true);
|
|
245
248
|
expect(isLockedWorkspacePath(`./.intentic/config/capabilities.json`)).toBe(true);
|
|
246
249
|
});
|
|
250
|
+
|
|
251
|
+
it(`lets the plan documents out of the session store around them`, () => {
|
|
252
|
+
// The plan a card asks the reader to approve, whose full text that card already renders. Refusing the
|
|
253
|
+
// file left the card's one link into the workspace landing on a padlock about the document it was
|
|
254
|
+
// asking about; the transcripts it sits beside stay locked.
|
|
255
|
+
expect(isLockedWorkspacePath(`${PLAN_DOCUMENTS_DIR}/wiggly-spring.md`)).toBe(false);
|
|
256
|
+
expect(isLockedWorkspacePath(PLAN_DOCUMENTS_DIR)).toBe(false);
|
|
257
|
+
expect(isLockedWorkspacePath(`.intentic/records/sessions/claude/projects/x.jsonl`)).toBe(true);
|
|
258
|
+
expect(isLockedWorkspacePath(`.intentic/records/sessions`)).toBe(true);
|
|
259
|
+
// …and it is the directory that is exempt, not the word: a sibling store named for it is not one.
|
|
260
|
+
expect(isLockedWorkspacePath(`.intentic/records/sessions/claude/plans-backup/x.md`)).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/* WHICH entry a locked path belongs to, which is what the refusal screen says a file holds and where to manage
|
|
265
|
+
* it. Split out from the boolean so the browser can key its sentences on the daemon's own list instead of a
|
|
266
|
+
* second copy of the rule — the copy it kept drifted through the state regrouping and stranded every locked
|
|
267
|
+
* file on the generic sentence. */
|
|
268
|
+
describe(`lockedWorkspaceEntry`, () => {
|
|
269
|
+
it(`names the entry a path matched, not the leaf it ends at`, () => {
|
|
270
|
+
// A locked FOLDER is one row in the explorer and never descended, so the name worth reporting is the
|
|
271
|
+
// folder's: "Cookies is kept private" is true of something the reader has never heard of.
|
|
272
|
+
expect(lockedWorkspaceEntry(`.intentic/local/browser/Default/Cookies`)).toBe(`local/browser`);
|
|
273
|
+
expect(lockedWorkspaceEntry(`.intentic/secrets/auth/codex/auth.json`)).toBe(`secrets/auth`);
|
|
274
|
+
expect(lockedWorkspaceEntry(`.intentic/config/capabilities.json`)).toBe(`config/capabilities.json`);
|
|
275
|
+
expect(lockedWorkspaceEntry(`.git/config`)).toBe(`.git`);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it(`answers undefined for everything the lock does not hold`, () => {
|
|
279
|
+
expect(lockedWorkspaceEntry(`src/app.ts`)).toBeUndefined();
|
|
280
|
+
expect(lockedWorkspaceEntry(`.intentic/config/settings.json`)).toBeUndefined();
|
|
281
|
+
expect(lockedWorkspaceEntry(`${PLAN_DOCUMENTS_DIR}/wiggly-spring.md`)).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it(`answers for every entry the lock declares`, () => {
|
|
285
|
+
// The set is the daemon's; this is what makes it addressable from the browser. An entry nobody can
|
|
286
|
+
// resolve back out is one the refusal screen could only describe generically.
|
|
287
|
+
for (const entry of LOCKED_STATE_ENTRIES) {
|
|
288
|
+
expect([entry, lockedWorkspaceEntry(`${STATE_DIR}/${entry}`)]).toEqual([entry, entry]);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
247
291
|
});
|
|
248
292
|
|
|
249
293
|
/* The carve-out the diff routes ask for, and the reason it is derived: a locked entry the root repo TRACKS has
|
|
@@ -330,10 +374,13 @@ describe(`VERSIONED_STATE_PATHS`, () => {
|
|
|
330
374
|
/* The connections themselves, and the entry that took the longest to earn its place: it was classed
|
|
331
375
|
* `secret` on the strength of holding each capability's credential, which stopped being true when the
|
|
332
376
|
* vault took the values out and left the shape behind. Connecting a deployment orchestrator, or
|
|
333
|
-
* granting a connected
|
|
377
|
+
* granting a connected device shell and screen control, is the largest change made to what this
|
|
334
378
|
* sandbox can DO, and it used to leave no diff. */
|
|
335
379
|
`${STATE_DIR}/config/capabilities.json`,
|
|
336
380
|
`${STATE_DIR}/config/capability-dismissals.json`,
|
|
381
|
+
// The context shelves: which repositories a conversation opened on one carries. A list of names, and
|
|
382
|
+
// the decision about what a session may see, which is what a review is for.
|
|
383
|
+
`${STATE_DIR}/config/context/`,
|
|
337
384
|
/* The two entries the AGENT authors on its own initiative, and the reason `versioned` is not read as
|
|
338
385
|
* config-only. Both are the sandbox acting outward: a draft publishes words under the owner's name,
|
|
339
386
|
* a workspace extension is code that runs in the app and can serve HTTP with the workspace under
|
package/src/workspace-state.ts
CHANGED
|
@@ -135,7 +135,7 @@ const STATE_FILES = [
|
|
|
135
135
|
* withSecretVault keeps credential VALUES off /work entirely, the manifest holds `__intentic_vaulted__`
|
|
136
136
|
* where one used to be, reads rehydrate so no caller noticed, and main.ts sweeps a hand-written value out at
|
|
137
137
|
* boot. What is left is the SHAPE of a connection: a kind, a URL, a username, a purpose, which permissions a
|
|
138
|
-
* connected
|
|
138
|
+
* connected device was granted.
|
|
139
139
|
*
|
|
140
140
|
* WHICH KEYS THOSE ARE IS DERIVED, not listed a second time: `echo` already answers "what of this config may
|
|
141
141
|
* a browser see", and the credential keys are exactly its complement (capabilities/secret-fields.ts). A kind
|
|
@@ -149,7 +149,7 @@ const STATE_FILES = [
|
|
|
149
149
|
* one credential apiece, the shape personas.json has had all along, for the same reason.
|
|
150
150
|
*
|
|
151
151
|
* `versioned`, which is the point. Connecting this sandbox to a deployment orchestrator, or granting a
|
|
152
|
-
* connected
|
|
152
|
+
* connected device shell and screen control, is the largest change anyone makes to what it can DO, and it
|
|
153
153
|
* left a diff nowhere. One consequence worth stating rather than discovering: an identifier that pairs with a
|
|
154
154
|
* credential. Komodo's api key beside its api secret, which its own connector card calls "like a database
|
|
155
155
|
* user", is echoed, and therefore lands in the diff exactly as a database username would. */
|
|
@@ -738,6 +738,12 @@ const STATE_FILES = [
|
|
|
738
738
|
* behaves, it holds no credential, and it belongs in a pull request, which is also what makes it
|
|
739
739
|
* searchable, since every versioned entry already is. */
|
|
740
740
|
{ path: ".intentic/config/personas/", invalidates: ["personas"], portability: "carry", versioned: true },
|
|
741
|
+
|
|
742
|
+
/* THE CONTEXT SHELVES, one JSON file per shelf (schemas/context.ts): which part of the workspace a
|
|
743
|
+
* conversation opened on it carries. `versioned` and `carry` on the persona card's own argument, a shelf is
|
|
744
|
+
* a list of item ids and holds no credential, and which repositories a session can see is exactly the kind
|
|
745
|
+
* of decision that belongs in a pull request. */
|
|
746
|
+
{ path: ".intentic/config/context/", invalidates: ["context"], portability: "carry", versioned: true },
|
|
741
747
|
] as const satisfies readonly WorkspaceStateFile[];
|
|
742
748
|
|
|
743
749
|
export const WORKSPACE_STATE_FILES: readonly WorkspaceStateFile[] = STATE_FILES;
|
|
@@ -955,7 +961,7 @@ export const isReportedManifest = (relPath: string): boolean => REPORTED_MANIFES
|
|
|
955
961
|
* and all of `identity` and `secrets` is in. That is not an accident of the grouping, it is a different question
|
|
956
962
|
*, "would showing the bytes hand someone something" rather than "what kind of thing is this", so it keeps an
|
|
957
963
|
* explicit list, just one that now names the folder each entry lives in. */
|
|
958
|
-
const LOCKED_STATE_ENTRIES: ReadonlySet<string> = new Set([
|
|
964
|
+
export const LOCKED_STATE_ENTRIES: ReadonlySet<string> = new Set([
|
|
959
965
|
"identity/owner.json",
|
|
960
966
|
"identity/members.json",
|
|
961
967
|
"identity/control-tokens.json",
|
|
@@ -971,28 +977,59 @@ const LOCKED_STATE_ENTRIES: ReadonlySet<string> = new Set([
|
|
|
971
977
|
"claude.json",
|
|
972
978
|
]);
|
|
973
979
|
|
|
974
|
-
/*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
* `
|
|
980
|
+
/* WHERE THE CLI'S PLAN FILES LAND, workspace-relative — and the one thing inside a locked entry that is not
|
|
981
|
+
* the sandbox's own state, which is why it is declared here rather than beside the card that draws it.
|
|
982
|
+
*
|
|
983
|
+
* `~/.claude/plans` is a symlink onto this directory (sessions/session-store.ts links the SDK's conversation
|
|
984
|
+
* state onto the workspace volume), so a plan file has a HARNESS-OWNED address: no guessing whether prose is a
|
|
985
|
+
* plan, no threshold on length, the path says so.
|
|
986
|
+
*
|
|
987
|
+
* What lands there is a DOCUMENT ADDRESSED TO THE READER. `records/sessions` is locked because it holds the
|
|
988
|
+
* provider's own conversation state; a plan is the opposite of that — the chat renders its full text into the
|
|
989
|
+
* card asking the reader to approve it, so opening the file publishes nothing the transcript had not. Locked
|
|
990
|
+
* anyway, the one link the card offers into the workspace landed on a padlock reading "it holds something only
|
|
991
|
+
* the sandbox itself uses", about a document the sandbox had just asked the reader to approve. */
|
|
992
|
+
export const PLAN_DOCUMENTS_DIR = `${STATE_DIR}/records/sessions/claude/plans`;
|
|
993
|
+
|
|
994
|
+
/* WHICH locked entry a workspace-root-relative path belongs to, or undefined for a path that is not in the
|
|
995
|
+
* control plane at all. Scoped deliberately tight, matching the guard: only the ROOT `.intentic` counts (a
|
|
996
|
+
* repo's own nested one is ordinary content) and only these entries within it, subtrees included, so a new
|
|
997
|
+
* provider dropped under `auth/` is covered without a second edit.
|
|
978
998
|
*
|
|
979
999
|
* The ROOT's own `.git` joins them. It is the pointer to the shadow history repo kept off the workspace so the
|
|
980
1000
|
* agent cannot rewrite its own past; a NESTED repo's `.git` is ordinary content and stays browsable.
|
|
981
1001
|
*
|
|
1002
|
+
* The ENTRY rather than a boolean, because the screen that reports a refusal has to say what the file holds and
|
|
1003
|
+
* where the thing inside it is actually managed, and it can only do that from the name of the entry the path
|
|
1004
|
+
* matched — never from the leaf, which is a Chromium cookie jar or a mint-fresh session id. Keeping that lookup
|
|
1005
|
+
* keyed on this function's answer is what stops the browser's sentences from drifting off the daemon's list, as
|
|
1006
|
+
* they did through the regrouping: every one of them was still keyed on `sessions` and `auth` while the rule
|
|
1007
|
+
* had moved to `records/sessions` and `secrets/auth`, so every locked file in the product fell through to the
|
|
1008
|
+
* generic sentence and lost its way out.
|
|
1009
|
+
*
|
|
982
1010
|
* Accepts either slash so a caller holding a platform path doesn't have to normalize first. */
|
|
983
|
-
export const
|
|
1011
|
+
export const lockedWorkspaceEntry = (relPath: string): string | undefined => {
|
|
984
1012
|
const segments = relPath.split(/[\\/]/).filter((segment) => segment !== "" && segment !== ".");
|
|
985
1013
|
if (segments[0] === ".git") {
|
|
986
|
-
return
|
|
1014
|
+
return ".git";
|
|
987
1015
|
}
|
|
988
1016
|
if (segments[0] !== STATE_DIR) {
|
|
989
|
-
return
|
|
1017
|
+
return undefined;
|
|
1018
|
+
}
|
|
1019
|
+
const rel = segments.join("/");
|
|
1020
|
+
if (rel === PLAN_DOCUMENTS_DIR || rel.startsWith(`${PLAN_DOCUMENTS_DIR}/`)) {
|
|
1021
|
+
return undefined;
|
|
990
1022
|
}
|
|
991
1023
|
// Two segments, which covers both spellings in the set above: a grouped entry (`secrets/auth`) matches as
|
|
992
1024
|
// written, and a bare root entry (`claude.json`) joins to itself because there is no second segment to add.
|
|
993
|
-
|
|
1025
|
+
const entry = segments.slice(1, 3).join("/");
|
|
1026
|
+
return LOCKED_STATE_ENTRIES.has(entry) ? entry : undefined;
|
|
994
1027
|
};
|
|
995
1028
|
|
|
1029
|
+
// Whether a path lands in that control plane, and so is refused by the file API and shown locked rather than
|
|
1030
|
+
// opened. The question every guard asks; the entry above is for the one screen that has to name it.
|
|
1031
|
+
export const isLockedWorkspacePath = (relPath: string): boolean => lockedWorkspaceEntry(relPath) !== undefined;
|
|
1032
|
+
|
|
996
1033
|
/* THE LOCKED ENTRIES THE ROOT REPO TRACKS, refused by the file API, and diffable anyway.
|
|
997
1034
|
*
|
|
998
1035
|
* `capabilities.json` is the only one today and the whole reason this exists. Both of its rules are right on
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
export declare const claudeContract: {
|
|
2
|
-
start: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
|
|
3
|
-
authorizeUrl: import("zod").ZodString;
|
|
4
|
-
verifier: import("zod").ZodString;
|
|
5
|
-
state: import("zod").ZodString;
|
|
6
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
7
|
-
exchange: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodObject<{
|
|
8
|
-
code: import("zod").ZodString;
|
|
9
|
-
verifier: import("zod").ZodString;
|
|
10
|
-
state: import("zod").ZodString;
|
|
11
|
-
label: import("zod").ZodOptional<import("zod").ZodString>;
|
|
12
|
-
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
13
|
-
id: import("zod").ZodString;
|
|
14
|
-
label: import("zod").ZodString;
|
|
15
|
-
email: import("zod").ZodOptional<import("zod").ZodString>;
|
|
16
|
-
organization: import("zod").ZodOptional<import("zod").ZodString>;
|
|
17
|
-
scope: import("zod").ZodOptional<import("zod").ZodString>;
|
|
18
|
-
connectedAt: import("zod").ZodNumber;
|
|
19
|
-
needsReauth: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
20
|
-
detail: import("zod").ZodOptional<import("zod").ZodString>;
|
|
21
|
-
usage: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
22
|
-
windows: import("zod").ZodArray<import("zod").ZodObject<{
|
|
23
|
-
kind: import("zod").ZodString;
|
|
24
|
-
label: import("zod").ZodOptional<import("zod").ZodString>;
|
|
25
|
-
utilization: import("zod").ZodNumber;
|
|
26
|
-
resetsAt: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
27
|
-
gates: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"all">, import("zod").ZodLiteral<"none">, import("zod").ZodObject<{
|
|
28
|
-
models: import("zod").ZodArray<import("zod").ZodString>;
|
|
29
|
-
}, import("zod/v4/core").$strip>]>;
|
|
30
|
-
}, import("zod/v4/core").$strip>>;
|
|
31
|
-
measuredAt: import("zod").ZodNumber;
|
|
32
|
-
}, import("zod/v4/core").$strip>>;
|
|
33
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
34
|
-
accounts: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodObject<{
|
|
35
|
-
force: import("zod").ZodDefault<import("zod").ZodCodec<import("zod").ZodString, import("zod").ZodBoolean>>;
|
|
36
|
-
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
37
|
-
accounts: import("zod").ZodArray<import("zod").ZodObject<{
|
|
38
|
-
id: import("zod").ZodString;
|
|
39
|
-
label: import("zod").ZodString;
|
|
40
|
-
email: import("zod").ZodOptional<import("zod").ZodString>;
|
|
41
|
-
organization: import("zod").ZodOptional<import("zod").ZodString>;
|
|
42
|
-
scope: import("zod").ZodOptional<import("zod").ZodString>;
|
|
43
|
-
connectedAt: import("zod").ZodNumber;
|
|
44
|
-
needsReauth: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
45
|
-
detail: import("zod").ZodOptional<import("zod").ZodString>;
|
|
46
|
-
usage: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
47
|
-
windows: import("zod").ZodArray<import("zod").ZodObject<{
|
|
48
|
-
kind: import("zod").ZodString;
|
|
49
|
-
label: import("zod").ZodOptional<import("zod").ZodString>;
|
|
50
|
-
utilization: import("zod").ZodNumber;
|
|
51
|
-
resetsAt: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
52
|
-
gates: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"all">, import("zod").ZodLiteral<"none">, import("zod").ZodObject<{
|
|
53
|
-
models: import("zod").ZodArray<import("zod").ZodString>;
|
|
54
|
-
}, import("zod/v4/core").$strip>]>;
|
|
55
|
-
}, import("zod/v4/core").$strip>>;
|
|
56
|
-
measuredAt: import("zod").ZodNumber;
|
|
57
|
-
}, import("zod/v4/core").$strip>>;
|
|
58
|
-
}, import("zod/v4/core").$strip>>;
|
|
59
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
60
|
-
rename: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodObject<{
|
|
61
|
-
id: import("zod").ZodString;
|
|
62
|
-
label: import("zod").ZodString;
|
|
63
|
-
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
64
|
-
id: import("zod").ZodString;
|
|
65
|
-
label: import("zod").ZodString;
|
|
66
|
-
email: import("zod").ZodOptional<import("zod").ZodString>;
|
|
67
|
-
organization: import("zod").ZodOptional<import("zod").ZodString>;
|
|
68
|
-
scope: import("zod").ZodOptional<import("zod").ZodString>;
|
|
69
|
-
connectedAt: import("zod").ZodNumber;
|
|
70
|
-
needsReauth: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
71
|
-
detail: import("zod").ZodOptional<import("zod").ZodString>;
|
|
72
|
-
usage: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
73
|
-
windows: import("zod").ZodArray<import("zod").ZodObject<{
|
|
74
|
-
kind: import("zod").ZodString;
|
|
75
|
-
label: import("zod").ZodOptional<import("zod").ZodString>;
|
|
76
|
-
utilization: import("zod").ZodNumber;
|
|
77
|
-
resetsAt: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
78
|
-
gates: import("zod").ZodUnion<readonly [import("zod").ZodLiteral<"all">, import("zod").ZodLiteral<"none">, import("zod").ZodObject<{
|
|
79
|
-
models: import("zod").ZodArray<import("zod").ZodString>;
|
|
80
|
-
}, import("zod/v4/core").$strip>]>;
|
|
81
|
-
}, import("zod/v4/core").$strip>>;
|
|
82
|
-
measuredAt: import("zod").ZodNumber;
|
|
83
|
-
}, import("zod/v4/core").$strip>>;
|
|
84
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
85
|
-
disconnect: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodObject<{
|
|
86
|
-
id: import("zod").ZodString;
|
|
87
|
-
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
88
|
-
ok: import("zod").ZodLiteral<true>;
|
|
89
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
90
|
-
};
|
|
91
|
-
//# sourceMappingURL=claude.contract.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"claude.contract.d.ts","sourceRoot":"","sources":["../../src/contracts/claude.contract.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,cAAc;IACvB,KAAK;;;;;IASL,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;IAYR,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;IAUR,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;IASN,UAAU;;;;;CASb,CAAC"}
|