@evandu/super-pi 0.0.0-stage → 0.1.0
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 +79 -2
- package/agents/coder.md +10 -0
- package/agents/critic.md +20 -0
- package/agents/patcher.md +23 -0
- package/agents/planner.md +18 -0
- package/agents/reviewer.md +13 -0
- package/agents/scout.md +23 -0
- package/agents/super-pi-task/core.ts +485 -0
- package/agents/super-pi-task/index.ts +141 -0
- package/agents/verifier.md +21 -0
- package/bin/super-pi.js +17 -0
- package/dist/client/assets/index-BMQsEdio.js +24 -0
- package/dist/client/assets/index-CsbZzuMy.css +1 -0
- package/dist/client/index.html +14 -0
- package/dist/extension.d.ts +15 -0
- package/dist/extension.js +111 -0
- package/dist/server/agents.d.ts +177 -0
- package/dist/server/agents.js +484 -0
- package/dist/server/application/catalog-service.d.ts +44 -0
- package/dist/server/application/catalog-service.js +94 -0
- package/dist/server/application/conversation-service.d.ts +73 -0
- package/dist/server/application/conversation-service.js +202 -0
- package/dist/server/application/extension-ui-service.d.ts +28 -0
- package/dist/server/application/extension-ui-service.js +73 -0
- package/dist/server/application/run-service.d.ts +115 -0
- package/dist/server/application/run-service.js +341 -0
- package/dist/server/application/workspace-service.d.ts +45 -0
- package/dist/server/application/workspace-service.js +106 -0
- package/dist/server/command-handler.d.ts +22 -0
- package/dist/server/command-handler.js +137 -0
- package/dist/server/files.d.ts +202 -0
- package/dist/server/files.js +629 -0
- package/dist/server/http-server.d.ts +20 -0
- package/dist/server/http-server.js +313 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +103 -0
- package/dist/server/pi-command.d.ts +65 -0
- package/dist/server/pi-command.js +183 -0
- package/dist/server/runtime.d.ts +819 -0
- package/dist/server/runtime.js +1470 -0
- package/dist/server/server-context.d.ts +46 -0
- package/dist/server/server-context.js +1 -0
- package/dist/server/store.d.ts +424 -0
- package/dist/server/store.js +1121 -0
- package/dist/server/subagent-runs.d.ts +142 -0
- package/dist/server/subagent-runs.js +315 -0
- package/dist/server/subagents.d.ts +194 -0
- package/dist/server/subagents.js +542 -0
- package/dist/server/task-tool.d.ts +196 -0
- package/dist/server/task-tool.js +285 -0
- package/dist/server/tool-calls.d.ts +62 -0
- package/dist/server/tool-calls.js +141 -0
- package/dist/server/types.d.ts +920 -0
- package/dist/server/types.js +11 -0
- package/dist/server/upload.d.ts +37 -0
- package/dist/server/upload.js +169 -0
- package/dist/server/ws-server.d.ts +45 -0
- package/dist/server/ws-server.js +116 -0
- package/package.json +115 -4
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Above this a read is refused rather than served. 5MB is roughly a large source file or a long document,
|
|
3
|
+
* and it is deliberately far below the point where an editor tab stops being useful: this panel is for
|
|
4
|
+
* reading and editing code and markdown, and a file too big for that has not become more editable by
|
|
5
|
+
* being loaded whole. The size is checked from `stat` BEFORE the read, so an oversized file costs one
|
|
6
|
+
* `stat` rather than a full buffer.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_FILE_BYTES: number;
|
|
9
|
+
/** Cap on one directory listing, so a `node_modules` or a generated assets folder cannot produce a
|
|
10
|
+
* multi-megabyte response. The count is reported as `truncated` rather than applied silently. */
|
|
11
|
+
export declare const MAX_DIR_ENTRIES = 2000;
|
|
12
|
+
/** Cap on a recursive filename search, for the same reason: a scan of a large repository has to end
|
|
13
|
+
* somewhere. The UI says the list was cut rather than pretending it is complete. */
|
|
14
|
+
export declare const MAX_SEARCH_RESULTS = 500;
|
|
15
|
+
/** How deep the recursive search walks. Twelve levels is past any real source tree; the cap is what
|
|
16
|
+
* turns a pathologically deep dependency tree from a hang into a result. */
|
|
17
|
+
export declare const MAX_SEARCH_DEPTH = 12;
|
|
18
|
+
/**
|
|
19
|
+
* Extensions this panel OPENS without needing a sniff. It is a hint used only to label a file in the tree
|
|
20
|
+
* before it is read, and never as a permission: a `.png` is refused by name even if a sniffing pass would
|
|
21
|
+
* have liked it, and a `.txt` holding PNG bytes is still refused by the sniff.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TEXT_EXTENSIONS: Set<string>;
|
|
24
|
+
/** The HTTP failure, carried as a status so the route can render it without a second switch. `details` is
|
|
25
|
+
* for the cases where the CLIENT can act on more than the sentence - today that is a conflict, where the
|
|
26
|
+
* current etag is what lets the UI offer a deliberate "overwrite anyway" instead of a blind one. */
|
|
27
|
+
export declare class FilesError extends Error {
|
|
28
|
+
readonly status: number;
|
|
29
|
+
readonly code?: string | undefined;
|
|
30
|
+
readonly details?: unknown;
|
|
31
|
+
constructor(status: number, message: string, code?: string | undefined, details?: unknown);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether `candidate` is `root` itself or something under it.
|
|
35
|
+
*
|
|
36
|
+
* THE COMPARISON IS NOT `startsWith(root)`, because that is the traversal bug this whole file exists to
|
|
37
|
+
* avoid: with root `/home/u/proj`, the string `/home/u/proj-secrets` also starts with `/home/u/proj`.
|
|
38
|
+
* `relative()` asks the question in the form that can be answered exactly - it returns a path RELATIVE to
|
|
39
|
+
* the root, so "escaped" becomes "the relative path begins by going up", which is true for
|
|
40
|
+
* `/home/u/proj/../etc` and false for `/home/u/proj/etc`. `isAbsolute(relative)` catches the case
|
|
41
|
+
* `relative` cannot express: two different Windows drives, where there is no relative path between them
|
|
42
|
+
* and it hands back an absolute one.
|
|
43
|
+
*/
|
|
44
|
+
export declare function contains(root: string, candidate: string): boolean;
|
|
45
|
+
/** A guard bound to one workspace root. `resolveInsideRoot` is the one-shot form and is what callers are
|
|
46
|
+
* meant to use; the factory exists for the two places that check many paths against the SAME root in one
|
|
47
|
+
* request, so the canonical form of the root is resolved once instead of once per entry. */
|
|
48
|
+
export declare function createRootGuard(root: string): (requested: string | null | undefined) => Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* THE CONTAINMENT CHECK. Every read, write, listing and search in this file goes through it, and the
|
|
51
|
+
* route layer is not allowed to do its own path arithmetic.
|
|
52
|
+
*
|
|
53
|
+
* `requested` arrives already percent-decoded, because `URLSearchParams` decodes once when the query is
|
|
54
|
+
* parsed. That is the right number of decodes: `%2e%2e%2f` is `../` by the time it gets here and is
|
|
55
|
+
* refused by the lexical test, while decoding a SECOND time would turn the harmless literal `%2e%2e%2f`
|
|
56
|
+
* into a live traversal - inventing an attack the URL parser does not actually have. The route decodes
|
|
57
|
+
* nothing further and never passes the raw query string down.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveInsideRoot(root: string, requested: string | null | undefined): Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* The identity of a file's CURRENT bytes, as far as a filesystem can cheaply say.
|
|
62
|
+
*
|
|
63
|
+
* Built from size and the nanosecond modification time rather than a content hash: hashing every file on
|
|
64
|
+
* every read would make opening a tab cost a full pass over the file, and the two fields together already
|
|
65
|
+
* change on any edit a human would call an edit. `bigint: true` is what makes this exact - the
|
|
66
|
+
* millisecond float `stat` reports by default rounds two edits inside the same millisecond into one
|
|
67
|
+
* value, and an external-change check that misses a change is worse than no check at all, because the
|
|
68
|
+
* user has been told it is safe to save.
|
|
69
|
+
*/
|
|
70
|
+
export declare function fileEtag(path: string): Promise<string>;
|
|
71
|
+
export type FileMeta = {
|
|
72
|
+
etag: string;
|
|
73
|
+
size: number;
|
|
74
|
+
modifiedAt: string;
|
|
75
|
+
};
|
|
76
|
+
export declare function fileMeta(path: string): Promise<FileMeta>;
|
|
77
|
+
export declare function looksBinary(sample: Uint8Array): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the NAME alone says this is binary.
|
|
80
|
+
*
|
|
81
|
+
* `endsWith` is the whole test, and the dot is what makes it a suffix test rather than a substring one:
|
|
82
|
+
* `"tool.bin".endsWith(".bin")` is true and `"foobin".endsWith(".bin")` is false, because the dot has to be
|
|
83
|
+
* one of the characters being matched. An earlier version added a "the character before the suffix must be
|
|
84
|
+
* a dot" guard on top of this, which was wrong - it compared the index of the dot AGAINST ITSELF minus
|
|
85
|
+
* one, so `"photo.png"` was checked at the `o` and came back false. Every binary file was therefore
|
|
86
|
+
* reaching the byte sniff instead of being refused by name, and the tests only ever passed because the
|
|
87
|
+
* fixture binaries also contained NUL bytes. A check that is dead code while a second check quietly does
|
|
88
|
+
* its job is exactly the kind of thing that survives review, which is why `files-api.test.mjs` asserts
|
|
89
|
+
* the extension path with a fixture whose CONTENT is text.
|
|
90
|
+
*/
|
|
91
|
+
export declare function hasBinaryExtension(name: string): boolean;
|
|
92
|
+
/** Whether a file is worth putting in an editor tab at all, decided from its NAME alone, for the tree's
|
|
93
|
+
* own labelling. The authoritative answer comes from `readWorkspaceFile`, which reads bytes. */
|
|
94
|
+
export declare function isTextName(name: string): boolean;
|
|
95
|
+
/** What a caller is told about one entry in a directory. */
|
|
96
|
+
export type DirEntry = {
|
|
97
|
+
/** The path RELATIVE to the workspace root, always `/`-separated, never with a leading slash. This is
|
|
98
|
+
* the form every client sends back, so there is exactly one spelling of "a file" on the wire and no
|
|
99
|
+
* round trip can invent a second one. */
|
|
100
|
+
path: string;
|
|
101
|
+
name: string;
|
|
102
|
+
kind: "directory" | "file" | "symlink";
|
|
103
|
+
/** Whether a file is worth opening, decided from its name. `null` for a directory. */
|
|
104
|
+
text: boolean | null;
|
|
105
|
+
/** Set when this entry is a link, and `kind` becomes `symlink` when it points OUT of the workspace. The
|
|
106
|
+
* tree can then say "cannot be opened" before the request comes back 403 rather than after. */
|
|
107
|
+
symlink?: boolean;
|
|
108
|
+
/** Set when a link points outside: the containment check's own verdict, surfaced rather than swallowed. */
|
|
109
|
+
outside?: boolean;
|
|
110
|
+
};
|
|
111
|
+
export type DirListing = {
|
|
112
|
+
/** The directory that was listed, relative to the root. `""` is the root itself. */
|
|
113
|
+
path: string;
|
|
114
|
+
/** Its parent, or `null` at the root - the tree uses this to decide whether to draw an "up" affordance. */
|
|
115
|
+
parent: string | null;
|
|
116
|
+
entries: DirEntry[];
|
|
117
|
+
truncated: boolean;
|
|
118
|
+
};
|
|
119
|
+
/** The `/`-separated relative form used on the wire and in every comparison. */
|
|
120
|
+
export declare function toRelative(root: string, absolute: string): string;
|
|
121
|
+
/** One directory's contents. Directories sort before files, then case-insensitively by name with numeric
|
|
122
|
+
* ordering, so `src2` lands between `src` and `src10` rather than after every `srcN`. */
|
|
123
|
+
export declare function listDirectory(root: string, requested: string | null | undefined): Promise<DirListing>;
|
|
124
|
+
export type SearchHit = {
|
|
125
|
+
path: string;
|
|
126
|
+
name: string;
|
|
127
|
+
directory: string;
|
|
128
|
+
};
|
|
129
|
+
export type SearchResult = {
|
|
130
|
+
hits: SearchHit[];
|
|
131
|
+
truncated: boolean;
|
|
132
|
+
skipped: string[];
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* A recursive FILENAME search. Filename only, on purpose: content search is a different feature with
|
|
136
|
+
* different costs - it reads every candidate file, honours the size cap on each one, and it is the thing
|
|
137
|
+
* that turns a panel into a hang - and the requirement is explicit that this round does not need it.
|
|
138
|
+
*
|
|
139
|
+
* Every directory it descends into goes through the SAME guard as every other path here, so the search
|
|
140
|
+
* cannot be walked out of the workspace by a link, and it stops at `MAX_SEARCH_DEPTH` and
|
|
141
|
+
* `MAX_SEARCH_RESULTS` so a repository with a vendored dependency tree returns a truncated answer rather
|
|
142
|
+
* than never returning.
|
|
143
|
+
*/
|
|
144
|
+
export declare function searchByName(root: string, query: string): Promise<SearchResult>;
|
|
145
|
+
export type FileContents = {
|
|
146
|
+
path: string;
|
|
147
|
+
content: string;
|
|
148
|
+
meta: FileMeta;
|
|
149
|
+
/** Newline convention read off the bytes, so a CRLF file is saved back as CRLF instead of silently
|
|
150
|
+
* rewritten to LF - which on Windows is a whole-file diff the user did not ask for. */
|
|
151
|
+
eol: "\n" | "\r\n";
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Read one file, or refuse it with a reason a person can act on.
|
|
155
|
+
*
|
|
156
|
+
* ORDER MATTERS and is the whole reason the refusals are trustworthy: extension, then SIZE from `stat`,
|
|
157
|
+
* then a sniff, then a fatal decode. The size check precedes the read so an oversized file costs a `stat`
|
|
158
|
+
* rather than a buffer, and the sniff precedes the decode so binary bytes are never handed to
|
|
159
|
+
* `TextDecoder` as if they were text - a decode that "succeeds" on binary produces U+FFFD throughout,
|
|
160
|
+
* which is mojibake, and mojibake that then gets SAVED is how a text editor destroys a binary file.
|
|
161
|
+
*/
|
|
162
|
+
export declare function readWorkspaceFile(root: string, requested: string): Promise<FileContents>;
|
|
163
|
+
export type WriteOutcome = {
|
|
164
|
+
path: string;
|
|
165
|
+
meta: FileMeta;
|
|
166
|
+
created: false;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Write one EXISTING text file. No creation, no deletion, no rename, no directories.
|
|
170
|
+
*
|
|
171
|
+
* THE SCOPE CALL, with its reasoning, because it is a decision and not an omission:
|
|
172
|
+
*
|
|
173
|
+
* This panel's stated job is to BROWSE and EDIT what is in the workspace. Creation is refused even though
|
|
174
|
+
* the brief leaves it open, because creation is the operation where a mistake has no undo: a bad `path`
|
|
175
|
+
* parameter or a bug that loses one character writes a file the user never asked for and never has to
|
|
176
|
+
* clean up, whereas a refused write leaves nothing behind. Deletion is deferred by the brief and is
|
|
177
|
+
* deferred here for the same reason plus the one that matters more - it is irreversible.
|
|
178
|
+
*
|
|
179
|
+
* The symmetry is deliberate: create and delete are the two ends of the same axis, and shipping one
|
|
180
|
+
* without the other is how a "conservative, no surprises" surface becomes a half-built file manager.
|
|
181
|
+
*
|
|
182
|
+
* What IS refused regardless: a write to a file that does not exist (428), a write with no `baseEtag` at
|
|
183
|
+
* all (428 - without one the server cannot tell a save from a blind overwrite, and "may I overwrite" is
|
|
184
|
+
* not a question this endpoint is allowed to skip), a write whose `baseEtag` no longer matches the file
|
|
185
|
+
* on disk (409 `stale`), and a write that would push the file past the size cap.
|
|
186
|
+
*
|
|
187
|
+
* The write itself is a whole-file replace through a temporary file in the SAME directory and a rename,
|
|
188
|
+
* so an interrupted save cannot leave a half-written file where a whole one used to be. The temporary name
|
|
189
|
+
* is derived from the target's OWN name rather than living in the system temp directory, because a rename
|
|
190
|
+
* across filesystems is not atomic and would silently become a copy.
|
|
191
|
+
*/
|
|
192
|
+
export declare function writeWorkspaceFile(root: string, requested: string, content: string, baseEtag: string | null | undefined): Promise<WriteOutcome>;
|
|
193
|
+
/** Every `FilesError` carries the status it should be rendered with; anything else is a real fault and
|
|
194
|
+
* becomes a 500 rather than being dressed up as a refusal the user is asked to understand. */
|
|
195
|
+
export declare function describe(error: unknown): {
|
|
196
|
+
status: number;
|
|
197
|
+
body: {
|
|
198
|
+
error: string;
|
|
199
|
+
code?: string;
|
|
200
|
+
[key: string]: unknown;
|
|
201
|
+
};
|
|
202
|
+
};
|