copilot-operator 0.1.39 → 0.1.40

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 (53) hide show
  1. package/dist/src/api/operator.controller.js +101 -2
  2. package/dist/src/api/operator.service.js +292 -25
  3. package/dist/src/api/server.js +7 -0
  4. package/dist/src/log/terminal.js +145 -0
  5. package/dist/src/plan/brief.js +1375 -1369
  6. package/dist/src/plan/schema.js +5 -0
  7. package/dist/src/plan/systemGuide.js +158 -16
  8. package/dist/src/session/exports.js +1 -0
  9. package/dist/src/session/startFailure.js +91 -0
  10. package/dist/src/vcs/prepareFromRemote.js +7 -20
  11. package/dist/src/vcs/remoteAuth.js +26 -1
  12. package/dist/src/vcs/runPreflight.js +31 -14
  13. package/dist/src/vcs/snapshot.js +3 -1
  14. package/dist/src/vcs/startChoice.js +295 -0
  15. package/dist/src/vcs/taskVcs.js +40 -5
  16. package/dist/web/404.html +2 -2
  17. package/dist/web/_next/static/chunks/499-28e118e33ce07935.js +1 -0
  18. package/dist/web/_next/static/chunks/549-f295c8429e83b25b.js +1 -0
  19. package/dist/web/_next/static/chunks/584-578be94a0786334b.js +1 -0
  20. package/dist/web/_next/static/chunks/867-8dc5bbb092ca2c5c.js +1 -0
  21. package/dist/web/_next/static/chunks/app/history/page-9f69ccde726c9be2.js +1 -0
  22. package/dist/web/_next/static/chunks/app/layout-73f28bc065a46ed3.js +1 -0
  23. package/dist/web/_next/static/chunks/app/page-e557d58476ae6383.js +1 -0
  24. package/dist/web/_next/static/chunks/app/sessions/view/page-0485ee7f781156ec.js +1 -0
  25. package/dist/web/_next/static/chunks/app/system/{page-5ae1f8054208e6eb.js → page-227a259e383ca2a0.js} +1 -1
  26. package/dist/web/appearance.html +2 -2
  27. package/dist/web/appearance.txt +27 -25
  28. package/dist/web/defaults.html +2 -2
  29. package/dist/web/defaults.txt +27 -25
  30. package/dist/web/history.html +2 -2
  31. package/dist/web/history.txt +27 -25
  32. package/dist/web/import.html +2 -2
  33. package/dist/web/import.txt +27 -25
  34. package/dist/web/index.html +2 -2
  35. package/dist/web/index.txt +27 -25
  36. package/dist/web/level1.html +2 -2
  37. package/dist/web/level1.txt +27 -25
  38. package/dist/web/presets.html +2 -2
  39. package/dist/web/presets.txt +27 -25
  40. package/dist/web/sessions/view.html +2 -2
  41. package/dist/web/sessions/view.txt +27 -25
  42. package/dist/web/system.html +2 -2
  43. package/dist/web/system.txt +27 -25
  44. package/package.json +158 -157
  45. package/dist/web/_next/static/chunks/549-bfde82e4a70ed1a0.js +0 -1
  46. package/dist/web/_next/static/chunks/584-eba7ffe26cf1be83.js +0 -1
  47. package/dist/web/_next/static/chunks/867-7fccf68b0840c0de.js +0 -1
  48. package/dist/web/_next/static/chunks/app/history/page-d3461384566aa332.js +0 -1
  49. package/dist/web/_next/static/chunks/app/layout-630b663626cebe4a.js +0 -1
  50. package/dist/web/_next/static/chunks/app/page-4defcfd1eb482644.js +0 -1
  51. package/dist/web/_next/static/chunks/app/sessions/view/page-945be57e397ae162.js +0 -1
  52. /package/dist/web/_next/static/{KYi1jz7Awse_66Fg8c7Ur → Y9tuV5LV-O0XyCREkeGc5}/_buildManifest.js +0 -0
  53. /package/dist/web/_next/static/{KYi1jz7Awse_66Fg8c7Ur → Y9tuV5LV-O0XyCREkeGc5}/_ssgManifest.js +0 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * What the bot's terminal showed, kept so it can be downloaded from the page.
3
+ *
4
+ * Asked for on 2026-10-13: when a run does not start at all, the operator wants to see the error, and
5
+ * a place to download what happened in the terminal. The terminal is the console the API runs in —
6
+ * `npm start` or `cop start` — and what it printed was on that screen only: a stack trace from
7
+ * Playwright about the browser profile, a refusal from git, scrolled away or closed with the window.
8
+ *
9
+ * Everything this process writes to stdout and stderr is copied, line by line with the time it was
10
+ * written, into a ring of the latest lines in memory and into `<data>/logs/terminal.log` on disk (the
11
+ * data folder is narrowed to this account; see `api/dataAcl.ts`), which rolls over to `terminal.1.log`
12
+ * at 5 MB. Writing to the console is not changed in any way: the copy is taken on the side, and a copy
13
+ * that fails is dropped rather than allowed to break the write it was copying.
14
+ */
15
+ import { appendFileSync, mkdirSync, renameSync, statSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ const MAX_LINES = 5_000;
18
+ const MAX_LINE = 4_000;
19
+ const ROLL_AT = 5 * 1024 * 1024;
20
+ const ring = [];
21
+ const partial = { out: '', err: '' };
22
+ let installed = false;
23
+ let file = null;
24
+ let written = 0;
25
+ // eslint-disable-next-line no-control-regex
26
+ const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
27
+ /** The file the lines go to, once `captureTerminal` was given a folder. */
28
+ export function terminalLogFile() {
29
+ return file;
30
+ }
31
+ /**
32
+ * Starts copying this process's console, once; a later call only points the copy at another folder.
33
+ * Lines written before a folder is known are kept in memory and written to the file when it is.
34
+ */
35
+ export function captureTerminal(dir) {
36
+ if (dir)
37
+ setFolder(dir);
38
+ if (installed)
39
+ return;
40
+ installed = true;
41
+ for (const stream of ['out', 'err']) {
42
+ const target = stream === 'out' ? process.stdout : process.stderr;
43
+ const original = target.write.bind(target);
44
+ target.write = ((chunk, ...rest) => {
45
+ try {
46
+ take(stream, typeof chunk === 'string' ? chunk : Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk instanceof Uint8Array ? Buffer.from(chunk).toString('utf8') : '');
47
+ }
48
+ catch {
49
+ /* the copy is never allowed to break the write */
50
+ }
51
+ return original(chunk, ...rest);
52
+ });
53
+ }
54
+ }
55
+ function setFolder(dir) {
56
+ try {
57
+ mkdirSync(dir, { recursive: true });
58
+ const next = join(dir, 'terminal.log');
59
+ if (next === file)
60
+ return;
61
+ const first = file === null;
62
+ file = next;
63
+ written = sizeOf(next);
64
+ // What was printed before the folder was known — the start-up — goes in first.
65
+ if (first && ring.length > 0)
66
+ append(ring.map(format).join(''));
67
+ }
68
+ catch {
69
+ file = null;
70
+ }
71
+ }
72
+ function take(stream, text) {
73
+ if (!text)
74
+ return;
75
+ const parts = (partial[stream] + text).split(/\r?\n/);
76
+ partial[stream] = parts.pop() ?? '';
77
+ // A line that never ends (a progress bar) is still kept, in pieces.
78
+ if (partial[stream].length > MAX_LINE) {
79
+ parts.push(partial[stream]);
80
+ partial[stream] = '';
81
+ }
82
+ const at = new Date().toISOString();
83
+ const out = [];
84
+ for (const raw of parts) {
85
+ const clean = raw.replace(ANSI, '').replace(/\r/g, '');
86
+ if (!clean.trim())
87
+ continue;
88
+ out.push({ at, stream, text: clean.length > MAX_LINE ? `${clean.slice(0, MAX_LINE)} …` : clean });
89
+ }
90
+ if (out.length === 0)
91
+ return;
92
+ ring.push(...out);
93
+ if (ring.length > MAX_LINES)
94
+ ring.splice(0, ring.length - MAX_LINES);
95
+ if (file)
96
+ append(out.map(format).join(''));
97
+ }
98
+ function format(l) {
99
+ return `${l.at} ${l.stream === 'err' ? 'ERR' : 'OUT'} ${l.text}\n`;
100
+ }
101
+ function append(text) {
102
+ if (!file)
103
+ return;
104
+ try {
105
+ if (written + text.length > ROLL_AT) {
106
+ try {
107
+ renameSync(file, file.replace(/\.log$/, '.1.log'));
108
+ }
109
+ catch {
110
+ /* nothing to roll over yet */
111
+ }
112
+ written = 0;
113
+ }
114
+ appendFileSync(file, text, 'utf8');
115
+ written += Buffer.byteLength(text);
116
+ }
117
+ catch {
118
+ /* a full disk or a locked file costs the copy, not the console */
119
+ }
120
+ }
121
+ function sizeOf(path) {
122
+ try {
123
+ return statSync(path).size;
124
+ }
125
+ catch {
126
+ return 0;
127
+ }
128
+ }
129
+ /** The lines kept in memory from `from` to `to` (both inclusive, ISO times), the latest `max` of them. */
130
+ export function terminalLines(opts = {}) {
131
+ const within = ring.filter((l) => (!opts.from || l.at >= opts.from) && (!opts.to || l.at <= opts.to));
132
+ return opts.max !== undefined && within.length > opts.max ? within.slice(within.length - opts.max) : within;
133
+ }
134
+ /** The lines as the download shows them: local time, the stream, the text. */
135
+ export function formatTerminal(list) {
136
+ return list.map((l) => `${localTime(l.at)} ${l.stream === 'err' ? 'ERR' : ' '} ${l.text}`).join('\n');
137
+ }
138
+ /** `2026-10-13 10:22:31.123` in this machine's time zone. */
139
+ export function localTime(iso) {
140
+ const d = new Date(iso);
141
+ if (Number.isNaN(d.getTime()))
142
+ return iso;
143
+ const p = (n, w = 2) => String(n).padStart(w, '0');
144
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}.${p(d.getMilliseconds(), 3)}`;
145
+ }