codebay 0.13.0 → 0.15.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.
Files changed (85) hide show
  1. package/.mochi/manifest.json +522 -443
  2. package/.mochi/svelte-client/HydratableIsland-smgsf0ma.js +1 -0
  3. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-9vc087ep.js +2 -0
  4. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-a48xq47a.js +14 -0
  5. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-d4zehrqz.js +3 -0
  6. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-t5175pf5.js +44 -0
  7. package/.mochi/svelte-client/chunk-0mstgbtr.js +2 -0
  8. package/.mochi/svelte-client/chunk-0q9g0kpg.js +1 -0
  9. package/.mochi/svelte-client/chunk-3fwjzw1r.js +1 -0
  10. package/.mochi/svelte-client/{chunk-spk1b2r4.js → chunk-5pg0be5f.js} +1 -1
  11. package/.mochi/svelte-client/{chunk-ccfh8hxz.js → chunk-a2hty99b.js} +1 -1
  12. package/.mochi/svelte-client/{chunk-0mc09cyb.js → chunk-ds9yn9cq.js} +1 -1
  13. package/.mochi/svelte-client/{chunk-phf7v5jj.js → chunk-fn03gxer.js} +1 -1
  14. package/.mochi/svelte-client/chunk-jhgq7f65.js +2 -0
  15. package/.mochi/svelte-client/chunk-p5a07kx1.js +1 -0
  16. package/.mochi/svelte-client/chunk-p5wpyhsf.js +2 -0
  17. package/.mochi/svelte-client/chunk-t2kfvtb6.js +2 -0
  18. package/.mochi/svelte-client/{chunk-x0m3k0rn.js → chunk-v3wg9mdy.js} +1 -1
  19. package/.mochi/svelte-compile/{App-94xsdn0w.server.js → App-68f9a9fq.server.js} +64 -59
  20. package/.mochi/svelte-compile/{Avatars-fnccw47h.server.js → Avatars-vyh3rwxx.server.js} +14 -29
  21. package/.mochi/svelte-compile/Instance-yfw7s5mp.server.js +627 -0
  22. package/.mochi/svelte-compile/{Settings-h7gsjyz3.server.js → Settings-0b5t9kw9.server.js} +348 -151
  23. package/.mochi/svelte-compile/{chunk-w0hca7va.js → chunk-0y3wrae9.js} +13 -1
  24. package/.mochi/svelte-compile/{chunk-ed46m58e.js → chunk-2qzc07gt.js} +13 -1
  25. package/.mochi/svelte-compile/chunk-ef3hvegw.js +21 -0
  26. package/.mochi/svelte-compile/chunk-f9td8ej2.js +24 -0
  27. package/.mochi/svelte-compile/{chunk-pqb403fh.js → chunk-g0eravvj.js} +5 -16
  28. package/.mochi/svelte-css/AgentLogBox-38adby5iygny7.css +1 -0
  29. package/.mochi/svelte-css/{InstanceCard-25z7nas7y98zk.css → InstanceCard-3vxozrp9uoads.css} +1 -1
  30. package/.mochi/svelte-css/{SettingsView-bccw34agd2bj.css → SettingsView-2400myxb9ot56.css} +1 -1
  31. package/README.md +32 -1
  32. package/migrations/0009_agent_runs.sql +24 -0
  33. package/migrations/0010_agent_run_model.sql +2 -0
  34. package/package.json +6 -2
  35. package/public/favicon.svg +9 -0
  36. package/src/components/AgentLogBox.svelte +458 -0
  37. package/src/components/AppShell.svelte +11 -0
  38. package/src/components/FolderBrowser.svelte +13 -5
  39. package/src/components/InstanceCard.svelte +38 -0
  40. package/src/components/InstanceView.svelte +8 -0
  41. package/src/components/SettingsView.svelte +260 -0
  42. package/src/components/UiShowcase.svelte +4 -2
  43. package/src/container-injections/claude-code-credentials.ts +2 -3
  44. package/src/container-injections/claude-code-skills.ts +125 -0
  45. package/src/container-injections/claude-output-style.ts +2 -3
  46. package/src/container-injections/claude-statusline.ts +9 -17
  47. package/src/container-injections/github-credentials.ts +12 -1
  48. package/src/index.ts +32 -7
  49. package/src/lib/agent-run-stream.ts +246 -0
  50. package/src/lib/agent-runs.server.ts +724 -0
  51. package/src/lib/auth.server.ts +11 -2
  52. package/src/lib/build-freshness.server.ts +71 -0
  53. package/src/lib/config.server.ts +29 -6
  54. package/src/lib/container-files.server.ts +23 -1
  55. package/src/lib/data-dir-lock.server.ts +78 -0
  56. package/src/lib/db.server.ts +120 -1
  57. package/src/lib/devcontainer.server.ts +60 -14
  58. package/src/lib/docker-client.server.ts +11 -5
  59. package/src/lib/drives.ts +11 -0
  60. package/src/lib/exec.server.ts +66 -5
  61. package/src/lib/host-claude.server.ts +31 -0
  62. package/src/lib/injections.server.ts +2 -0
  63. package/src/lib/instances.server.ts +45 -3
  64. package/src/lib/log-capture.server.ts +17 -5
  65. package/src/lib/mcp-auth.server.ts +52 -0
  66. package/src/lib/picker.server.ts +23 -2
  67. package/src/lib/sandbox-ops.server.ts +263 -0
  68. package/src/mcp/routes.server.ts +25 -0
  69. package/src/mcp/server.server.ts +52 -0
  70. package/src/mcp/tools.server.ts +494 -0
  71. package/src/pages/Settings.svelte +15 -0
  72. package/src/routes.ts +72 -1
  73. package/src/shell.html +1 -0
  74. package/src/types.ts +15 -0
  75. package/.mochi/svelte-client/HydratableIsland-zrdsrsmg.js +0 -1
  76. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-v7y7dh53.js +0 -2
  77. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-tmwd6fpb.js +0 -14
  78. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-m7t64dbh.js +0 -1
  79. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-wedc9x9y.js +0 -39
  80. package/.mochi/svelte-client/chunk-5jwfs4p9.js +0 -2
  81. package/.mochi/svelte-client/chunk-6qg4q98a.js +0 -2
  82. package/.mochi/svelte-client/chunk-tmeh70wg.js +0 -2
  83. package/.mochi/svelte-client/chunk-vs1yjbqt.js +0 -2
  84. package/.mochi/svelte-client/chunk-y7f4jbdm.js +0 -2
  85. package/.mochi/svelte-compile/Instance-knnkcn2p.server.js +0 -377
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Chunk-oriented because the poller tails raw bytes that usually end mid-line, and free of
3
+ * Node/Bun APIs so it can be unit-tested on its own.
4
+ */
5
+
6
+ /** Everything the run row needs, accumulated across chunks. */
7
+ export interface RunStreamState {
8
+ sessionId: string | null;
9
+ /** The model Claude actually ran with (an id like `claude-opus-5`), from the `init` event. */
10
+ model: string | null;
11
+ lastActivity: string | null;
12
+ numTurns: number | null;
13
+ costUsd: number | null;
14
+ durationMs: number | null;
15
+ result: string | null;
16
+ /** Serialized `structured_output`, present only when the caller passed a `--json-schema`. */
17
+ structuredOutput: string | null;
18
+ isError: boolean;
19
+ /** True once the terminal `result` event has been seen, whatever its subtype. */
20
+ resultSeen: boolean;
21
+ }
22
+
23
+ export function emptyRunState(): RunStreamState {
24
+ return {
25
+ sessionId: null,
26
+ model: null,
27
+ lastActivity: null,
28
+ numTurns: null,
29
+ costUsd: null,
30
+ durationMs: null,
31
+ result: null,
32
+ structuredOutput: null,
33
+ isError: false,
34
+ resultSeen: false
35
+ };
36
+ }
37
+
38
+ /** Long enough to identify the step, short enough to sit on one line of a caller's poll output. */
39
+ const ACTIVITY_MAX = 120;
40
+
41
+ /** The timeline has a whole panel to render into, so it keeps more of each line than the poller. */
42
+ const TIMELINE_MAX = 400;
43
+
44
+ function truncate(text: string, max = ACTIVITY_MAX): string {
45
+ const oneLine = text.replace(/\s+/g, ' ').trim();
46
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + '…' : oneLine;
47
+ }
48
+
49
+ /** The field that best identifies a call, per tool; anything unlisted falls back to the first string. */
50
+ const TOOL_SUMMARY_FIELDS = [
51
+ 'command',
52
+ 'file_path',
53
+ 'path',
54
+ 'pattern',
55
+ 'description',
56
+ 'query',
57
+ 'url',
58
+ 'prompt'
59
+ ];
60
+
61
+ function summarizeToolInput(input: unknown): string {
62
+ if (typeof input !== 'object' || input === null) return '';
63
+ const obj = input as Record<string, unknown>;
64
+ for (const field of TOOL_SUMMARY_FIELDS) {
65
+ const value = obj[field];
66
+ if (typeof value === 'string' && value.trim()) return value;
67
+ }
68
+ const first = Object.values(obj).find((v) => typeof v === 'string' && v.trim());
69
+ return typeof first === 'string' ? first : '';
70
+ }
71
+
72
+ interface ContentBlock {
73
+ type?: string;
74
+ text?: string;
75
+ name?: string;
76
+ input?: unknown;
77
+ }
78
+
79
+ /** The last block wins, so `last_activity` reflects the most recent thing Claude did in the turn. */
80
+ function activityFromBlocks(blocks: ContentBlock[]): string | null {
81
+ let activity: string | null = null;
82
+ for (const block of blocks) {
83
+ if (block.type === 'tool_use' && typeof block.name === 'string') {
84
+ const arg = summarizeToolInput(block.input);
85
+ activity = arg ? `${block.name}(${truncate(arg)})` : block.name;
86
+ } else if (block.type === 'text' && typeof block.text === 'string' && block.text.trim()) {
87
+ activity = truncate(block.text);
88
+ }
89
+ }
90
+ return activity;
91
+ }
92
+
93
+ interface StreamEvent {
94
+ type?: string;
95
+ subtype?: string;
96
+ session_id?: string;
97
+ model?: string;
98
+ parent_tool_use_id?: string | null;
99
+ message?: { content?: ContentBlock[] };
100
+ error?: string;
101
+ attempt?: number;
102
+ result?: string;
103
+ structured_output?: unknown;
104
+ is_error?: boolean;
105
+ num_turns?: number;
106
+ total_cost_usd?: number;
107
+ duration_ms?: number;
108
+ }
109
+
110
+ function applyEvent(state: RunStreamState, event: StreamEvent): void {
111
+ if (typeof event.session_id === 'string' && !state.sessionId) state.sessionId = event.session_id;
112
+
113
+ if (event.type === 'system' && event.subtype === 'init' && typeof event.model === 'string') {
114
+ state.model = event.model;
115
+ return;
116
+ }
117
+
118
+ if (event.type === 'system' && event.subtype === 'api_retry') {
119
+ state.lastActivity = `Retrying (${event.error ?? 'error'}), attempt ${event.attempt ?? 1}…`;
120
+ return;
121
+ }
122
+
123
+ if (event.type === 'assistant' && Array.isArray(event.message?.content)) {
124
+ // Subagent chatter would otherwise drown out what the main loop is doing.
125
+ if (event.parent_tool_use_id != null) return;
126
+ const activity = activityFromBlocks(event.message.content);
127
+ if (activity) state.lastActivity = activity;
128
+ return;
129
+ }
130
+
131
+ if (event.type === 'result') {
132
+ state.resultSeen = true;
133
+ state.isError = event.is_error === true || event.subtype !== 'success';
134
+ if (typeof event.result === 'string') state.result = event.result;
135
+ if (event.structured_output !== undefined && event.structured_output !== null) {
136
+ state.structuredOutput = JSON.stringify(event.structured_output);
137
+ }
138
+ if (typeof event.num_turns === 'number') state.numTurns = event.num_turns;
139
+ if (typeof event.total_cost_usd === 'number') state.costUsd = event.total_cost_usd;
140
+ if (typeof event.duration_ms === 'number') state.durationMs = event.duration_ms;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Folds a chunk of newline-delimited JSON into `state`, returning the incomplete trailing line for
146
+ * the caller to prepend next time. A line that won't parse is skipped rather than fatal — a torn
147
+ * write reappears whole on the following pass.
148
+ */
149
+ export function readRunChunk(
150
+ state: RunStreamState,
151
+ carry: string,
152
+ chunk: string
153
+ ): { state: RunStreamState; carry: string } {
154
+ const lines = (carry + chunk).split('\n');
155
+ const tail = lines.pop() ?? '';
156
+ for (const line of lines) {
157
+ const trimmed = line.trim();
158
+ if (!trimmed) continue;
159
+ try {
160
+ applyEvent(state, JSON.parse(trimmed) as StreamEvent);
161
+ } catch {
162
+ // Not a torn line but genuine garbage (a stray shell warning); ignore it.
163
+ }
164
+ }
165
+ return { state, carry: tail };
166
+ }
167
+
168
+ /** Replays a whole mirror file — how a run's state is rebuilt after the manager restarts. */
169
+ export function readRunFile(text: string): RunStreamState {
170
+ return readRunChunk(emptyRunState(), '', text).state;
171
+ }
172
+
173
+ /** One rendered row of the Agent log panel. */
174
+ export interface RunTimelineEntry {
175
+ kind: 'tool' | 'text' | 'retry' | 'result';
176
+ /** Tool name, when `kind` is `'tool'`. */
177
+ name?: string;
178
+ text: string;
179
+ /** True for a subagent's message, which the panel indents rather than hides. */
180
+ subagent?: boolean;
181
+ isError?: boolean;
182
+ }
183
+
184
+ function timelineFromEvent(event: StreamEvent): RunTimelineEntry[] {
185
+ if (event.type === 'system' && event.subtype === 'api_retry') {
186
+ return [
187
+ {
188
+ kind: 'retry',
189
+ text: `Retrying (${event.error ?? 'error'}), attempt ${event.attempt ?? 1}…`
190
+ }
191
+ ];
192
+ }
193
+ if (event.type === 'assistant' && Array.isArray(event.message?.content)) {
194
+ const subagent = event.parent_tool_use_id != null;
195
+ const out: RunTimelineEntry[] = [];
196
+ for (const block of event.message.content) {
197
+ if (block.type === 'tool_use' && typeof block.name === 'string') {
198
+ const arg = summarizeToolInput(block.input);
199
+ out.push({
200
+ kind: 'tool',
201
+ name: block.name,
202
+ text: truncate(arg, TIMELINE_MAX),
203
+ subagent
204
+ });
205
+ } else if (block.type === 'text' && typeof block.text === 'string' && block.text.trim()) {
206
+ out.push({ kind: 'text', text: truncate(block.text, TIMELINE_MAX), subagent });
207
+ }
208
+ }
209
+ return out;
210
+ }
211
+ if (event.type === 'result') {
212
+ const isError = event.is_error === true || event.subtype !== 'success';
213
+ return [
214
+ {
215
+ kind: 'result',
216
+ text: truncate(event.result ?? event.subtype ?? 'finished', TIMELINE_MAX),
217
+ isError
218
+ }
219
+ ];
220
+ }
221
+ return [];
222
+ }
223
+
224
+ /**
225
+ * Flattens a whole mirrored run into the rows the Agent log renders. Separate from `readRunChunk`
226
+ * because that one only keeps the *latest* of each field — a timeline needs every step.
227
+ */
228
+ export function parseRunTimeline(text: string): RunTimelineEntry[] {
229
+ const out: RunTimelineEntry[] = [];
230
+ for (const line of text.split('\n')) {
231
+ const trimmed = line.trim();
232
+ if (!trimmed) continue;
233
+ try {
234
+ for (const entry of timelineFromEvent(JSON.parse(trimmed) as StreamEvent)) {
235
+ // Claude's closing message arrives twice — once as the final assistant text block and
236
+ // again as the `result` event's text — so the result supersedes it rather than repeats it.
237
+ const prev = out.at(-1);
238
+ if (entry.kind === 'result' && prev?.kind === 'text' && prev.text === entry.text) out.pop();
239
+ out.push(entry);
240
+ }
241
+ } catch {
242
+ // A torn trailing line; it reappears whole once more bytes land.
243
+ }
244
+ }
245
+ return out;
246
+ }