@saadnvd1/agent-os 0.3.488 → 0.3.493
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 +32 -2
- package/components/Chat/ChatPanel.tsx +8 -2
- package/components/Chat/Tools.tsx +9 -3
- package/components/SessionList/OrchestratorPinRow.test.ts +2 -0
- package/components/SessionList/OrchestratorPinRow.tsx +36 -0
- package/components/SessionList/RowContext.tsx +4 -0
- package/components/SessionList/SessionRow.tsx +17 -4
- package/components/SessionList/SessionShelves.tsx +8 -2
- package/components/SessionList/SidebarBody.tsx +17 -1
- package/components/SessionList/index.tsx +4 -0
- package/components/SessionList/useSidebarData.ts +64 -18
- package/data/chat/useChat.ts +12 -1
- package/data/push/topics.test.ts +9 -0
- package/data/push/topics.ts +3 -14
- package/lib/chat/bodyCache.ts +69 -0
- package/lib/chat/store.ts +89 -0
- package/lib/chat/worker/host.ts +11 -3
- package/lib/orchestrator/brief.ts +1 -1
- package/lib/orchestrator/shell-guard.ts +536 -0
- package/lib/orchestrator/shell-parse.ts +289 -0
- package/lib/orchestrator/tool-names.ts +13 -22
- package/lib/orchestrator/write-guard.ts +54 -57
- package/lib/sidebar/queued.ts +3 -0
- package/lib/sidebar/rollup.ts +58 -0
- package/lib/sidebar/shelves.ts +24 -0
- package/lib/tasks/queue.ts +3 -0
- package/package.json +1 -1
- package/stores/sidebarUi.ts +15 -3
- package/lib/orchestrator/git-guard.ts +0 -201
package/stores/sidebarUi.ts
CHANGED
|
@@ -11,12 +11,19 @@ interface Remembered {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const sidebarUi = proxy<
|
|
14
|
-
Remembered & {
|
|
14
|
+
Remembered & {
|
|
15
|
+
query: string;
|
|
16
|
+
donePages: number;
|
|
17
|
+
hydrated: boolean;
|
|
18
|
+
// Filter to one orchestrator's work; session-only, resets on reload.
|
|
19
|
+
orchestratorId: string | null;
|
|
20
|
+
}
|
|
15
21
|
>({
|
|
16
22
|
workspaceId: null,
|
|
17
23
|
projectId: null,
|
|
18
|
-
doneCollapsed:
|
|
24
|
+
doneCollapsed: true,
|
|
19
25
|
query: "",
|
|
26
|
+
orchestratorId: null,
|
|
20
27
|
donePages: 0,
|
|
21
28
|
hydrated: false,
|
|
22
29
|
});
|
|
@@ -35,7 +42,7 @@ export const sidebarUiActions = {
|
|
|
35
42
|
const saved = read();
|
|
36
43
|
sidebarUi.workspaceId = saved.workspaceId ?? null;
|
|
37
44
|
sidebarUi.projectId = saved.projectId ?? null;
|
|
38
|
-
sidebarUi.doneCollapsed =
|
|
45
|
+
sidebarUi.doneCollapsed = saved.doneCollapsed ?? true;
|
|
39
46
|
sidebarUi.hydrated = true;
|
|
40
47
|
subscribe(sidebarUi, () => {
|
|
41
48
|
try {
|
|
@@ -52,12 +59,17 @@ export const sidebarUiActions = {
|
|
|
52
59
|
setWorkspace: (id: string | null) => {
|
|
53
60
|
sidebarUi.workspaceId = id;
|
|
54
61
|
sidebarUi.projectId = null;
|
|
62
|
+
sidebarUi.orchestratorId = null;
|
|
55
63
|
sidebarUi.donePages = 0;
|
|
56
64
|
},
|
|
57
65
|
setProject: (id: string | null) => {
|
|
58
66
|
sidebarUi.projectId = id;
|
|
59
67
|
sidebarUi.donePages = 0;
|
|
60
68
|
},
|
|
69
|
+
setOrchestrator: (id: string | null) => {
|
|
70
|
+
sidebarUi.orchestratorId = id;
|
|
71
|
+
sidebarUi.donePages = 0;
|
|
72
|
+
},
|
|
61
73
|
setQuery: (query: string) => {
|
|
62
74
|
sidebarUi.query = query;
|
|
63
75
|
},
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The shell commands an orchestrator may run, read before the shell sees
|
|
3
|
-
* them. Its permissions allow `aos` commands and `git`; this makes sure each
|
|
4
|
-
* is one plain command (no `;`, `&&`, pipes, redirects, substitutions or
|
|
5
|
-
* globs a shell would act on), and that a git command works only in one of
|
|
6
|
-
* its own quick changes' worktrees (`-C <path>`), does nothing that runs
|
|
7
|
-
* other code or writes outside it, and never pushes: quick_pr pushes, and
|
|
8
|
-
* never to the default branch.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// What git may do in a quick worktree: look, stage, undo. Committing is
|
|
12
|
-
// quick_pr's alone: it runs with hooks off, where a shell commit would run
|
|
13
|
-
// whatever the project (or the orchestrator's own edit) put in its hooks.
|
|
14
|
-
const GIT_ALLOWED = new Set([
|
|
15
|
-
"status",
|
|
16
|
-
"diff",
|
|
17
|
-
"log",
|
|
18
|
-
"show",
|
|
19
|
-
"add",
|
|
20
|
-
"rm",
|
|
21
|
-
"restore",
|
|
22
|
-
"reset",
|
|
23
|
-
"rev-parse",
|
|
24
|
-
"ls-files",
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
// Git config can run programs (core.fsmonitor, diff.external, filter and
|
|
28
|
-
// textconv drivers), so git runs only at the worktree's own root, where it
|
|
29
|
-
// finds the real git dir, never in a folder holding a repository the
|
|
30
|
-
// orchestrator wrote (the write guard refuses HEAD, .git/ and .gitattributes
|
|
31
|
-
// inside quick worktrees). A .gitattributes already committed can name a
|
|
32
|
-
// driver, but a driver's command lives in config, which only a repository
|
|
33
|
-
// the orchestrator can't write defines.
|
|
34
|
-
export const GIT_LAYOUT_FILE =
|
|
35
|
-
/(^|\/)(HEAD|\.gitattributes)$|(^|\/)\.git(\/|$)/i;
|
|
36
|
-
|
|
37
|
-
// `aos` commands carry free text (notify, send): only what a shell would
|
|
38
|
-
// chain, redirect or substitute is refused, not ? ! # * or a plain $.
|
|
39
|
-
export function aosRefusal(command: string): string | null {
|
|
40
|
-
const bad =
|
|
41
|
-
"use one plain aos command: no ;, &&, pipes, redirects, backticks or $( ) outside single quotes";
|
|
42
|
-
let q: string | null = null;
|
|
43
|
-
for (let i = 0; i < command.length; i++) {
|
|
44
|
-
const c = command[i];
|
|
45
|
-
if (q === "'") {
|
|
46
|
-
if (c === "'") q = null;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (c === "\\") {
|
|
50
|
-
// zsh deletes a backslash-newline before it parses, joining $ to what
|
|
51
|
-
// follows; and its ${(e)...} evaluates what an escaped \$ or \` left.
|
|
52
|
-
const n = command[i + 1] ?? "";
|
|
53
|
-
if (/[\n\r]/.test(n) || (q === '"' && /[$`]/.test(n)))
|
|
54
|
-
return `Refused: ${bad}.`;
|
|
55
|
-
i++;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (c === "`") return `Refused: ${bad}.`;
|
|
59
|
-
// After a $ only a plain variable name (or $?, $#, $@, $*, $-, a digit)
|
|
60
|
-
// or a literal $ may follow: ${..}, $[..], $'..' and the like evaluate
|
|
61
|
-
// or quote differently in the shell than here.
|
|
62
|
-
if (c === "$") {
|
|
63
|
-
const n = command[i + 1] ?? "";
|
|
64
|
-
if (!/^[\sA-Za-z0-9_?#@*-]?$/.test(n) && !(q === '"' && n === '"'))
|
|
65
|
-
return `Refused: ${bad}.`;
|
|
66
|
-
}
|
|
67
|
-
if (q === '"') {
|
|
68
|
-
if (c === '"') q = null;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (c === "'" || c === '"') q = c;
|
|
72
|
-
else if (/[;&|<>()\n\r]/.test(c)) return `Refused: ${bad}.`;
|
|
73
|
-
}
|
|
74
|
-
return q ? "Refused: an unclosed quote." : null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Options that write a file somewhere else, read a command to run, or read
|
|
78
|
-
// outside the repository. Git accepts any unique prefix of a long option
|
|
79
|
-
// (--ext-di, --textc), so this refuses the prefixes, not the spellings.
|
|
80
|
-
const GIT_REFUSED_ARG = /^--(ou|ex|te|no-i)/;
|
|
81
|
-
|
|
82
|
-
// A shell word, with its quotes taken off, or why the command isn't one
|
|
83
|
-
// plain command.
|
|
84
|
-
export function shellWords(command: string): string[] | string {
|
|
85
|
-
const words: string[] = [];
|
|
86
|
-
let word: string | null = null;
|
|
87
|
-
const plain =
|
|
88
|
-
"use one plain command: no ;, &&, ||, pipes, redirects, $, backticks, globs or comments outside quotes (quote anything with those)";
|
|
89
|
-
for (let i = 0; i < command.length; i++) {
|
|
90
|
-
const c = command[i];
|
|
91
|
-
if (c === " " || c === "\t") {
|
|
92
|
-
if (word !== null) words.push(word);
|
|
93
|
-
word = null;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (c === "'") {
|
|
97
|
-
const end = command.indexOf("'", i + 1);
|
|
98
|
-
if (end < 0) return "an unclosed ' quote";
|
|
99
|
-
word = (word ?? "") + command.slice(i + 1, end);
|
|
100
|
-
i = end;
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (c === '"') {
|
|
104
|
-
let text = "";
|
|
105
|
-
let j = i + 1;
|
|
106
|
-
for (; j < command.length && command[j] !== '"'; j++) {
|
|
107
|
-
const d = command[j];
|
|
108
|
-
if (d === "$" || d === "`") return plain;
|
|
109
|
-
if (d === "\\" && j + 1 < command.length) {
|
|
110
|
-
const n = command[++j];
|
|
111
|
-
text += /["\\$`\n]/.test(n) ? n : `\\${n}`;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
text += d;
|
|
115
|
-
}
|
|
116
|
-
if (j >= command.length) return 'an unclosed " quote';
|
|
117
|
-
word = (word ?? "") + text;
|
|
118
|
-
i = j;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (c === "\\") {
|
|
122
|
-
if (i + 1 >= command.length || command[i + 1] === "\n") return plain;
|
|
123
|
-
word = (word ?? "") + command[++i];
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (/[;&|<>()$`\n\r*?[\]{}!#]/.test(c)) return plain;
|
|
127
|
-
// zsh expands a word starting with = or ~name; only ~ and ~/ are read
|
|
128
|
-
// (as the home folder), by the caller.
|
|
129
|
-
if (word === null && c === "=") return plain;
|
|
130
|
-
if (word === null && c === "~" && !/^~($|[\s/])/.test(command.slice(i)))
|
|
131
|
-
return plain;
|
|
132
|
-
word = (word ?? "") + c;
|
|
133
|
-
}
|
|
134
|
-
if (word !== null) words.push(word);
|
|
135
|
-
return words;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export type GitCommand =
|
|
139
|
-
| { refusal: string }
|
|
140
|
-
| { refusal: null; dir: string | null; sub: string | null };
|
|
141
|
-
|
|
142
|
-
// A shell command read for the guard: why it's refused outright, or, for a
|
|
143
|
-
// git command, the folder it runs in (from -C) to check against its quick
|
|
144
|
-
// worktrees. `dir` and `sub` are null for a command that isn't git.
|
|
145
|
-
export function readCommand(command: unknown): GitCommand {
|
|
146
|
-
if (typeof command !== "string" || !command.trim())
|
|
147
|
-
return { refusal: "No command to check." };
|
|
148
|
-
if (/^aos(\s|$)/.test(command.trim())) {
|
|
149
|
-
const refusal = aosRefusal(command.trim());
|
|
150
|
-
return refusal ? { refusal } : { refusal: null, dir: null, sub: null };
|
|
151
|
-
}
|
|
152
|
-
const words = shellWords(command.trim());
|
|
153
|
-
if (typeof words === "string") return { refusal: `Refused: ${words}.` };
|
|
154
|
-
if (words[0] !== "git") {
|
|
155
|
-
// `VAR=value git …` sets an environment variable the shell would act on
|
|
156
|
-
// (GIT_DIR, GIT_INDEX_FILE, GIT_WORK_TREE), which can redirect git
|
|
157
|
-
// outside its quick worktree.
|
|
158
|
-
if (words[0].includes("="))
|
|
159
|
-
return {
|
|
160
|
-
refusal:
|
|
161
|
-
"Refused: no inline VAR=value before a command; environment variables can point git outside your worktree.",
|
|
162
|
-
};
|
|
163
|
-
return { refusal: null, dir: null, sub: null };
|
|
164
|
-
}
|
|
165
|
-
const usage =
|
|
166
|
-
"git runs only in one of your quick changes' worktrees, as `git -C <its path> <command>`";
|
|
167
|
-
let dir: string | null = null;
|
|
168
|
-
let i = 1;
|
|
169
|
-
for (; i < words.length; i++) {
|
|
170
|
-
const w = words[i];
|
|
171
|
-
if (w === "-C") {
|
|
172
|
-
if (dir !== null || i + 1 >= words.length)
|
|
173
|
-
return { refusal: `Refused: ${usage}, with one -C.` };
|
|
174
|
-
dir = words[++i];
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
if (w === "--no-pager" || w === "--no-optional-locks") continue;
|
|
178
|
-
if (w.startsWith("-"))
|
|
179
|
-
return {
|
|
180
|
-
refusal: `Refused: git's ${w} option isn't allowed here; ${usage}.`,
|
|
181
|
-
};
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
if (dir === null) return { refusal: `Refused: ${usage}.` };
|
|
185
|
-
const sub = words[i] ?? null;
|
|
186
|
-
if (sub === "push")
|
|
187
|
-
return {
|
|
188
|
-
refusal:
|
|
189
|
-
"Refused: quick_pr pushes a quick change's branch and opens its PR; nothing pushes from the shell, and nothing ever pushes to the default branch.",
|
|
190
|
-
};
|
|
191
|
-
if (!sub || !GIT_ALLOWED.has(sub))
|
|
192
|
-
return {
|
|
193
|
-
refusal: `Refused: git ${sub ?? ""} isn't allowed here. In a quick worktree git may only ${[...GIT_ALLOWED].join(", ")}; quick_pr commits, pushes and opens the PR.`,
|
|
194
|
-
};
|
|
195
|
-
const bad = words.slice(i + 1).find((w) => GIT_REFUSED_ARG.test(w));
|
|
196
|
-
if (bad)
|
|
197
|
-
return {
|
|
198
|
-
refusal: `Refused: git's ${bad} option writes or runs something outside the worktree.`,
|
|
199
|
-
};
|
|
200
|
-
return { refusal: null, dir, sub };
|
|
201
|
-
}
|