copilot-operator 0.1.3 → 0.1.4
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 +8 -2
- package/dist/src/api/operator.controller.js +6 -1
- package/dist/src/api/operator.service.js +55 -22
- package/dist/src/exec/authorizer.js +2 -2
- package/dist/src/exec/commandHeads.js +799 -0
- package/dist/src/exec/policy.js +1 -1
- package/dist/src/exec/programs.js +22 -82
- package/dist/src/exec/reportFile.js +5 -2
- package/dist/src/orchestrator/review.js +4 -3
- package/dist/src/orchestrator/taskRunner.js +32 -7
- package/dist/src/plan/schema.js +7 -0
- package/dist/src/plan/systemGuide.js +8 -0
- package/dist/src/protocol/reporter.js +8 -6
- package/dist/src/session/exports.js +128 -1
- package/dist/src/session/store.js +2 -0
- package/dist/src/vcs/taskVcs.js +33 -0
- package/dist/web/404.html +2 -2
- package/dist/web/_next/static/chunks/867-f0e3e152f6f67e45.js +1 -0
- package/dist/web/_next/static/chunks/89-382e9b29486d2747.js +1 -0
- package/dist/web/_next/static/chunks/app/defaults/page-1747c931adc746fd.js +1 -0
- package/dist/web/_next/static/chunks/app/history/{page-2232949a2ff8559e.js → page-8d4f817e36986e3f.js} +1 -1
- package/dist/web/_next/static/chunks/app/sessions/view/{page-9dda275f4b1b0423.js → page-185f5fcc383e6619.js} +1 -1
- package/dist/web/appearance.html +2 -2
- package/dist/web/appearance.txt +10 -10
- package/dist/web/defaults.html +2 -2
- package/dist/web/defaults.txt +10 -10
- package/dist/web/history.html +2 -2
- package/dist/web/history.txt +10 -10
- package/dist/web/import.html +2 -2
- package/dist/web/import.txt +10 -10
- package/dist/web/index.html +2 -2
- package/dist/web/index.txt +10 -10
- package/dist/web/level1.html +2 -2
- package/dist/web/level1.txt +10 -10
- package/dist/web/presets.html +2 -2
- package/dist/web/presets.txt +10 -10
- package/dist/web/sessions/view.html +2 -2
- package/dist/web/sessions/view.txt +10 -10
- package/dist/web/system.html +2 -2
- package/dist/web/system.txt +10 -10
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/867-55627c5122ff6336.js +0 -1
- package/dist/web/_next/static/chunks/89-5585315f51e4c481.js +0 -1
- package/dist/web/_next/static/chunks/app/defaults/page-4ff25cafe6de779c.js +0 -1
- /package/dist/web/_next/static/{QwjyyBjkO5xlKFU5O6Wux → p9lexLS0u8W5N6VzLavHO}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{QwjyyBjkO5xlKFU5O6Wux → p9lexLS0u8W5N6VzLavHO}/_ssgManifest.js +0 -0
package/dist/src/exec/policy.js
CHANGED
|
@@ -92,7 +92,7 @@ confinement) {
|
|
|
92
92
|
// The allowlist after the known-bad floor and the operator's deny list, so their precise
|
|
93
93
|
// messages win, and this catches the long tail neither of them was ever going to enumerate: a
|
|
94
94
|
// program that is simply not part of this project's toolchain. See `programs.ts`.
|
|
95
|
-
const offlist = programRefusal(command, allowedPrograms);
|
|
95
|
+
const offlist = programRefusal(command, allowedPrograms, shell);
|
|
96
96
|
if (offlist)
|
|
97
97
|
return offlist;
|
|
98
98
|
const trap = findShellExecuteTrap(command, shell, env);
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* before this existed starts failing on an upgrade.
|
|
30
30
|
*/
|
|
31
31
|
import { extname } from 'node:path';
|
|
32
|
+
import { scanCommandHeads } from './commandHeads.js';
|
|
32
33
|
/**
|
|
33
34
|
* Windows executable extensions. A token carrying one of these names a program by file, whatever
|
|
34
35
|
* the stem, so it is always checked; a token with any other extension (`config.json`, `app.ts`)
|
|
@@ -55,6 +56,7 @@ const SHELL_WORDS = new Set([
|
|
|
55
56
|
'gi', 'gp', 'sp', 'si', 'gm', 'gu', 'gv', 'sv', 'select', 'where', 'foreach-object', 'where-object',
|
|
56
57
|
'sort', 'group', 'measure', 'ft', 'fl', 'fw', 'fh', 'oh', 'out-host', 'tee',
|
|
57
58
|
'sls', 'gcm', 'gmo', 'ipmo', 'man', 'help', 'more', 'h', 'history', 'r',
|
|
59
|
+
'%', '?', // ForEach-Object and Where-Object, as `ls | % { $_.Name }` writes them
|
|
58
60
|
'gsv', 'gps', 'ps', 'kill', 'sleep', 'start', 'saps', 'spps', 'sasv', 'spsv',
|
|
59
61
|
'iwr', 'irm', 'wget', 'curl', // PowerShell aliases for Invoke-WebRequest/Invoke-RestMethod; the raw .exe forms are allowlisted by name if wanted, and iwr|iex is caught by dangerous.ts
|
|
60
62
|
// cmd built-ins
|
|
@@ -62,87 +64,13 @@ const SHELL_WORDS = new Set([
|
|
|
62
64
|
'pushd', 'popd', 'ver', 'vol', 'date', 'time', 'exist', 'not', 'defined', 'errorlevel',
|
|
63
65
|
]);
|
|
64
66
|
/**
|
|
65
|
-
* The
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* Best effort by design. The point is to find the programs a line would obviously start, not to
|
|
71
|
-
* be a PowerShell parser; a construction it cannot read yields no head for that fragment, and the
|
|
72
|
-
* fragment then rests on `dangerous.ts` and the human, which is where the honest limit was always
|
|
73
|
-
* going to be anyway.
|
|
67
|
+
* The name at the head of every command a line invokes. Read by `commandHeads.ts`, which follows
|
|
68
|
+
* PowerShell's command and expression modes (or `cmd`'s rules for a `cmd` step), so strings,
|
|
69
|
+
* operators, array values, method arguments and hashtable keys are not mistaken for programs, and
|
|
70
|
+
* a command nested in a script block, a subexpression or after an assignment is not missed.
|
|
74
71
|
*/
|
|
75
|
-
export function commandHeads(command) {
|
|
76
|
-
|
|
77
|
-
const boundaries = new Set(['|', ';', '&', '\n', '(', '{']);
|
|
78
|
-
let expectStart = true;
|
|
79
|
-
let quote = null;
|
|
80
|
-
let i = 0;
|
|
81
|
-
const readToken = (from) => {
|
|
82
|
-
let j = from;
|
|
83
|
-
let text = '';
|
|
84
|
-
let q = null;
|
|
85
|
-
while (j < command.length) {
|
|
86
|
-
const ch = command[j];
|
|
87
|
-
if (q) {
|
|
88
|
-
if (ch === q)
|
|
89
|
-
q = null;
|
|
90
|
-
else
|
|
91
|
-
text += ch;
|
|
92
|
-
j += 1;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (ch === "'" || ch === '"') {
|
|
96
|
-
q = ch;
|
|
97
|
-
j += 1;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (/\s/.test(ch) || boundaries.has(ch) || ch === ')' || ch === '}')
|
|
101
|
-
break;
|
|
102
|
-
text += ch;
|
|
103
|
-
j += 1;
|
|
104
|
-
}
|
|
105
|
-
return { text, end: j };
|
|
106
|
-
};
|
|
107
|
-
while (i < command.length) {
|
|
108
|
-
const ch = command[i];
|
|
109
|
-
if (quote) {
|
|
110
|
-
if (ch === quote)
|
|
111
|
-
quote = null;
|
|
112
|
-
i += 1;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (ch === "'" || ch === '"') {
|
|
116
|
-
quote = ch;
|
|
117
|
-
i += 1;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (expectStart && !/\s/.test(ch) && !boundaries.has(ch) && ch !== ')' && ch !== '}') {
|
|
121
|
-
const { text, end } = readToken(i);
|
|
122
|
-
i = end;
|
|
123
|
-
// A dot-source — `. .\script.ps1` — runs the file that follows in the current scope. The
|
|
124
|
-
// dot is not what runs; the next token is, and it is the one the allowlist has to see.
|
|
125
|
-
// (`& .\script.ps1` already works this way because `&` is a boundary.)
|
|
126
|
-
if (text === '.')
|
|
127
|
-
continue;
|
|
128
|
-
if (text)
|
|
129
|
-
heads.push(text);
|
|
130
|
-
expectStart = false;
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (boundaries.has(ch)) {
|
|
134
|
-
// A back-tick immediately before a newline is a line continuation, not a new statement.
|
|
135
|
-
if (ch === '\n' && command[i - 1] === '`') {
|
|
136
|
-
i += 1;
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
expectStart = true;
|
|
140
|
-
i += 1;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
i += 1;
|
|
144
|
-
}
|
|
145
|
-
return heads;
|
|
72
|
+
export function commandHeads(command, shell) {
|
|
73
|
+
return scanCommandHeads(command, shell).heads;
|
|
146
74
|
}
|
|
147
75
|
/**
|
|
148
76
|
* The external-program name a statement head invokes, normalised for the allowlist, or null when
|
|
@@ -273,12 +201,24 @@ export function inlineCodeRefusal(command) {
|
|
|
273
201
|
* is a legitimate tool this project happens to need, and the fix is one line of config — not a
|
|
274
202
|
* reason to weaken the gate for everyone.
|
|
275
203
|
*/
|
|
276
|
-
export function programRefusal(command, allowedPrograms) {
|
|
204
|
+
export function programRefusal(command, allowedPrograms, shell = 'pwsh') {
|
|
277
205
|
if (!allowedPrograms || allowedPrograms.length === 0)
|
|
278
206
|
return null;
|
|
279
207
|
const allowed = new Set(allowedPrograms.map((p) => p.trim().toLowerCase().replace(/\.(exe|com|bat|cmd|ps1|msi)$/i, '')).filter(Boolean));
|
|
208
|
+
/*
|
|
209
|
+
* Fail-closed: a line that cannot be read with certainty is a line whose programs are not known,
|
|
210
|
+
* and an allowlist that cannot name the programs has nothing to check them against. What makes
|
|
211
|
+
* a line uncertain — an unclosed string or bracket, a program chosen through a variable — is
|
|
212
|
+
* never needed by honest work, so refusing it costs a rewrite and guessing could cost the gate.
|
|
213
|
+
*/
|
|
214
|
+
const scan = scanCommandHeads(command, shell);
|
|
215
|
+
if (scan.uncertain) {
|
|
216
|
+
return (`refused: the runner could not tell which programs this line starts (${scan.uncertain}). ` +
|
|
217
|
+
'Write it so every program is named literally — close each quote and bracket, and call a program by its ' +
|
|
218
|
+
'name or a literal path rather than through a variable or an expression.');
|
|
219
|
+
}
|
|
280
220
|
// What `Start-Process` is asked to start is a program this line starts, as much as a head is.
|
|
281
|
-
for (const head of [...
|
|
221
|
+
for (const head of [...scan.heads, ...startProcessTargets(command)]) {
|
|
282
222
|
const program = externalProgram(head);
|
|
283
223
|
if (program && !allowed.has(program)) {
|
|
284
224
|
return (`refused: "${program}" is not in execution.allowedPrograms. This machine runs only the project's ` +
|
|
@@ -42,8 +42,11 @@ export function clip(text, maxChars) {
|
|
|
42
42
|
return `${text.slice(0, half)}\n\n[... ${dropped} characters omitted ...]\n\n${text.slice(-half)}`;
|
|
43
43
|
}
|
|
44
44
|
function sectionFor(r, maxOutputChars) {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// A refused step never ran, so it is not given an exit code or a duration that could be read as
|
|
46
|
+
// a command that ran and failed; the header says what happened instead.
|
|
47
|
+
const head = r.outcome === 'refused'
|
|
48
|
+
? `--- step ${r.id} (${r.shell}, REFUSED by the runner, never executed, no exit code)\n$ ${r.command}\n`
|
|
49
|
+
: `--- step ${r.id} (${r.shell}, ${r.outcome}, exit ${r.exitCode}, ` + `${(r.durationMs / 1000).toFixed(1)}s)\n$ ${r.command}\n`;
|
|
47
50
|
const out = clip(r.stdout, maxOutputChars);
|
|
48
51
|
const err = r.stderr.trim().length > 0 ? `[stderr]\n${clip(r.stderr, maxOutputChars)}\n` : '';
|
|
49
52
|
const note = r.truncated ? `[note] output was truncated; the full stream is in ${r.logPath}\n` : '';
|
|
@@ -426,7 +426,7 @@ ${machineNote}` : contract);
|
|
|
426
426
|
confinement: { roots: deps.roots, cwd: deps.cwd },
|
|
427
427
|
});
|
|
428
428
|
if (decision.action !== 'run') {
|
|
429
|
-
results.push(refused(step.id, decision.reason, step.cmd, step.shell));
|
|
429
|
+
results.push(refused(step.id, decision.reason, step.cmd, step.shell, decision.by === 'operator' ? 'operator' : 'runner'));
|
|
430
430
|
if (decision.action === 'abort')
|
|
431
431
|
break;
|
|
432
432
|
continue;
|
|
@@ -525,7 +525,7 @@ export function refusedChecksMessage(refused) {
|
|
|
525
525
|
].join('\n');
|
|
526
526
|
}
|
|
527
527
|
/** A step the review did not run, in the shape the reporter expects. */
|
|
528
|
-
function refused(id, reason, command = '(not run)', requested) {
|
|
528
|
+
function refused(id, reason, command = '(not run)', requested, by = 'runner') {
|
|
529
529
|
return {
|
|
530
530
|
id,
|
|
531
531
|
// The shell it would have run in, rather than the one this file used to name: a report line
|
|
@@ -537,7 +537,8 @@ function refused(id, reason, command = '(not run)', requested) {
|
|
|
537
537
|
requestedShell: requested ?? null,
|
|
538
538
|
command,
|
|
539
539
|
exitCode: -4,
|
|
540
|
-
|
|
540
|
+
// Who declined it, as in `refusedResult` in the task runner: a rule, or a person.
|
|
541
|
+
outcome: by === 'operator' ? 'aborted' : 'refused',
|
|
541
542
|
durationMs: 0,
|
|
542
543
|
stdout: '',
|
|
543
544
|
stderr: `[policy] step not executed: ${reason}\n`,
|
|
@@ -41,6 +41,7 @@ import { composeOpening, READ_ONLY_NOTE } from '../session/compose.js';
|
|
|
41
41
|
import { mirrorProject, describeMirror } from '../context/projectMirror.js';
|
|
42
42
|
import { projectNameFor, projectTargetDir, removeLegacyFlatMirror } from '../context/desktopMirror.js';
|
|
43
43
|
import { prepareForTask, commitTaskResult, repoDirOf } from '../vcs/taskVcs.js';
|
|
44
|
+
import { exportMachine, writeAttemptRecord } from '../session/exports.js';
|
|
44
45
|
/** Why a task that was reported as done is being closed as failed. */
|
|
45
46
|
function checksFailedReason(outcomes) {
|
|
46
47
|
const failed = outcomes.filter((o) => !o.passed);
|
|
@@ -110,14 +111,18 @@ function blockedReason(tried, needed) {
|
|
|
110
111
|
.filter(Boolean)
|
|
111
112
|
.join(' — ');
|
|
112
113
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
114
|
+
/**
|
|
115
|
+
* A step that was not run never reaches the shell, but Copilot still has to hear about it — and
|
|
116
|
+
* hear who declined it: the runner (`refused`: a rule, so rewrite the step) or the operator
|
|
117
|
+
* (`aborted`: a person stopped or skipped it).
|
|
118
|
+
*/
|
|
119
|
+
function refusedResult(step, reason, by = 'runner') {
|
|
115
120
|
return {
|
|
116
121
|
id: step.id,
|
|
117
122
|
shell: effectiveShell(step.shell),
|
|
118
123
|
command: describeStep(step),
|
|
119
124
|
exitCode: -4,
|
|
120
|
-
outcome: 'aborted',
|
|
125
|
+
outcome: by === 'operator' ? 'aborted' : 'refused',
|
|
121
126
|
durationMs: 0,
|
|
122
127
|
stdout: '',
|
|
123
128
|
stderr: `[policy] step not executed: ${reason}\n`,
|
|
@@ -445,6 +450,26 @@ export async function runTask(transport, session, task, deps) {
|
|
|
445
450
|
});
|
|
446
451
|
sink.event('task-finished', { status, reason, iterations }, `task "${task.title}" ${status}${reason ? `: ${reason}` : ''}`);
|
|
447
452
|
await log.close();
|
|
453
|
+
/*
|
|
454
|
+
* An attempt that did not end done leaves its plan, work and runner views in its own folder,
|
|
455
|
+
* written after the transcript is closed so they carry all of it. See `writeAttemptRecord`.
|
|
456
|
+
* A failure to write them is reported and changes nothing about the outcome.
|
|
457
|
+
*/
|
|
458
|
+
if (status !== 'done') {
|
|
459
|
+
const now = await store.getSession(session.id);
|
|
460
|
+
const ended = now?.tasks.find((x) => x.id === task.id);
|
|
461
|
+
if (now && ended) {
|
|
462
|
+
await writeAttemptRecord(now, ended, cfg.resolved.runsDir, exportMachine(cfg))
|
|
463
|
+
.then((dir) => {
|
|
464
|
+
if (dir) {
|
|
465
|
+
bus.publish({ sessionId: session.id, taskId: task.id, type: 'attempt-record', level: 'info',
|
|
466
|
+
message: `plan, work and runner of this attempt saved in ${dir}`, data: { dir } });
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
.catch((e) => bus.publish({ sessionId: session.id, taskId: task.id, type: 'attempt-record', level: 'warn',
|
|
470
|
+
message: `could not save the plan, work and runner of this attempt: ${e.message}` }));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
448
473
|
return { status, iterations, summary, reason };
|
|
449
474
|
};
|
|
450
475
|
await setTask((t) => {
|
|
@@ -1181,7 +1206,7 @@ export async function runTask(transport, session, task, deps) {
|
|
|
1181
1206
|
let shellRefused = 0;
|
|
1182
1207
|
for (const step of reply.steps) {
|
|
1183
1208
|
if (signal?.aborted) {
|
|
1184
|
-
results.push(refusedResult(step, 'stopped by the operator'));
|
|
1209
|
+
results.push(refusedResult(step, 'stopped by the operator', 'operator'));
|
|
1185
1210
|
aborted = true;
|
|
1186
1211
|
break;
|
|
1187
1212
|
}
|
|
@@ -1233,13 +1258,13 @@ export async function runTask(transport, session, task, deps) {
|
|
|
1233
1258
|
t.status = 'running';
|
|
1234
1259
|
});
|
|
1235
1260
|
if (decision.action === 'abort') {
|
|
1236
|
-
results.push(refusedResult(step, decision.reason));
|
|
1261
|
+
results.push(refusedResult(step, decision.reason, decision.by === 'operator' ? 'operator' : 'runner'));
|
|
1237
1262
|
aborted = true;
|
|
1238
1263
|
break;
|
|
1239
1264
|
}
|
|
1240
1265
|
if (decision.action === 'skip') {
|
|
1241
|
-
sink.event('step-skipped', { id: step.id, reason: decision.reason }, `step ${step.id} skipped: ${decision.reason}`, 'warn');
|
|
1242
|
-
results.push(refusedResult(step, decision.reason));
|
|
1266
|
+
sink.event('step-skipped', { id: step.id, reason: decision.reason, by: decision.by ?? 'runner' }, `step ${step.id} skipped: ${decision.reason}`, 'warn');
|
|
1267
|
+
results.push(refusedResult(step, decision.reason, decision.by === 'operator' ? 'operator' : 'runner'));
|
|
1243
1268
|
continue;
|
|
1244
1269
|
}
|
|
1245
1270
|
const long = step.expect === 'long';
|
package/dist/src/plan/schema.js
CHANGED
|
@@ -195,6 +195,13 @@ const TaskInput = z.object({
|
|
|
195
195
|
* branch so nothing is lost and the next task starts clean.
|
|
196
196
|
*/
|
|
197
197
|
readOnly: z.boolean().default(false),
|
|
198
|
+
/**
|
|
199
|
+
* What earlier attempts of this task were asked, written by the plan export for a task that has
|
|
200
|
+
* been run more than once (`earlierPlans` in `session/exports.ts`). History, not instruction:
|
|
201
|
+
* accepted so that an exported plan imports again without a warning about it, and then ignored —
|
|
202
|
+
* an imported task starts from its current text, with no attempts behind it.
|
|
203
|
+
*/
|
|
204
|
+
earlierAttempts: z.array(z.unknown()).optional(),
|
|
198
205
|
});
|
|
199
206
|
const SessionInput = z.object({
|
|
200
207
|
name: z
|
|
@@ -1481,6 +1481,14 @@ export const SYSTEM_GUIDE = [
|
|
|
1481
1481
|
doesEn: 'Saves the log of one earlier attempt, rather than of the latest one, to the Desktop.',
|
|
1482
1482
|
doesBg: 'Запазва на десктопа log-а на конкретен по-ранен опит, а не на последния.',
|
|
1483
1483
|
},
|
|
1484
|
+
{
|
|
1485
|
+
key: 'save.attemptRecord',
|
|
1486
|
+
kind: 'button',
|
|
1487
|
+
en: 'download plan, work and runner of this attempt',
|
|
1488
|
+
bg: 'изтегли план, работа и runner на този опит',
|
|
1489
|
+
doesEn: 'Downloads one earlier attempt as it ran — the plan it was given, what happened to the work, what the runner did — as one file, even if the task was edited since. An attempt that did not end done also keeps the three as plan.json, work.json and runner.json in its run folder.',
|
|
1490
|
+
doesBg: 'Изтегля един по-ранен опит, както е вървял — планът, който е получил, какво е станало с работата, какво е направил runner-ът — в един файл, дори ако задачата е редактирана след това. Опит, който не е завършил готов, пази трите и като plan.json, work.json и runner.json в папката на пускането си.',
|
|
1491
|
+
},
|
|
1484
1492
|
{
|
|
1485
1493
|
key: 'reg.retriedFreshDone',
|
|
1486
1494
|
kind: 'badge',
|
|
@@ -20,13 +20,15 @@ function outcomeSummary(r) {
|
|
|
20
20
|
return `step ${r.id} hit its time limit`;
|
|
21
21
|
case 'idle-timeout':
|
|
22
22
|
return `step ${r.id} produced no output and was stopped`;
|
|
23
|
+
case 'refused':
|
|
24
|
+
// The runner declined it — a policy, a deny pattern, a repeat, damaged text, a shell that is
|
|
25
|
+
// not here — and it never reached a process, so it has no exit code to report. Said as a
|
|
26
|
+
// refusal, apart from a step that ran and exited non-zero: the move after one is to write
|
|
27
|
+
// the step differently, after the other it is to read the output and fix the work.
|
|
28
|
+
return `step ${r.id} was refused by the runner and never ran (no exit code); the reason is under it`;
|
|
23
29
|
case 'aborted':
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
// "aborted by the operator" told the model a person had stopped it, which is a reason to
|
|
27
|
-
// wait rather than to write the step again, and the reason it could act on was in the
|
|
28
|
-
// stderr underneath a sentence contradicting it.
|
|
29
|
-
return r.exitCode === -4 ? `step ${r.id} was not run; see why under it` : `step ${r.id} aborted by the operator`;
|
|
30
|
+
// A person stopped or skipped it — a reason to carry on without it, not to rewrite it.
|
|
31
|
+
return r.exitCode === -4 ? `step ${r.id} was not run: the operator skipped or stopped it` : `step ${r.id} aborted by the operator`;
|
|
30
32
|
case 'spawn-error':
|
|
31
33
|
return `step ${r.id} could not be started`;
|
|
32
34
|
}
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* All three exist for one task and for one run, because those are the two sizes a question
|
|
19
19
|
* comes in: "why did this one fail" and "what did this whole run do".
|
|
20
20
|
*/
|
|
21
|
-
import {
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
22
23
|
import { join } from 'node:path';
|
|
23
24
|
import { withoutPersona } from '../plan/importPlan.js';
|
|
24
25
|
const EXPECTED_HEADER = '### Expected result';
|
|
@@ -164,6 +165,9 @@ export function buildPlanExport(scope) {
|
|
|
164
165
|
task.review = false;
|
|
165
166
|
if (t.readOnly)
|
|
166
167
|
task.readOnly = true;
|
|
168
|
+
const earlier = earlierPlans(t, level2);
|
|
169
|
+
if (earlier.length > 0)
|
|
170
|
+
task.earlierAttempts = earlier;
|
|
167
171
|
return task;
|
|
168
172
|
}),
|
|
169
173
|
};
|
|
@@ -171,6 +175,129 @@ export function buildPlanExport(scope) {
|
|
|
171
175
|
}),
|
|
172
176
|
};
|
|
173
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* What each earlier attempt of a task was asked, in the plan's own words.
|
|
180
|
+
*
|
|
181
|
+
* The plan is the task as it is now, and after "Edit and run again" that is not what the failed
|
|
182
|
+
* attempt ran: its text, its checks, its branch plan may all have changed. The attempt's own
|
|
183
|
+
* snapshot is here, beside it, so the plan of a failure can be read — and handed on — after the
|
|
184
|
+
* task has moved past it. Only what differs from the task as it stands is written out; an attempt
|
|
185
|
+
* that ran the same plan says so by its status alone. The importer ignores this key: a plan that
|
|
186
|
+
* comes back in is the current one.
|
|
187
|
+
*/
|
|
188
|
+
function earlierPlans(t, sessionLevel2) {
|
|
189
|
+
const current = splitPrompt(t.prompt);
|
|
190
|
+
return (t.attempts ?? []).map((a, i) => {
|
|
191
|
+
const out = { attempt: i + 1, status: a.status };
|
|
192
|
+
if (a.reason)
|
|
193
|
+
out.reason = a.reason;
|
|
194
|
+
if (a.title !== t.title)
|
|
195
|
+
out.title = a.title;
|
|
196
|
+
const p = splitPrompt(a.prompt);
|
|
197
|
+
if (p.prompt !== current.prompt)
|
|
198
|
+
out.prompt = p.prompt;
|
|
199
|
+
if (p.expected !== current.expected)
|
|
200
|
+
out.expected = p.expected;
|
|
201
|
+
const aLevel2 = planLevel2({ ...t, level2: a.level2 });
|
|
202
|
+
if (aLevel2 !== planLevel2(t))
|
|
203
|
+
out.level2 = aLevel2 === sessionLevel2 ? '' : aLevel2;
|
|
204
|
+
if (a.checks && JSON.stringify(a.checks) !== JSON.stringify(t.checks ?? []))
|
|
205
|
+
out.checks = a.checks.map(planCheck);
|
|
206
|
+
if (a.vcsPlan && JSON.stringify(a.vcsPlan) !== JSON.stringify(t.vcsPlan ?? {}))
|
|
207
|
+
out.vcs = a.vcsPlan;
|
|
208
|
+
return out;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// ------------------------------------------------------------------------------------------
|
|
212
|
+
// one attempt, as it was
|
|
213
|
+
// ------------------------------------------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* The task as it stood for one of its attempts: that attempt's text, checks, outcome and run
|
|
216
|
+
* folder, with only the attempts before it as its history. The current attempt is the task
|
|
217
|
+
* itself. Null for an attempt the task never had.
|
|
218
|
+
*
|
|
219
|
+
* This is what lets every view above be taken of a past attempt without a second set of
|
|
220
|
+
* builders: the views read a task, and this hands them the task that attempt was.
|
|
221
|
+
*/
|
|
222
|
+
export function taskAtAttempt(task, attempt) {
|
|
223
|
+
const current = task.attempt ?? 1;
|
|
224
|
+
if (attempt === current)
|
|
225
|
+
return task;
|
|
226
|
+
if (!Number.isInteger(attempt) || attempt < 1 || attempt > current)
|
|
227
|
+
return null;
|
|
228
|
+
const a = task.attempts?.[attempt - 1];
|
|
229
|
+
if (!a)
|
|
230
|
+
return null;
|
|
231
|
+
return {
|
|
232
|
+
...task,
|
|
233
|
+
attempt,
|
|
234
|
+
status: a.status,
|
|
235
|
+
runId: a.runId,
|
|
236
|
+
runGroup: a.runGroup,
|
|
237
|
+
startedAt: a.startedAt,
|
|
238
|
+
finishedAt: a.finishedAt,
|
|
239
|
+
iterations: a.iterations,
|
|
240
|
+
summary: a.summary,
|
|
241
|
+
reason: a.reason,
|
|
242
|
+
deviations: a.deviations,
|
|
243
|
+
disputes: a.disputes,
|
|
244
|
+
title: a.title,
|
|
245
|
+
prompt: a.prompt,
|
|
246
|
+
level2: a.level2,
|
|
247
|
+
checks: a.checks ?? task.checks,
|
|
248
|
+
vcsPlan: a.vcsPlan ?? task.vcsPlan,
|
|
249
|
+
checkResults: a.checkResults,
|
|
250
|
+
review: a.review,
|
|
251
|
+
vcs: a.vcs,
|
|
252
|
+
attempts: task.attempts?.slice(0, attempt - 1),
|
|
253
|
+
continuing: undefined,
|
|
254
|
+
finalReply: undefined,
|
|
255
|
+
firstMessage: undefined,
|
|
256
|
+
logFile: a.runId ? 'task-log.txt' : undefined,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/** The session with one of its tasks replaced, so a scope can be built around that version. */
|
|
260
|
+
export function withTask(session, task) {
|
|
261
|
+
return { ...session, tasks: session.tasks.map((t) => (t.id === task.id ? task : t)) };
|
|
262
|
+
}
|
|
263
|
+
/** What the runner export says about the machine, from the configuration in force. */
|
|
264
|
+
export function exportMachine(cfg) {
|
|
265
|
+
return {
|
|
266
|
+
node: process.versions.node,
|
|
267
|
+
platform: process.platform,
|
|
268
|
+
cwd: cfg.resolved.cwd,
|
|
269
|
+
limits: { ...cfg.limits, execution: { commandTimeoutSec: cfg.execution.commandTimeoutSec, idleTimeoutSec: cfg.execution.idleTimeoutSec, mode: cfg.execution.mode } },
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/** The three files an attempt that did not end done leaves in its own run folder. */
|
|
273
|
+
export const ATTEMPT_RECORD_FILES = { plan: 'plan.json', work: 'work.json', runner: 'runner.json' };
|
|
274
|
+
/**
|
|
275
|
+
* Writes the plan, work and runner views of an attempt into its run folder, and returns the folder.
|
|
276
|
+
*
|
|
277
|
+
* For an attempt that did not end done, written as it ends. The same three views can be
|
|
278
|
+
* downloaded at any time — but only while the task's record exists and still describes that
|
|
279
|
+
* attempt: a deleted task or session takes the record with it, and the files are then the only
|
|
280
|
+
* account of what the failure was asked, did and ran into. The run folder outlives both (it goes
|
|
281
|
+
* only by `runsRetentionDays`), which is why they are kept there, beside the transcript they are
|
|
282
|
+
* read from. Null when the attempt has no run folder, which is an attempt that never started.
|
|
283
|
+
*/
|
|
284
|
+
export async function writeAttemptRecord(session, task, runsDir, machine) {
|
|
285
|
+
if (!task.runId)
|
|
286
|
+
return null;
|
|
287
|
+
const dir = join(runsDir, task.runId);
|
|
288
|
+
if (!existsSync(dir))
|
|
289
|
+
return null;
|
|
290
|
+
const scope = {
|
|
291
|
+
sessions: [withTask(session, task)],
|
|
292
|
+
taskFilter: (_s, t) => t.id === task.id,
|
|
293
|
+
label: `${session.name}-${task.title}-attempt-${task.attempt ?? 1}`,
|
|
294
|
+
};
|
|
295
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
296
|
+
await writeFile(join(dir, ATTEMPT_RECORD_FILES.plan), json(buildPlanExport(scope)), 'utf8');
|
|
297
|
+
await writeFile(join(dir, ATTEMPT_RECORD_FILES.work), json(await buildDomainExport(scope, runsDir)), 'utf8');
|
|
298
|
+
await writeFile(join(dir, ATTEMPT_RECORD_FILES.runner), json(await buildBotExport(scope, runsDir, machine)), 'utf8');
|
|
299
|
+
return dir;
|
|
300
|
+
}
|
|
174
301
|
async function readEvents(runsDir, runId) {
|
|
175
302
|
if (!runId)
|
|
176
303
|
return [];
|
package/dist/src/vcs/taskVcs.js
CHANGED
|
@@ -225,6 +225,39 @@ export async function commitTaskResult(session, task, outcome, bus) {
|
|
|
225
225
|
}
|
|
226
226
|
return { ...current, commit: result.commit, commits, files, ...(suspicious.length > 0 ? { suspicious } : {}) };
|
|
227
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Commits what a task left behind when the process running it ended before it could.
|
|
230
|
+
*
|
|
231
|
+
* `commitTaskResult` runs when a task finishes, whatever the outcome, which covers every failure
|
|
232
|
+
* the runner itself decides. It does not cover the process going away mid-task — `npm start`
|
|
233
|
+
* stopped with Ctrl+C, a restart, a crash, the laptop shutting down. Then nothing committed the
|
|
234
|
+
* work: at the next start the task was only marked aborted, its changes stayed loose in the tree,
|
|
235
|
+
* and the next attempt found a dirty repository and ran with version control switched off — so
|
|
236
|
+
* the first attempt's work was neither on a branch nor carried forward, and one `git checkout .`
|
|
237
|
+
* from gone. Called at startup for each task the recovery pass closes.
|
|
238
|
+
*
|
|
239
|
+
* Only on the task's own branch. The runner created that branch for this task and the process
|
|
240
|
+
* that owned it is gone, so what is uncommitted there is the task's work; a repository that has
|
|
241
|
+
* since been moved to another branch is someone's deliberate act, and is left alone with a
|
|
242
|
+
* warning, as a dirty tree is left alone before a task.
|
|
243
|
+
*/
|
|
244
|
+
export async function commitInterrupted(session, task, reason, bus) {
|
|
245
|
+
const settings = session.vcs;
|
|
246
|
+
const branch = task.vcs?.branch;
|
|
247
|
+
if (!settings?.enabled || !settings.commitOnFinish || !branch)
|
|
248
|
+
return undefined;
|
|
249
|
+
const dir = repoDirOf(session);
|
|
250
|
+
const state = await repoState(dir);
|
|
251
|
+
if (!state.isRepo || !state.dirty)
|
|
252
|
+
return undefined;
|
|
253
|
+
if (state.branch !== branch) {
|
|
254
|
+
bus.publish({ sessionId: session.id, taskId: task.id, type: 'vcs-problem', level: 'warn',
|
|
255
|
+
message: `"${task.title}" was interrupted with uncommitted changes, but ${dir} is now on ${state.branch ?? 'no branch'}, ` +
|
|
256
|
+
`not on the task's branch ${branch}; nothing was committed, so the changes are where they are` });
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
return await commitTaskResult(session, task, { status: 'aborted', reason }, bus);
|
|
260
|
+
}
|
|
228
261
|
/**
|
|
229
262
|
* A commit message that says what was asked and how it ended, in git's own shape.
|
|
230
263
|
*
|
package/dist/web/404.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--p9lexLS0u8W5N6VzLavHO--><html lang="en" data-theme="light" data-textsize="normal" data-contrast="normal" data-motion="normal" data-links="plain" data-focus="normal"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/5a10c60adf9c5336.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-0f31b926d9a9c5a4.js"/><script src="/_next/static/chunks/c7879cf7-b5ab1053c1d9a2e7.js" async=""></script><script src="/_next/static/chunks/131-bae98f790a541679.js" async=""></script><script src="/_next/static/chunks/main-app-e3a5f713ba31557e.js" async=""></script><script src="/_next/static/chunks/197-f7c33a0114fc3db3.js" async=""></script><script src="/_next/static/chunks/867-f0e3e152f6f67e45.js" async=""></script><script src="/_next/static/chunks/549-2086c05135bc9753.js" async=""></script><script src="/_next/static/chunks/app/layout-92fa876afe619cf3.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>copilot-operator</title><meta name="description" content="Runs task loops with Microsoft 365 Copilot on this machine."/><script>
|
|
2
2
|
(function () {
|
|
3
3
|
try {
|
|
4
4
|
var d = document.documentElement;
|
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
d.dataset.focus = a.strongFocus ? 'strong' : 'normal';
|
|
13
13
|
} catch (e) {}
|
|
14
14
|
})();
|
|
15
|
-
</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><a class="skip-link" href="#main">Skip to the content</a><div class="wrap"><header class="top"><h1><a href="/">copilot-operator</a></h1><nav aria-label="Main"><a href="/"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M4 5h16v4H4zM4 11h16v4H4zM4 17h10v2H4z"></path></svg><span>Sessions</span></a><a href="/defaults"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm8.5 4 1.5 1-1.6 2.8-1.8-.4a7 7 0 0 1-1.5 1.5l.4 1.8L14.7 21l-1-1.5a7 7 0 0 1-3.4 0l-1 1.5-2.8-1.6.4-1.8A7 7 0 0 1 5.4 16l-1.8.4L2 13.6 3.5 12.6V11.4L2 10.4l1.6-2.8 1.8.4A7 7 0 0 1 6.9 6.5l-.4-1.8L9.3 3l1 1.5a7 7 0 0 1 3.4 0l1-1.5 2.8 1.6-.4 1.8a7 7 0 0 1 1.5 1.5l1.8-.4L22 10.4l-1.5 1z"></path></svg><span>Settings</span></a><a href="/import"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 3v10m0 0 4-4m-4 4-4-4M4 15v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span>Plan from JSON</span></a><a href="/history"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 3a9 9 0 1 0 8.5 6M12 7v5l3 2M3 4v5h5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span>Task register</span></a><a href="/level1"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M6 3h9l5 5v13H6zM14 3v6h6M9 13h7M9 17h7" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path></svg><span>Level 1 prompt</span></a><a href="/presets"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M5 4h14v4H5zM5 10h14v4H5zM5 16h9v4H5z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path></svg><span>Level 2 presets</span></a><a href="/appearance"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 3a9 9 0 0 0 0 18c1.5 0 2-1 2-2s-1-1.5-1-2.5S14 15 15.5 15H17a4 4 0 0 0 4-4 8 8 0 0 0-9-8zM8 9a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm4-3a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm4 3a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"></path></svg><span>Appearance</span></a><a href="/system"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M4 5h16v10H4zM2 19h20M9 15v4M15 15v4" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path></svg><span>System</span></a><select aria-label="Language" style="width:auto"><option value="en" selected="">English</option><option value="bg">Български</option></select></nav></header><main id="main"><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--></main></div><script src="/_next/static/chunks/webpack-0f31b926d9a9c5a4.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[1205,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"themeScript\"]\n3:I[1205,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"AppearanceProvider\"]\n4:I[867,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"LanguageProvider\"]\n5:I[1035,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"DialogHost\"]\n6:I[8214,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"SkipLink\"]\n7:I[8197,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"\"]\n8:I[8214,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"Nav\"]\n9:I[6657,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-55627c5122ff6336.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"GlobalApprovals\"]\na:I[5204,[],\"\"]\nb:I[2642,[],\"\"]\nc:I[433,[],\"OutletBoundary\"]\ne:I[9884,[],\"AsyncMetadataOutlet\"]\n10:I[433,[],\"ViewportBoundary\"]\n12:I[433,[],\"MetadataBoundary\"]\n13:\"$Sreact.suspense\"\n15:I[3536,[],\"\"]\n:HL[\"/_next/static/css/5a10c60adf9c5336.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"QwjyyBjkO5xlKFU5O6Wux\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/5a10c60adf9c5336.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"suppressHydrationWarning\":true,\"data-theme\":\"light\",\"data-textsize\":\"normal\",\"data-contrast\":\"normal\",\"data-motion\":\"normal\",\"data-links\":\"plain\",\"data-focus\":\"normal\",\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$2\"}}]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$L5\",null,{\"children\":[[\"$\",\"$L6\",null,{}],[\"$\",\"div\",null,{\"className\":\"wrap\",\"children\":[[\"$\",\"header\",null,{\"className\":\"top\",\"children\":[[\"$\",\"h1\",null,{\"children\":[\"$\",\"$L7\",null,{\"href\":\"/\",\"children\":\"copilot-operator\"}]}],[\"$\",\"$L8\",null,{}]]}],[\"$\",\"$L9\",null,{}],[\"$\",\"main\",null,{\"id\":\"main\",\"children\":[\"$\",\"$La\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$Lb\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]]}]]}]}]}]}]]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$La\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$Lb\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$Lc\",null,{\"children\":[\"$Ld\",[\"$\",\"$Le\",null,{\"promise\":\"$@f\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$L10\",null,{\"children\":\"$L11\"}],null],[\"$\",\"$L12\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$13\",null,{\"fallback\":null,\"children\":\"$L14\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$15\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"11:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nd:null\n"])</script><script>self.__next_f.push([1,"f:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"copilot-operator\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Runs task loops with Microsoft 365 Copilot on this machine.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"14:\"$f:metadata\"\n"])</script></body></html>
|
|
15
|
+
</script><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><a class="skip-link" href="#main">Skip to the content</a><div class="wrap"><header class="top"><h1><a href="/">copilot-operator</a></h1><nav aria-label="Main"><a href="/"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M4 5h16v4H4zM4 11h16v4H4zM4 17h10v2H4z"></path></svg><span>Sessions</span></a><a href="/defaults"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm8.5 4 1.5 1-1.6 2.8-1.8-.4a7 7 0 0 1-1.5 1.5l.4 1.8L14.7 21l-1-1.5a7 7 0 0 1-3.4 0l-1 1.5-2.8-1.6.4-1.8A7 7 0 0 1 5.4 16l-1.8.4L2 13.6 3.5 12.6V11.4L2 10.4l1.6-2.8 1.8.4A7 7 0 0 1 6.9 6.5l-.4-1.8L9.3 3l1 1.5a7 7 0 0 1 3.4 0l1-1.5 2.8 1.6-.4 1.8a7 7 0 0 1 1.5 1.5l1.8-.4L22 10.4l-1.5 1z"></path></svg><span>Settings</span></a><a href="/import"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 3v10m0 0 4-4m-4 4-4-4M4 15v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span>Plan from JSON</span></a><a href="/history"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 3a9 9 0 1 0 8.5 6M12 7v5l3 2M3 4v5h5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span>Task register</span></a><a href="/level1"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M6 3h9l5 5v13H6zM14 3v6h6M9 13h7M9 17h7" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path></svg><span>Level 1 prompt</span></a><a href="/presets"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M5 4h14v4H5zM5 10h14v4H5zM5 16h9v4H5z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path></svg><span>Level 2 presets</span></a><a href="/appearance"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 3a9 9 0 0 0 0 18c1.5 0 2-1 2-2s-1-1.5-1-2.5S14 15 15.5 15H17a4 4 0 0 0 4-4 8 8 0 0 0-9-8zM8 9a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm4-3a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm4 3a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"></path></svg><span>Appearance</span></a><a href="/system"><svg class="nav-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M4 5h16v10H4zM2 19h20M9 15v4M15 15v4" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path></svg><span>System</span></a><select aria-label="Language" style="width:auto"><option value="en" selected="">English</option><option value="bg">Български</option></select></nav></header><main id="main"><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--></main></div><script src="/_next/static/chunks/webpack-0f31b926d9a9c5a4.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[1205,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"themeScript\"]\n3:I[1205,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"AppearanceProvider\"]\n4:I[867,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"LanguageProvider\"]\n5:I[1035,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"DialogHost\"]\n6:I[8214,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"SkipLink\"]\n7:I[8197,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"\"]\n8:I[8214,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"Nav\"]\n9:I[6657,[\"197\",\"static/chunks/197-f7c33a0114fc3db3.js\",\"867\",\"static/chunks/867-f0e3e152f6f67e45.js\",\"549\",\"static/chunks/549-2086c05135bc9753.js\",\"177\",\"static/chunks/app/layout-92fa876afe619cf3.js\"],\"GlobalApprovals\"]\na:I[5204,[],\"\"]\nb:I[2642,[],\"\"]\nc:I[433,[],\"OutletBoundary\"]\ne:I[9884,[],\"AsyncMetadataOutlet\"]\n10:I[433,[],\"ViewportBoundary\"]\n12:I[433,[],\"MetadataBoundary\"]\n13:\"$Sreact.suspense\"\n15:I[3536,[],\"\"]\n:HL[\"/_next/static/css/5a10c60adf9c5336.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"p9lexLS0u8W5N6VzLavHO\",\"p\":\"\",\"c\":[\"\",\"_not-found\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/5a10c60adf9c5336.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"suppressHydrationWarning\":true,\"data-theme\":\"light\",\"data-textsize\":\"normal\",\"data-contrast\":\"normal\",\"data-motion\":\"normal\",\"data-links\":\"plain\",\"data-focus\":\"normal\",\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"script\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$2\"}}]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"$L3\",null,{\"children\":[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$L5\",null,{\"children\":[[\"$\",\"$L6\",null,{}],[\"$\",\"div\",null,{\"className\":\"wrap\",\"children\":[[\"$\",\"header\",null,{\"className\":\"top\",\"children\":[[\"$\",\"h1\",null,{\"children\":[\"$\",\"$L7\",null,{\"href\":\"/\",\"children\":\"copilot-operator\"}]}],[\"$\",\"$L8\",null,{}]]}],[\"$\",\"$L9\",null,{}],[\"$\",\"main\",null,{\"id\":\"main\",\"children\":[\"$\",\"$La\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$Lb\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]]}]]}]}]}]}]]}]]}],{\"children\":[\"/_not-found\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$La\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$Lb\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$Lc\",null,{\"children\":[\"$Ld\",[\"$\",\"$Le\",null,{\"promise\":\"$@f\"}]]}]]}],{},null,false]},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[[\"$\",\"$L10\",null,{\"children\":\"$L11\"}],null],[\"$\",\"$L12\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$13\",null,{\"fallback\":null,\"children\":\"$L14\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$15\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"11:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\nd:null\n"])</script><script>self.__next_f.push([1,"f:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"copilot-operator\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Runs task loops with Microsoft 365 Copilot on this machine.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"14:\"$f:metadata\"\n"])</script></body></html>
|