team 0.1.1 → 0.2.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 +38 -12
- package/dist/ansi.d.ts +13 -0
- package/dist/ansi.js +75 -0
- package/dist/approve/approval.d.ts +11 -0
- package/dist/approve/approval.js +80 -8
- package/dist/approve/fingerprint.d.ts +29 -6
- package/dist/approve/fingerprint.js +57 -32
- package/dist/budgets/gate.d.ts +5 -4
- package/dist/budgets/gate.js +23 -14
- package/dist/budgets/readings.d.ts +57 -9
- package/dist/budgets/readings.js +134 -18
- package/dist/budgets/run.js +8 -2
- package/dist/budgets/table.d.ts +4 -2
- package/dist/budgets/table.js +29 -11
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +8 -5
- package/dist/commands/add.d.ts +0 -1
- package/dist/commands/add.js +18 -9
- package/dist/commands/approve.js +17 -1
- package/dist/commands/doctor.d.ts +17 -1
- package/dist/commands/doctor.js +128 -8
- package/dist/commands/down.d.ts +6 -2
- package/dist/commands/down.js +40 -14
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +4 -2
- package/dist/commands/remove.d.ts +2 -0
- package/dist/commands/remove.js +26 -4
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +22 -3
- package/dist/commands/up.d.ts +3 -1
- package/dist/commands/up.js +12 -6
- package/dist/commands/watch.d.ts +4 -0
- package/dist/commands/watch.js +99 -19
- package/dist/conformance/adapter.d.ts +5 -0
- package/dist/conformance/adapter.js +44 -0
- package/dist/file/check.d.ts +20 -0
- package/dist/file/check.js +112 -0
- package/dist/file/lines.js +13 -2
- package/dist/file/sections/budgets.d.ts +15 -0
- package/dist/file/sections/budgets.js +180 -0
- package/dist/file/sections/coordinator.d.ts +2 -0
- package/dist/file/sections/coordinator.js +11 -0
- package/dist/file/sections/format.d.ts +2 -0
- package/dist/file/sections/format.js +13 -0
- package/dist/file/sections/identity.d.ts +2 -0
- package/dist/file/sections/identity.js +103 -0
- package/dist/file/sections/index.d.ts +9 -0
- package/dist/file/sections/index.js +43 -0
- package/dist/file/sections/lead.d.ts +4 -0
- package/dist/file/sections/lead.js +18 -0
- package/dist/file/sections/limits.d.ts +2 -0
- package/dist/file/sections/limits.js +43 -0
- package/dist/file/sections/machine.d.ts +2 -0
- package/dist/file/sections/machine.js +38 -0
- package/dist/file/sections/operator.d.ts +2 -0
- package/dist/file/sections/operator.js +11 -0
- package/dist/file/sections/project.d.ts +2 -0
- package/dist/file/sections/project.js +13 -0
- package/dist/file/sections/rules.d.ts +2 -0
- package/dist/file/sections/rules.js +9 -0
- package/dist/file/sections/seats.d.ts +7 -0
- package/dist/file/sections/seats.js +161 -0
- package/dist/file/sections/section.d.ts +32 -0
- package/dist/file/sections/section.js +7 -0
- package/dist/file/sections/session.d.ts +2 -0
- package/dist/file/sections/session.js +16 -0
- package/dist/file/sections/tools.d.ts +2 -0
- package/dist/file/sections/tools.js +43 -0
- package/dist/file/sections/trust.d.ts +2 -0
- package/dist/file/sections/trust.js +20 -0
- package/dist/file/sections/units.d.ts +16 -0
- package/dist/file/sections/units.js +8 -0
- package/dist/file/sections/visibility.d.ts +2 -0
- package/dist/file/sections/visibility.js +12 -0
- package/dist/file/sections/watch-checks.d.ts +9 -0
- package/dist/file/sections/watch-checks.js +14 -0
- package/dist/file/sections/watch.d.ts +14 -0
- package/dist/file/sections/watch.js +111 -0
- package/dist/file/sections/workspace.d.ts +4 -0
- package/dist/file/sections/workspace.js +77 -0
- package/dist/file/types.d.ts +6 -0
- package/dist/file/validate.d.ts +4 -12
- package/dist/file/validate.js +45 -668
- package/dist/herdr.d.ts +5 -0
- package/dist/herdr.js +51 -12
- package/dist/launch/agent.d.ts +1 -0
- package/dist/launch/agent.js +13 -0
- package/dist/launch/deliver.d.ts +7 -1
- package/dist/launch/deliver.js +159 -7
- package/dist/launch/execute.d.ts +4 -2
- package/dist/launch/execute.js +36 -13
- package/dist/launch/plan.js +16 -2
- package/dist/launch/rules.d.ts +4 -2
- package/dist/launch/rules.js +17 -3
- package/dist/profiles/antigravity.yaml +25 -2
- package/dist/profiles/claude-code.yaml +51 -6
- package/dist/profiles/codex.yaml +14 -1
- package/dist/profiles/cursor.yaml +46 -3
- package/dist/profiles/overrides.d.ts +67 -0
- package/dist/profiles/overrides.js +190 -0
- package/dist/profiles/profile.d.ts +3 -0
- package/dist/profiles/profile.js +5 -1
- package/dist/state.d.ts +9 -4
- package/dist/state.js +37 -0
- package/dist/status/compare.js +7 -4
- package/dist/status/statusline.js +3 -1
- package/dist/store/store.d.ts +5 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +7 -0
- package/dist/watch/check.d.ts +1 -0
- package/dist/watch/checks/budget.js +50 -51
- package/dist/watch/dialect.d.ts +6 -1
- package/dist/watch/dialect.js +68 -12
- package/dist/watch/pass.d.ts +10 -2
- package/dist/watch/pass.js +70 -29
- package/dist/watch/screen-core.d.ts +41 -4
- package/dist/watch/screen-core.js +510 -89
- package/dist/watch/screen-data.d.ts +34 -0
- package/dist/watch/screen-file.d.ts +8 -2
- package/dist/watch/screen-file.js +344 -39
- package/dist/watch/screen-profile.d.ts +19 -0
- package/dist/watch/screen-profile.js +1 -0
- package/dist/watch/screen.d.ts +25 -6
- package/dist/watch/screen.js +48 -9
- package/examples/team.yaml +8 -0
- package/package.json +9 -3
- package/schema/team.schema.json +498 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { countedFor } from "../../budgets/readings.js";
|
|
2
2
|
import { WINDOWS } from "../../profiles/quota.js";
|
|
3
3
|
import { reported } from "../check.js";
|
|
4
4
|
export const budget = {
|
|
@@ -40,65 +40,64 @@ export const budget = {
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
/**
|
|
43
|
-
* The account's
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* The account's figures this pass, per window, from the first source that has one for it (§ 4.3,
|
|
44
|
+
* § 5) — the same rule `countedFor` applies for the launch gate and the table, so the three can't
|
|
45
|
+
* drift apart. A check reading counts while it is fresh; a screen reading counts when the verdict
|
|
46
|
+
* keeps it — fresh, stale, or refusing — and a bare first sight is `unseen`. Null: nothing counts
|
|
47
|
+
* at all. A spend check is one number and no window, fresh by § 5.
|
|
46
48
|
*/
|
|
47
49
|
function countedOf(name, account, team, staleMs, now) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
const outcome = team.outcomes.find((one) => one.account === name);
|
|
51
|
+
const read = outcome && outcome.state === 'read' ? outcome : null;
|
|
52
|
+
if (read && read.reading.kind === 'spend' && account.sources.includes('check') && now - read.reading.at < staleMs) {
|
|
53
|
+
return { kind: 'spend', amount: read.reading.amount, currency: read.reading.currency };
|
|
54
|
+
}
|
|
55
|
+
const windows = [];
|
|
56
|
+
let seen = false;
|
|
57
|
+
for (const window of WINDOWS) {
|
|
58
|
+
// The readings of one window, by source: the screen readings the core folded in, and the
|
|
59
|
+
// check's when this pass's outcome carries that window (§ 5).
|
|
60
|
+
const screen = team.readings.filter((reading) => reading.account === name && reading.window === window && reading.source === 'status_line');
|
|
61
|
+
const checks = read && read.reading.kind === 'subscription'
|
|
62
|
+
? read.reading.windows
|
|
63
|
+
.filter((one) => one.window === window)
|
|
64
|
+
.map((one) => ({
|
|
65
|
+
account: name,
|
|
66
|
+
window: one.window,
|
|
67
|
+
left: one.left,
|
|
68
|
+
used: one.used,
|
|
69
|
+
changedAt: one.at,
|
|
70
|
+
resetsAt: one.resetsAt,
|
|
71
|
+
seat: null,
|
|
72
|
+
source: 'check',
|
|
73
|
+
confirmed: true,
|
|
74
|
+
}))
|
|
75
|
+
: [];
|
|
76
|
+
if (!screen.length && !checks.length)
|
|
77
|
+
continue;
|
|
78
|
+
const result = countedFor(account.sources, screen, checks, now, staleMs, account.reserve);
|
|
79
|
+
if (result.kind === 'unknown')
|
|
80
|
+
continue;
|
|
81
|
+
if (result.kind === 'unconfirmed') {
|
|
82
|
+
seen = true;
|
|
64
83
|
continue;
|
|
65
84
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
groups.set(reading.window, group);
|
|
73
|
-
}
|
|
74
|
-
const windows = [];
|
|
75
|
-
let seen = false;
|
|
76
|
-
for (const group of groups.values()) {
|
|
77
|
-
const result = verdict(group, now, staleMs, account.reserve);
|
|
78
|
-
if (result.kind === 'unknown')
|
|
79
|
-
continue;
|
|
80
|
-
if (result.kind === 'unconfirmed') {
|
|
81
|
-
seen = true;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
windows.push({
|
|
85
|
-
window: result.reading.window,
|
|
86
|
-
left: result.reading.left,
|
|
87
|
-
used: result.reading.used,
|
|
88
|
-
resetsAt: result.reading.resetsAt,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (windows.length)
|
|
92
|
-
return { kind: 'subscription', windows };
|
|
93
|
-
if (seen)
|
|
94
|
-
return 'unseen';
|
|
95
|
-
continue;
|
|
85
|
+
windows.push({
|
|
86
|
+
window: result.reading.window,
|
|
87
|
+
left: result.reading.left,
|
|
88
|
+
used: result.reading.used,
|
|
89
|
+
resetsAt: result.reading.resetsAt,
|
|
90
|
+
});
|
|
96
91
|
}
|
|
92
|
+
if (windows.length)
|
|
93
|
+
return { kind: 'subscription', windows };
|
|
94
|
+
if (seen)
|
|
95
|
+
return 'unseen';
|
|
97
96
|
return null;
|
|
98
97
|
}
|
|
99
98
|
/** The account reads unknown while a seat that spends it is running: the operator can stop it. */
|
|
100
99
|
function unknown(name, team, ctx) {
|
|
101
|
-
const on = team.seats.filter((seat) => seat.running && seat.
|
|
100
|
+
const on = team.seats.filter((seat) => seat.running && seat.account === name).map((seat) => seat.name);
|
|
102
101
|
if (!on.length)
|
|
103
102
|
return null;
|
|
104
103
|
const verb = on.length === 1 ? 'runs' : 'run';
|
package/dist/watch/dialect.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export declare class DialectError extends Error {
|
|
2
2
|
constructor(message: string);
|
|
3
3
|
}
|
|
4
|
-
/** Compile one pattern. `ignoreCase` is the stage's flag, never an inline `(?i)`.
|
|
4
|
+
/** Compile one pattern. `ignoreCase` is the stage's flag, never an inline `(?i)`. The
|
|
5
|
+
* flag is an ASCII fold: each ASCII letter of the pattern becomes its two-letter class,
|
|
6
|
+
* `[Aa]`, exactly what the hand-spelled patterns wrote out. Folding that lives in the
|
|
7
|
+
* pattern's own text travels with it — the shared dialect's other engine compiles the
|
|
8
|
+
* same source — and the engine's wider Unicode folding (a long s for s, the Kelvin
|
|
9
|
+
* sign for k) never applies. */
|
|
5
10
|
export declare function compilePattern(source: string, ignoreCase?: boolean): RegExp;
|
package/dist/watch/dialect.js
CHANGED
|
@@ -20,25 +20,31 @@ export class DialectError extends Error {
|
|
|
20
20
|
this.name = 'DialectError';
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
/** Compile one pattern. `ignoreCase` is the stage's flag, never an inline `(?i)`.
|
|
23
|
+
/** Compile one pattern. `ignoreCase` is the stage's flag, never an inline `(?i)`. The
|
|
24
|
+
* flag is an ASCII fold: each ASCII letter of the pattern becomes its two-letter class,
|
|
25
|
+
* `[Aa]`, exactly what the hand-spelled patterns wrote out. Folding that lives in the
|
|
26
|
+
* pattern's own text travels with it — the shared dialect's other engine compiles the
|
|
27
|
+
* same source — and the engine's wider Unicode folding (a long s for s, the Kelvin
|
|
28
|
+
* sign for k) never applies. */
|
|
24
29
|
export function compilePattern(source, ignoreCase = false) {
|
|
25
30
|
if (source.length === 0)
|
|
26
31
|
throw new DialectError('a pattern is empty');
|
|
27
32
|
if (source.length > MAX_PATTERN)
|
|
28
33
|
throw new DialectError(`a pattern is longer than ${MAX_PATTERN} characters`);
|
|
29
34
|
refuseSpelling(source);
|
|
30
|
-
const parser = new Parser(source);
|
|
35
|
+
const parser = new Parser(source, ignoreCase);
|
|
31
36
|
const body = parser.parseAlt();
|
|
32
37
|
if (!parser.done())
|
|
33
38
|
throw new DialectError('a pattern has an extra ")"');
|
|
34
|
-
|
|
35
|
-
return new RegExp(body.source, flags);
|
|
39
|
+
return new RegExp(body.source, 'u');
|
|
36
40
|
}
|
|
37
41
|
class Parser {
|
|
38
42
|
at = 0;
|
|
39
43
|
source;
|
|
40
|
-
|
|
44
|
+
ignoreCase;
|
|
45
|
+
constructor(source, ignoreCase) {
|
|
41
46
|
this.source = source;
|
|
47
|
+
this.ignoreCase = ignoreCase;
|
|
42
48
|
}
|
|
43
49
|
done() {
|
|
44
50
|
return this.at >= this.source.length;
|
|
@@ -114,7 +120,15 @@ class Parser {
|
|
|
114
120
|
if (ch === ')')
|
|
115
121
|
throw new DialectError('a pattern has an extra ")"');
|
|
116
122
|
const cp = this.readCodePoint();
|
|
117
|
-
return this.leaf(
|
|
123
|
+
return this.leaf(this.fold(cp), true);
|
|
124
|
+
}
|
|
125
|
+
// A literal under ignore_case: an ASCII letter becomes its two-letter class, `[Aa]`,
|
|
126
|
+
// as the hand-spelled patterns wrote it; everything else stands as itself.
|
|
127
|
+
fold(cp) {
|
|
128
|
+
const other = asciiCaseSwapped(cp);
|
|
129
|
+
if (!this.ignoreCase || other === null)
|
|
130
|
+
return escapeLiteral(cp);
|
|
131
|
+
return emitClass(false, [cp, other]);
|
|
118
132
|
}
|
|
119
133
|
parseGroup() {
|
|
120
134
|
this.expect('(');
|
|
@@ -213,7 +227,7 @@ class Parser {
|
|
|
213
227
|
parts.push({ kind: 'char', cp: this.readCodePoint() });
|
|
214
228
|
}
|
|
215
229
|
this.expect(']');
|
|
216
|
-
return classSource(negated, foldRanges(parts));
|
|
230
|
+
return classSource(negated, foldRanges(parts, this.ignoreCase));
|
|
217
231
|
}
|
|
218
232
|
parseClassEscape() {
|
|
219
233
|
this.expect('\\');
|
|
@@ -318,7 +332,9 @@ function refuseSpelling(source) {
|
|
|
318
332
|
}
|
|
319
333
|
// A class is a finite set, except a complement shorthand (`\S`, `\D`, `\W`), whose
|
|
320
334
|
// negation brings it back to a finite set. `[^\S\n]` is the whitespace set without
|
|
321
|
-
// a newline, which is what the working-line pattern relies on.
|
|
335
|
+
// a newline, which is what the working-line pattern relies on. A positive class that
|
|
336
|
+
// mixed one with other members would compile to a class nobody wrote (`[\D0-9]` is
|
|
337
|
+
// everything), so it is refused rather than rewritten.
|
|
322
338
|
function classSource(negated, parts) {
|
|
323
339
|
const positive = new Set();
|
|
324
340
|
const complements = [];
|
|
@@ -333,13 +349,16 @@ function classSource(negated, parts) {
|
|
|
333
349
|
}
|
|
334
350
|
if (complements.length === 0)
|
|
335
351
|
return emitClass(negated, [...positive]);
|
|
352
|
+
if (!negated && positive.size > 0) {
|
|
353
|
+
throw new DialectError('a class cannot mix a complement shorthand with other members: negate the class, or write the shorthand alone');
|
|
354
|
+
}
|
|
336
355
|
let inter = new Set(complements[0]);
|
|
337
356
|
for (const extra of complements.slice(1))
|
|
338
357
|
inter = new Set([...inter].filter((cp) => extra.includes(cp)));
|
|
339
358
|
const remaining = [...inter].filter((cp) => !positive.has(cp));
|
|
340
359
|
return emitClass(!negated, remaining);
|
|
341
360
|
}
|
|
342
|
-
function foldRanges(parts) {
|
|
361
|
+
function foldRanges(parts, ignoreCase = false) {
|
|
343
362
|
const out = [];
|
|
344
363
|
for (let i = 0; i < parts.length; i++) {
|
|
345
364
|
const part = parts[i];
|
|
@@ -358,7 +377,38 @@ function foldRanges(parts) {
|
|
|
358
377
|
else
|
|
359
378
|
out.push(part);
|
|
360
379
|
}
|
|
361
|
-
|
|
380
|
+
if (!ignoreCase)
|
|
381
|
+
return out;
|
|
382
|
+
// Under ignore_case every ASCII letter of the class carries its other case too, as the
|
|
383
|
+
// letter classes of the hand-spelled patterns did. A complement shorthand (`\S` and
|
|
384
|
+
// the like) is written as its own set: its members are not the pattern's letters.
|
|
385
|
+
return out.map((part) => {
|
|
386
|
+
if (part.kind === 'char') {
|
|
387
|
+
const other = asciiCaseSwapped(part.cp);
|
|
388
|
+
return other === null ? part : { kind: 'chars', cps: [part.cp, other] };
|
|
389
|
+
}
|
|
390
|
+
if (part.kind === 'chars') {
|
|
391
|
+
const cps = [...part.cps];
|
|
392
|
+
for (const cp of part.cps) {
|
|
393
|
+
const other = asciiCaseSwapped(cp);
|
|
394
|
+
if (other !== null)
|
|
395
|
+
cps.push(other);
|
|
396
|
+
}
|
|
397
|
+
return { kind: 'chars', cps };
|
|
398
|
+
}
|
|
399
|
+
return part;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
// The other case of an ASCII letter, or null for every other character: the whole of a
|
|
403
|
+
// pattern's ignore_case fold. The engine's Unicode folding — a long s (U+017F) for s,
|
|
404
|
+
// the Kelvin sign (U+212A) for k — is deliberately not applied; the hand-spelled
|
|
405
|
+
// classes this flag replaced never applied it.
|
|
406
|
+
function asciiCaseSwapped(cp) {
|
|
407
|
+
if (cp >= 0x41 && cp <= 0x5a)
|
|
408
|
+
return cp + 0x20;
|
|
409
|
+
if (cp >= 0x61 && cp <= 0x7a)
|
|
410
|
+
return cp - 0x20;
|
|
411
|
+
return null;
|
|
362
412
|
}
|
|
363
413
|
function emitClass(negated, cps) {
|
|
364
414
|
const sorted = [...new Set(cps)].sort((a, b) => a - b);
|
|
@@ -395,7 +445,7 @@ function classChar(cp) {
|
|
|
395
445
|
if (cp === 0x2d || cp === 0x5d || cp === 0x5e || cp === 0x5c)
|
|
396
446
|
return `\\${String.fromCodePoint(cp)}`;
|
|
397
447
|
if (cp < 0x20 || cp > 0x7e)
|
|
398
|
-
return
|
|
448
|
+
return codePointEscape(cp);
|
|
399
449
|
return String.fromCodePoint(cp);
|
|
400
450
|
}
|
|
401
451
|
function escapeLiteral(cp) {
|
|
@@ -403,9 +453,15 @@ function escapeLiteral(cp) {
|
|
|
403
453
|
if ('\\.^$|?*+()[]{}'.includes(ch))
|
|
404
454
|
return `\\${ch}`;
|
|
405
455
|
if (cp < 0x20 || cp > 0x7e)
|
|
406
|
-
return
|
|
456
|
+
return codePointEscape(cp);
|
|
407
457
|
return ch;
|
|
408
458
|
}
|
|
459
|
+
// A code point above U+FFFF needs the "\u{…}" form, which every pattern can carry: the "u"
|
|
460
|
+
// flag is always set (compilePattern). A four-digit escape cannot spell it — the digits
|
|
461
|
+
// would spill into the following character.
|
|
462
|
+
function codePointEscape(cp) {
|
|
463
|
+
return cp > 0xffff ? `\\u{${cp.toString(16)}}` : `\\u${cp.toString(16).padStart(4, '0')}`;
|
|
464
|
+
}
|
|
409
465
|
function seqSource(atoms) {
|
|
410
466
|
return atoms.map((atom) => atom.source).join('');
|
|
411
467
|
}
|
package/dist/watch/pass.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type Seen } from '../budgets/readings.ts';
|
|
2
2
|
import type { CheckOutcome } from '../budgets/run.ts';
|
|
3
3
|
import type { TeamFile } from '../file/types.ts';
|
|
4
|
+
import { type QuotaPattern } from '../profiles/quota.ts';
|
|
4
5
|
import type { SessionState } from '../state.ts';
|
|
5
6
|
import type { Live } from '../status/compare.ts';
|
|
6
7
|
import { type Report, type SeatCheck, type SeatHistory, type TeamCheck } from './check.ts';
|
|
7
8
|
import type { Machine } from './machine.ts';
|
|
9
|
+
import { type Screen } from './screen.ts';
|
|
8
10
|
export type Memory = {
|
|
9
11
|
history: Record<string, SeatHistory>;
|
|
10
12
|
slots: Record<string, unknown>;
|
|
@@ -35,8 +37,14 @@ export type PassInput = {
|
|
|
35
37
|
now: number;
|
|
36
38
|
memory: Memory;
|
|
37
39
|
approval?: string[] | null;
|
|
38
|
-
watch
|
|
40
|
+
watch: TeamFile['watch'];
|
|
39
41
|
outcomes?: readonly CheckOutcome[];
|
|
40
42
|
budgets?: TeamFile['budgets'];
|
|
43
|
+
readings?: readonly Seen[];
|
|
44
|
+
foreground?: Readonly<Record<string, readonly string[] | null>>;
|
|
45
|
+
/** Screen readings with the approved overrides applied. The shipped profiles, when omitted. */
|
|
46
|
+
readScreen?: (cli: string, screen: string | undefined) => Screen;
|
|
47
|
+
/** Quota patterns in force, shipped plus the approved override. The shipped list, when omitted. */
|
|
48
|
+
quotaFor?: (cli: string) => readonly QuotaPattern[];
|
|
41
49
|
};
|
|
42
|
-
export declare function pass({ team, state, live, machine, now, memory, approval, watch, outcomes, budgets, }: PassInput): PassResult;
|
|
50
|
+
export declare function pass({ team, state, live, machine, now, memory, approval, watch, outcomes, budgets, readings: stored, foreground, readScreen: read, quotaFor: patternsOf, }: PassInput): PassResult;
|
package/dist/watch/pass.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// runs the registered checks in the order it owns, applies the report-once rule, and dials the
|
|
3
3
|
// nudge. A check never reads herdr, a screen or the file itself — only the observation it is
|
|
4
4
|
// handed (RFC 0002 § 4.2).
|
|
5
|
-
import {
|
|
6
|
-
import { observe,
|
|
7
|
-
import {
|
|
5
|
+
import { seatNamed } from "../approve/fingerprint.js";
|
|
6
|
+
import { observe, observeCheck } from "../budgets/readings.js";
|
|
7
|
+
import { reportedLiveAgent } from "../launch/agent.js";
|
|
8
|
+
import { profileFor, quotaFor as shippedQuota } from "../profiles/profile.js";
|
|
8
9
|
import { figuresOf } from "../profiles/quota.js";
|
|
9
10
|
import { seatModel } from "../status/statusline.js";
|
|
10
11
|
import { ALWAYS_ON, } from "./check.js";
|
|
@@ -33,8 +34,8 @@ export function newMemory() {
|
|
|
33
34
|
export const NUDGE_TEXT = 'Team watch: reports are waiting in .agents/team.log';
|
|
34
35
|
// The seat checks, in the order their reports land in the log; the team checks, after them. The
|
|
35
36
|
// order is the core's: a seat's wrong model is reported before its permission prompt, and every
|
|
36
|
-
// seat before the team. Disabling a check in the
|
|
37
|
-
// except the four that can't be turned off.
|
|
37
|
+
// seat before the team. Disabling a check in the `watch` section in force takes it out of the
|
|
38
|
+
// run, except the four that can't be turned off.
|
|
38
39
|
export const SEAT_CHECKS = [missing, modelDrift, attention, unsent, idle];
|
|
39
40
|
export const TEAM_CHECKS = [extra, teamIdle, approval, load, memoryCheck, disk, swapFree, swapGrowth, budget];
|
|
40
41
|
// The one exclusive reading of a seat's screen and herdr status. herdr can report a seat at a
|
|
@@ -52,16 +53,20 @@ function attentionOf(screen, status, quiet) {
|
|
|
52
53
|
}
|
|
53
54
|
// The figures off a seat's own pane: only a composer screen — idle, unsent or working — shows a
|
|
54
55
|
// status row at all, and only that row's line is read. A dialog, a question, a trust screen or an
|
|
55
|
-
// unknown one gives no figures, and neither does a line the seat printed or typed.
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// unknown one gives no figures, and neither does a line the seat printed or typed. And only a
|
|
57
|
+
// pane where herdr reports the seat's CLI still running: a shell's last row is a last row by
|
|
58
|
+
// position, not a status row, and a pane herdr could not read gives none either — no figure is
|
|
59
|
+
// invented where the CLI was not seen. (`down` and `remove` read that same null the other way at
|
|
60
|
+
// their departure wait; not knowing must not end a wait.)
|
|
61
|
+
function quotaOf(cli, screen, pane, runs, patterns) {
|
|
62
|
+
if (pane === undefined || !runs)
|
|
58
63
|
return [];
|
|
59
64
|
if (screen.kind !== 'idle' && screen.kind !== 'unsent' && screen.kind !== 'working')
|
|
60
65
|
return [];
|
|
61
|
-
return figuresOf(
|
|
66
|
+
return figuresOf(patterns, statusRow(cli, pane));
|
|
62
67
|
}
|
|
63
68
|
// One pass of the watch. Pure: it reads what it is handed and changes only `memory`.
|
|
64
|
-
export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
69
|
+
export function pass({ team, state, live, machine, now, memory, approval, watch, outcomes = [], budgets = team.budgets, readings: stored = [], foreground, readScreen: read = readScreen, quotaFor: patternsOf = shippedQuota, }) {
|
|
65
70
|
const reports = [];
|
|
66
71
|
const current = new Set();
|
|
67
72
|
// Reported when it starts, and again only after it has cleared.
|
|
@@ -81,6 +86,7 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
81
86
|
name: seat.name,
|
|
82
87
|
cli: seat.cli,
|
|
83
88
|
vendor: seat.vendor,
|
|
89
|
+
account: seat.account ?? seat.vendor,
|
|
84
90
|
parked: seat.parked || seat.stopped,
|
|
85
91
|
stopped: seat.stopped,
|
|
86
92
|
seat,
|
|
@@ -88,10 +94,18 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
88
94
|
...Object.entries(state.seats).filter(([, recorded]) => recorded.temporary).map(([name, recorded]) => {
|
|
89
95
|
// A temporary seat signs like the seat it is like: its CLI, and its account.
|
|
90
96
|
const like = team.seats.find((seat) => seat.name === recorded.temporary?.like);
|
|
91
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
name,
|
|
99
|
+
cli: like?.cli ?? '',
|
|
100
|
+
vendor: like?.vendor ?? '',
|
|
101
|
+
account: like?.account ?? like?.vendor ?? '',
|
|
102
|
+
parked: false,
|
|
103
|
+
stopped: false,
|
|
104
|
+
seat: undefined,
|
|
105
|
+
};
|
|
92
106
|
}),
|
|
93
107
|
];
|
|
94
|
-
for (const { name, cli, vendor, parked, stopped, seat } of seats) {
|
|
108
|
+
for (const { name, cli, vendor, account, parked, stopped, seat } of seats) {
|
|
95
109
|
const agent = live.agents.find((candidate) => candidate.name === name);
|
|
96
110
|
const lead = name === team.coordinator || name === team.operator;
|
|
97
111
|
if (!agent) {
|
|
@@ -104,6 +118,7 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
104
118
|
screen: { kind: 'unknown' },
|
|
105
119
|
cli,
|
|
106
120
|
vendor,
|
|
121
|
+
account,
|
|
107
122
|
quota: [],
|
|
108
123
|
running: false,
|
|
109
124
|
quiet: false,
|
|
@@ -120,7 +135,7 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
120
135
|
}
|
|
121
136
|
known.add(agent.pane);
|
|
122
137
|
const pane = live.screens[agent.pane];
|
|
123
|
-
const screen =
|
|
138
|
+
const screen = read(cli, pane);
|
|
124
139
|
const quiet = agent.status === 'idle' || agent.status === 'done';
|
|
125
140
|
// Working is herdr's word or the screen's: a seat mid-turn is never idle, whatever its
|
|
126
141
|
// status says.
|
|
@@ -146,7 +161,8 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
146
161
|
screen,
|
|
147
162
|
cli,
|
|
148
163
|
vendor,
|
|
149
|
-
|
|
164
|
+
account,
|
|
165
|
+
quota: quotaOf(cli, screen, pane, reportedLiveAgent(foreground?.[agent.pane] ?? null, profileFor(cli)?.processNames ?? []), patternsOf(cli)),
|
|
150
166
|
running: true,
|
|
151
167
|
quiet,
|
|
152
168
|
working,
|
|
@@ -159,19 +175,45 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
159
175
|
history: { lastWorking: history.lastWorking, idleSince: history.idleSince },
|
|
160
176
|
});
|
|
161
177
|
}
|
|
162
|
-
// The figures this pass saw, folded into the readings the
|
|
163
|
-
// whose `sources` name `status_line` takes a screen reading: a check-only account never
|
|
164
|
-
// one here, and neither does an account the budgets in force don't name (#50).
|
|
165
|
-
|
|
178
|
+
// The figures this pass saw, folded into the readings the project keeps (§ 4.3, § 4.4). Only an
|
|
179
|
+
// account whose `sources` name `status_line` takes a screen reading: a check-only account never
|
|
180
|
+
// records one here, and neither does an account the budgets in force don't name (#50). A figure
|
|
181
|
+
// names the account its pattern measures (§ 3b): one that measures this seat's vendor is this
|
|
182
|
+
// seat's account — its own `account:` when the file names one, so two accounts of one vendor
|
|
183
|
+
// keep two buckets — while a figure on another account (a launcher showing the vendor it really
|
|
184
|
+
// runs on) stays that account's, whichever seat's screen showed it.
|
|
185
|
+
//
|
|
186
|
+
// A seat the approval lists as changed — or as not in the approved file — is drift, and its
|
|
187
|
+
// figures are read as they are but none is folded: an unapproved edit to its `account:` must
|
|
188
|
+
// not move its figure into another account's bucket, where `up` and `add` would count it.
|
|
189
|
+
// Nothing is saved for that seat until the owner approves.
|
|
190
|
+
const drift = new Set((Array.isArray(approval) ? approval : []).map(seatNamed).filter((name) => name !== null));
|
|
191
|
+
for (const [name, recorded] of Object.entries(state.seats)) {
|
|
192
|
+
if (recorded.temporary && drift.has(recorded.temporary.like)) {
|
|
193
|
+
drift.add(name);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let readings = stored.slice();
|
|
166
197
|
for (const seat of observations) {
|
|
167
|
-
if (!seat.running)
|
|
198
|
+
if (!seat.running || drift.has(seat.name))
|
|
168
199
|
continue;
|
|
169
200
|
for (const figure of seat.quota) {
|
|
170
|
-
|
|
171
|
-
|
|
201
|
+
const account = figure.account === seat.vendor ? seat.account : figure.account;
|
|
202
|
+
if (budgets.accounts[account]?.sources.includes('status_line')) {
|
|
203
|
+
readings = observe(readings, { ...figure, account }, seat.name, now);
|
|
172
204
|
}
|
|
173
205
|
}
|
|
174
206
|
}
|
|
207
|
+
// A check reading lands in the same slot (§ 5): what an approved check read outlives the watch,
|
|
208
|
+
// so `up` and `add` count it after the watch has exited. Only an account whose sources name
|
|
209
|
+
// `check`, and only when its check actually read this pass.
|
|
210
|
+
for (const outcome of outcomes) {
|
|
211
|
+
if (outcome.state !== 'read' || outcome.reading.kind !== 'subscription')
|
|
212
|
+
continue;
|
|
213
|
+
if (!budgets.accounts[outcome.account]?.sources.includes('check'))
|
|
214
|
+
continue;
|
|
215
|
+
readings = observeCheck(readings, outcome.account, outcome.reading.windows);
|
|
216
|
+
}
|
|
175
217
|
const teamObservation = {
|
|
176
218
|
team,
|
|
177
219
|
state,
|
|
@@ -196,15 +238,14 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
196
238
|
return memory.slots[kind];
|
|
197
239
|
},
|
|
198
240
|
};
|
|
199
|
-
// The four § 4.2 keeps out of `watch.checks` run whatever the
|
|
241
|
+
// The four § 4.2 keeps out of `watch.checks` run whatever the list says: the validation refuses
|
|
200
242
|
// them, and this holds even for a file that reached memory another way.
|
|
201
243
|
//
|
|
202
|
-
//
|
|
203
|
-
// `
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
const off = approved ? new Set(team.watch.checks) : new Set();
|
|
244
|
+
// The off list is the section in force, which the caller passes as `watch`. A file that was
|
|
245
|
+
// approved — `approval` is a list, empty or not — runs that list. An unapproved `watch.checks`
|
|
246
|
+
// edit does not turn anything new off, and a check the approved list turned off stays off.
|
|
247
|
+
// A file never approved, or whose approval was not looked at, turns nothing off.
|
|
248
|
+
const off = Array.isArray(approval) ? new Set(watch.checks) : new Set();
|
|
208
249
|
const enabled = (name) => ALWAYS_ON.includes(name) || !off.has(name);
|
|
209
250
|
for (const seat of observations) {
|
|
210
251
|
for (const check of SEAT_CHECKS)
|
|
@@ -226,7 +267,7 @@ export function pass({ team, state, live, machine, now, memory, approval, watch
|
|
|
226
267
|
const operator = live.agents.find((agent) => agent.name === team.operator);
|
|
227
268
|
const cli = team.seats.find((seat) => seat.name === team.operator)?.cli ?? '';
|
|
228
269
|
const free = operator !== undefined && (operator.status === 'idle' || operator.status === 'done')
|
|
229
|
-
&&
|
|
270
|
+
&& read(cli, live.screens[operator.pane]).kind === 'idle';
|
|
230
271
|
if (operator && free) {
|
|
231
272
|
nudge = { pane: operator.pane, text: NUDGE_TEXT, pending: memory.pending.slice() };
|
|
232
273
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Screen } from './screen.ts';
|
|
2
|
-
import type { ScreenData } from './screen-data.ts';
|
|
3
|
-
export type { Composer, FallbackRule, LinePattern, Placeholder, Rule, ScreenData, Stage } from './screen-data.ts';
|
|
2
|
+
import type { ScreenData, Wrap } from './screen-data.ts';
|
|
3
|
+
export type { Composer, FallbackRule, LinePattern, Placeholder, PlaceholderStyle, Rule, ScreenData, Stage, Wrap } from './screen-data.ts';
|
|
4
|
+
export type { ScreenProfile, ComposerReading } from './screen-profile.ts';
|
|
4
5
|
export type ReadClock = {
|
|
5
6
|
now(): number;
|
|
6
7
|
budgetMs: number;
|
|
@@ -11,10 +12,46 @@ export type ReadClock = {
|
|
|
11
12
|
* typed — is never the row. The caller still has to know the screen is a composer screen.
|
|
12
13
|
*/
|
|
13
14
|
export declare function statusRowOf(data: ScreenData, lines: string[]): string | null;
|
|
15
|
+
export type Fold = {
|
|
16
|
+
count: number;
|
|
17
|
+
rows: string[];
|
|
18
|
+
width: number;
|
|
19
|
+
};
|
|
20
|
+
/** The input box a composer draws for its current text: the first line after the prompt, the
|
|
21
|
+
* continuation rows under it, the column those rows start at (the prompt's own width plus
|
|
22
|
+
* the separator the input row draws), and the profile's wrap rule when a capture shows how
|
|
23
|
+
* the box continues a line onto its next row. */
|
|
24
|
+
export type Box = {
|
|
25
|
+
first: string;
|
|
26
|
+
indent: number;
|
|
27
|
+
rows: string[];
|
|
28
|
+
wrap?: Wrap;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The fold a `two-rules-footer-below` composer's window shows, or null. A marker whose count is
|
|
32
|
+
* zero names no hidden rows to compare against, so it is no fold; it is still a marker, and
|
|
33
|
+
* `foldMarked` catches it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function foldOf(data: ScreenData, lines: string[]): Fold | null;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the window shows a fold marker at all, whatever count it names: a marker claiming zero
|
|
38
|
+
* hidden rows (or a count a fold could not have) is still a box that is not showing the whole
|
|
39
|
+
* text, and it must never be taken for an ordinary unsent box and submitted unverified.
|
|
40
|
+
*/
|
|
41
|
+
export declare function foldMarked(data: ScreenData, lines: string[]): boolean;
|
|
14
42
|
/** `lines` is already the window: the last 20 lines, each trimmed at the end. */
|
|
15
43
|
export declare function classifyLines(data: ScreenData, lines: string[], clock?: ReadClock): Screen;
|
|
16
44
|
/**
|
|
17
|
-
* The composer alone, for a turn that is still running. The
|
|
18
|
-
*
|
|
45
|
+
* The composer alone, for a turn that is still running. The dialog stages and
|
|
46
|
+
* floor must not be overridden by composer readings returning idle or unsent.
|
|
19
47
|
*/
|
|
20
48
|
export declare function composeLines(data: ScreenData, lines: string[], clock?: ReadClock): Screen;
|
|
49
|
+
/**
|
|
50
|
+
* The input box a composer draws for its current text, or null when the composer does not read
|
|
51
|
+
* `unsent`: an idle box holds nothing, and every other shape is not the box. The rows are the
|
|
52
|
+
* ones the mode's own frame delimits — between the prompt row and the box's closing line; the
|
|
53
|
+
* `status-then-one` fallback (the captured completion frame) delimits nothing, so its box is
|
|
54
|
+
* its one input row, the popup rows below it being its filter's to exclude. The caller compares
|
|
55
|
+
* the box to the text it typed; nothing here decides that.
|
|
56
|
+
*/
|
|
57
|
+
export declare function composerBox(data: ScreenData, lines: string[]): Box | null;
|