@idleware/ringi 0.6.1 → 0.7.1

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 (162) hide show
  1. package/README.md +7 -18
  2. package/package.json +1 -1
  3. package/src/main/resources/profiles/a2a-1.json +0 -4
  4. package/src/main/resources/skill/ringi/SKILL.md +2 -18
  5. package/target/dts/adapters/host-posix/index.d.ts +2 -2
  6. package/target/dts/adapters/transport-git/branches.d.ts +2 -3
  7. package/target/dts/adapters/transport-git/index.d.ts +1 -2
  8. package/target/dts/adapters/transport-git/remote/admit.d.ts +2 -2
  9. package/target/dts/adapters/transport-git/remote/heartbeat.d.ts +2 -2
  10. package/target/dts/adapters/transport-git/remote/keys.d.ts +3 -3
  11. package/target/dts/adapters/transport-git/remote/types.d.ts +0 -1
  12. package/target/dts/adapters/transport-git/remote.d.ts +1 -1
  13. package/target/dts/adapters/transport-git/repo.d.ts +0 -1
  14. package/target/dts/adapters/transport-git/signing.d.ts +3 -3
  15. package/target/dts/clients/cli/agent/index.d.ts +0 -2
  16. package/target/dts/clients/cli/shared/index.d.ts +0 -1
  17. package/target/dts/core/agents/heartbeat/index.d.ts +2 -5
  18. package/target/dts/core/agents/heartbeat/pulse.d.ts +1 -3
  19. package/target/dts/core/agents/overview.d.ts +1 -5
  20. package/target/dts/core/agents/ports/host.d.ts +5 -37
  21. package/target/dts/core/agents/sessions/agent.d.ts +0 -17
  22. package/target/dts/core/agents/sessions/index.d.ts +0 -1
  23. package/target/dts/core/hub/history/index.d.ts +1 -11
  24. package/target/dts/core/hub/ports/store.d.ts +1 -5
  25. package/target/dts/core/hub/protocol/model.d.ts +0 -1
  26. package/target/dts/core/hub/protocol/reading.d.ts +0 -1
  27. package/target/dts/core/hub/rooms/index.d.ts +0 -4
  28. package/target/dts/core/hub/rooms/messages.d.ts +0 -1
  29. package/target/dts/core/hub/rooms/rooms.d.ts +1 -1
  30. package/target/dts/core/hub/signals/model.d.ts +0 -2
  31. package/target/dts/core/hub/usecases/hubs.d.ts +0 -1
  32. package/target/dts/core/hub/usecases/room.d.ts +0 -1
  33. package/target/dts/core/hub/usecases/rooms.d.ts +1 -5
  34. package/target/dts/core/hub/usecases/send.d.ts +0 -1
  35. package/target/dts/core/hub/usecases/view.d.ts +0 -22
  36. package/target/dts/core/hub/work/index.d.ts +0 -1
  37. package/target/dts/index.d.ts +0 -1
  38. package/target/esm/adapters/host-posix/index.js +30 -32
  39. package/target/esm/adapters/transport-git/branches.js +13 -20
  40. package/target/esm/adapters/transport-git/index.js +8 -9
  41. package/target/esm/adapters/transport-git/marks.js +4 -10
  42. package/target/esm/adapters/transport-git/remote/admit.js +8 -10
  43. package/target/esm/adapters/transport-git/remote/heartbeat-push.js +1 -1
  44. package/target/esm/adapters/transport-git/remote/heartbeat.js +4 -4
  45. package/target/esm/adapters/transport-git/remote/keys.js +7 -32
  46. package/target/esm/adapters/transport-git/remote/main.js +1 -4
  47. package/target/esm/adapters/transport-git/remote.js +1 -1
  48. package/target/esm/adapters/transport-git/repo.js +5 -3
  49. package/target/esm/adapters/transport-git/signing.js +12 -17
  50. package/target/esm/adapters/transport-git/transport.js +6 -6
  51. package/target/esm/clients/cli/agent/index.js +4 -10
  52. package/target/esm/clients/cli/arg-parse.js +1 -1
  53. package/target/esm/clients/cli/commands.js +6 -7
  54. package/target/esm/clients/cli/help.js +6 -13
  55. package/target/esm/clients/cli/operator/heartbeat-format.js +1 -1
  56. package/target/esm/clients/cli/operator/hub-commands.js +1 -1
  57. package/target/esm/clients/cli/operator/index.js +1 -1
  58. package/target/esm/clients/cli/operator/room.js +46 -7
  59. package/target/esm/clients/cli/operator/where-format.js +1 -3
  60. package/target/esm/clients/cli/shared/index.js +1 -2
  61. package/target/esm/clients/cli/shared/messages.js +1 -1
  62. package/target/esm/clients/cli/shared/profile.js +2 -2
  63. package/target/esm/clients/cli/shared/work.js +1 -1
  64. package/target/esm/composition/index.js +1 -3
  65. package/target/esm/core/agents/heartbeat/index.js +16 -23
  66. package/target/esm/core/agents/heartbeat/pulse.js +18 -33
  67. package/target/esm/core/agents/heartbeat/tools.js +1 -2
  68. package/target/esm/core/agents/overview.js +5 -6
  69. package/target/esm/core/agents/ports/host.js +2 -2
  70. package/target/esm/core/agents/sessions/agent.js +3 -23
  71. package/target/esm/core/agents/sessions/binding.js +5 -7
  72. package/target/esm/core/agents/sessions/index.js +3 -7
  73. package/target/esm/core/agents/sessions/join.js +1 -5
  74. package/target/esm/core/agents/sessions/resume.js +3 -4
  75. package/target/esm/core/agents/sessions/wait.js +10 -31
  76. package/target/esm/core/hub/capabilities.js +4 -2
  77. package/target/esm/core/hub/history/index.js +3 -1
  78. package/target/esm/core/hub/ports/store.js +3 -3
  79. package/target/esm/core/hub/protocol/index.js +3 -3
  80. package/target/esm/core/hub/protocol/model.js +2 -3
  81. package/target/esm/core/hub/protocol/reading.js +1 -3
  82. package/target/esm/core/hub/rooms/compat.js +1 -2
  83. package/target/esm/core/hub/rooms/hubs.js +2 -5
  84. package/target/esm/core/hub/rooms/index.js +3 -3
  85. package/target/esm/core/hub/rooms/messages.js +5 -10
  86. package/target/esm/core/hub/rooms/rooms.js +9 -38
  87. package/target/esm/core/hub/signals/effective.js +1 -21
  88. package/target/esm/core/hub/signals/index.js +2 -2
  89. package/target/esm/core/hub/signals/pins.js +1 -1
  90. package/target/esm/core/hub/usecases/hubs.js +0 -3
  91. package/target/esm/core/hub/usecases/room.js +2 -3
  92. package/target/esm/core/hub/usecases/rooms.js +4 -17
  93. package/target/esm/core/hub/usecases/roster.js +1 -1
  94. package/target/esm/core/hub/usecases/send.js +2 -12
  95. package/target/esm/core/hub/usecases/view.js +2 -6
  96. package/target/esm/core/hub/work/task.js +4 -11
  97. package/target/esm/index.js +1 -2
  98. package/target/esm/lib/json.js +1 -1
  99. package/target/dts/adapters/host-posix/group-state.d.ts +0 -7
  100. package/target/dts/adapters/host-posix/groups.d.ts +0 -1
  101. package/target/dts/adapters/host-posix/launch.d.ts +0 -7
  102. package/target/dts/adapters/host-posix/probes.d.ts +0 -5
  103. package/target/dts/adapters/host-posix/stats.d.ts +0 -9
  104. package/target/dts/adapters/transport-kafka/index.d.ts +0 -2
  105. package/target/dts/adapters/transport-temporal/index.d.ts +0 -2
  106. package/target/dts/clients/cli/operator/room-lifecycle.d.ts +0 -5
  107. package/target/dts/clients/cli/operator/room-list.d.ts +0 -5
  108. package/target/dts/clients/cli/operator/room-members.d.ts +0 -4
  109. package/target/dts/clients/cli/operator/watch.d.ts +0 -2
  110. package/target/dts/clients/cli/shared/budget.d.ts +0 -7
  111. package/target/dts/core/agents/turns/budgets.d.ts +0 -5
  112. package/target/dts/core/agents/turns/dispatch.d.ts +0 -9
  113. package/target/dts/core/agents/turns/exhaustion.d.ts +0 -11
  114. package/target/dts/core/agents/turns/grants.d.ts +0 -4
  115. package/target/dts/core/agents/turns/index.d.ts +0 -4
  116. package/target/dts/core/agents/turns/limits.d.ts +0 -2
  117. package/target/dts/core/agents/turns/messages.d.ts +0 -10
  118. package/target/dts/core/agents/turns/pings.d.ts +0 -5
  119. package/target/dts/core/agents/turns/plan.d.ts +0 -3
  120. package/target/dts/core/agents/turns/poll.d.ts +0 -8
  121. package/target/dts/core/agents/turns/results.d.ts +0 -3
  122. package/target/dts/core/agents/turns/seat.d.ts +0 -2
  123. package/target/dts/core/agents/turns/types.d.ts +0 -59
  124. package/target/dts/core/hub/decisions/budget.d.ts +0 -15
  125. package/target/dts/core/hub/decisions/check.d.ts +0 -6
  126. package/target/dts/core/hub/decisions/index.d.ts +0 -5
  127. package/target/dts/core/hub/decisions/proposal.d.ts +0 -38
  128. package/target/dts/core/hub/decisions/usage.d.ts +0 -12
  129. package/target/dts/core/hub/usecases/budget.d.ts +0 -23
  130. package/target/dts/core/hub/usecases/propose.d.ts +0 -17
  131. package/target/esm/adapters/host-posix/group-state.js +0 -24
  132. package/target/esm/adapters/host-posix/groups.js +0 -43
  133. package/target/esm/adapters/host-posix/launch.js +0 -80
  134. package/target/esm/adapters/host-posix/probes.js +0 -66
  135. package/target/esm/adapters/host-posix/stats.js +0 -27
  136. package/target/esm/adapters/transport-kafka/index.js +0 -4
  137. package/target/esm/adapters/transport-temporal/index.js +0 -4
  138. package/target/esm/clients/cli/operator/room-lifecycle.js +0 -23
  139. package/target/esm/clients/cli/operator/room-list.js +0 -27
  140. package/target/esm/clients/cli/operator/room-members.js +0 -15
  141. package/target/esm/clients/cli/operator/watch.js +0 -15
  142. package/target/esm/clients/cli/shared/budget.js +0 -50
  143. package/target/esm/core/agents/turns/budgets.js +0 -48
  144. package/target/esm/core/agents/turns/dispatch.js +0 -40
  145. package/target/esm/core/agents/turns/exhaustion.js +0 -16
  146. package/target/esm/core/agents/turns/grants.js +0 -36
  147. package/target/esm/core/agents/turns/index.js +0 -33
  148. package/target/esm/core/agents/turns/limits.js +0 -11
  149. package/target/esm/core/agents/turns/messages.js +0 -32
  150. package/target/esm/core/agents/turns/pings.js +0 -30
  151. package/target/esm/core/agents/turns/plan.js +0 -29
  152. package/target/esm/core/agents/turns/poll.js +0 -48
  153. package/target/esm/core/agents/turns/results.js +0 -40
  154. package/target/esm/core/agents/turns/seat.js +0 -44
  155. package/target/esm/core/agents/turns/types.js +0 -3
  156. package/target/esm/core/hub/decisions/budget.js +0 -40
  157. package/target/esm/core/hub/decisions/check.js +0 -43
  158. package/target/esm/core/hub/decisions/index.js +0 -10
  159. package/target/esm/core/hub/decisions/proposal.js +0 -73
  160. package/target/esm/core/hub/decisions/usage.js +0 -39
  161. package/target/esm/core/hub/usecases/budget.js +0 -78
  162. package/target/esm/core/hub/usecases/propose.js +0 -39
@@ -1,15 +0,0 @@
1
- // watch: the headless watcher the operator starts for an agent's seat (`ringi watch <room>/<agent> --exec …`).
2
- import { watch } from '../../../core/agents/sessions/agent.js';
3
- import { fail, words } from '../../../lib/index.js';
4
- import { flag, opt, out } from '../args.js';
5
- const numberOption = (o, k) => opt(o, k) === undefined ? undefined : Number(opt(o, k));
6
- export const watchCommand = async (o) => {
7
- process.exitCode = await watch({
8
- seat: o._[0] ?? fail('usage: ringi watch <room>/<agent> --exec <cmd> [--scope <text>] [--once] [--require-grant] …'),
9
- exec: opt(o, 'exec') ?? fail('--exec <cmd> is required: the command that starts a headless turn'),
10
- hub: opt(o, 'hub'), scope: opt(o, 'scope'), once: flag(o, 'once'), requireGrant: flag(o, 'require-grant'), handoff: flag(o, 'handoff'),
11
- interval: numberOption(o, 'interval'), from: opt(o, 'from') ? words(opt(o, 'from')) : undefined, maxHop: numberOption(o, 'max-hop'),
12
- maxRuns: numberOption(o, 'max-runs'), window: numberOption(o, 'window')
13
- }, out);
14
- };
15
- //# sourceMappingURL=watch.js.map
@@ -1,50 +0,0 @@
1
- // budget: `propose`, `stamp`, `decide`, `proposals`, `grants`.
2
- import * as Budget from '../../../core/hub/usecases/budget.js';
3
- import { fail, short, words } from '../../../lib/index.js';
4
- import { attachments, dump, flag, list, opt, out, textOf } from '../args.js';
5
- // --- decisions and budgets ---
6
- export const propose = (a, o) => {
7
- const decider = opt(o, 'decider') ?? fail('--decider <id> is required: who decides (operator, or someone with authority in the hub contract)');
8
- const r = Budget.propose(a, {
9
- text: textOf(o), decider, reviewers: words(opt(o, 'reviewers')), budget: opt(o, 'budget'), due: opt(o, 'due'),
10
- supersedes: opt(o, 'supersedes'), files: list(o, 'attach').flatMap(attachments)
11
- });
12
- out(`proposed ${short(r.id)} to ${r.to.join(', ') || 'the room'}; decider ${decider}${r.budget ? `, budget ${Budget.budgetText(r.budget)}` : ''}`);
13
- };
14
- export const stamp_ = (a, o) => {
15
- const s = o._[1] ?? '';
16
- if (!Budget.STAMPS.includes(s))
17
- fail(`usage: stamp <proposal> ${Budget.STAMPS.join('|')} -m <reason>`);
18
- const r = Budget.stamp(a, o._[0] ?? fail('name the proposal'), s, textOf(o));
19
- out(`stamped ${short(r.proposal.id)} ${s}: ${short(r.id)}`);
20
- };
21
- export const decide = (a, o) => {
22
- const d = o._[1] ?? '';
23
- if (!Budget.DECISIONS.includes(d))
24
- fail(`usage: decide <proposal> ${Budget.DECISIONS.join('|')} -m <reason>`);
25
- const r = Budget.decide(a, o._[0] ?? fail('name the proposal'), d, textOf(o));
26
- out(`${d} ${short(r.proposal.id)}: ${short(r.id)}${r.budget ? `; grant ${short(r.id)} with ${Budget.budgetText(r.budget)}. Work spends it with --grant ${short(r.id)}` : ''}`);
27
- };
28
- export const proposals = (a, o) => {
29
- const { proposals: ps, views } = Budget.proposals(a, { all: flag(o, 'all') });
30
- if (flag(o, 'json'))
31
- return dump(views);
32
- if (!ps.length)
33
- return out(flag(o, 'all') ? 'no proposals' : 'no open proposals (proposals --all for everything)');
34
- for (const x of ps) {
35
- const st = x.reviewers.map(r => `${r}:${x.stamps[r]?.stamp ?? '-'}`).join(' ');
36
- out(`${short(x.proposal.id)} ${x.state.padEnd(10)} ${x.proposal.from} → ${x.decider}${x.budget ? ` [${Budget.budgetText(x.budget)}]` : ''}${st ? ` ${st}` : ''} ${x.proposal.subject}`);
37
- }
38
- };
39
- export const grants = (a, o) => {
40
- const gs = Budget.grants(a);
41
- if (flag(o, 'json'))
42
- return dump(gs);
43
- if (!gs.length)
44
- return out('no grants');
45
- for (const g of gs) {
46
- const u = g.usage;
47
- out(`${short(g.grant)} ${g.revoked ? 'revoked ' : u.left ? 'active ' : 'spent '} runs ${u.runs}/${g.budget.runs}, open ${u.open}${u.uncertain ? `, uncertain ${u.uncertain}` : ''}, ${u.seconds}s used, ≤${g.budget['run-seconds']}s a run, concurrency ${g.budget.concurrency} ${g.subject}`);
48
- }
49
- };
50
- //# sourceMappingURL=budget.js.map
@@ -1,48 +0,0 @@
1
- // budgets: which groups of messages may start a turn now: within a grant's budget, or within the watcher's own run
2
- // limit when no grant covers them.
3
- import { note, short } from '../../../lib/index.js';
4
- import { reportExhausted } from './exhaustion.js';
5
- import { reserveGrant } from './grants.js';
6
- const ownRun = (c, grp, p, now) => {
7
- if (c.w.requireGrant) {
8
- for (const m of grp.msgs)
9
- note(`watch: ${short(m.id)} is outside any grant; --require-grant: not starting a turn`);
10
- return;
11
- }
12
- if (p.ownRuns.length >= c.maxRuns) {
13
- p.hold = true;
14
- note(`watch: own run budget spent (${c.maxRuns} per ${c.span}s); ${grp.msgs.length} message(s) wait`);
15
- return;
16
- }
17
- p.ownRuns = [...p.ownRuns, now];
18
- p.runs.push({ ...grp, timeout: null });
19
- };
20
- const grantedRun = ({ c, R, all, grp, r, p }) => {
21
- if (r.wait) {
22
- p.hold = true;
23
- note(`watch: ${r.why}; waiting`);
24
- return;
25
- }
26
- if (r.why) {
27
- note(`watch: ${r.why}`);
28
- reportExhausted({ c, R, all, grp, r, p });
29
- return;
30
- }
31
- p.runs.push({ ...grp, run: r.run, timeout: r.gr.budget['run-seconds'] });
32
- };
33
- export const reserveGroups = (c, R, all, groups, st) => {
34
- const now = Date.now() / 1000;
35
- const p = { runs: [], hold: false, lost: false, ownRuns: (st.runs ?? []).filter(t => t > now - c.span), exhausted: { ...st.exhausted } };
36
- for (const grp of groups.values()) {
37
- if (!grp.grant) {
38
- ownRun(c, grp, p, now);
39
- continue;
40
- }
41
- const r = reserveGrant(c, R, all, grp);
42
- if (r.lost)
43
- return { ...p, runs: [], hold: true, lost: true };
44
- grantedRun({ c, R, all, grp, r, p });
45
- }
46
- return p;
47
- };
48
- //# sourceMappingURL=budgets.js.map
@@ -1,40 +0,0 @@
1
- // dispatch: a planned turn is started through the process host, still holding the seat and a valid grant.
2
- import * as D from '../../hub/decisions/index.js';
3
- import { hubPorts, locked } from '../../hub/ports/index.js';
4
- import { fail, note, short } from '../../../lib/index.js';
5
- import { link } from '../../hub/protocol/index.js';
6
- import { loadRoomUnlocked, loadState, readerLock, saveState } from '../../hub/history/index.js';
7
- import { readSession } from '../sessions/index.js';
8
- import { agentPorts } from '../ports/index.js';
9
- const grantProblem = (c, R, run) => {
10
- if (!run.grant)
11
- return undefined;
12
- const { logs } = readerLock(R, c.me, () => loadRoomUnlocked(R, c.me));
13
- const gr = D.grantOf(link(logs), R.authorityAt, run.grant);
14
- return !gr ? 'its grant is no longer valid' : gr.revoked ? 'its grant was revoked' : undefined;
15
- };
16
- const spawnTurn = (c, run) => locked('sessions', () => {
17
- const cur = readSession(c.handle);
18
- if (!cur || cur.pid !== process.pid)
19
- return { why: 'the seat was taken over' };
20
- if (cur.state !== 'active')
21
- return { why: 'the watcher was stopped' };
22
- note(`watch: ${run.msgs.length} new${run.grant ? ` under grant ${short(run.grant)}` : ''}: ${c.cmd}`);
23
- return {
24
- done: agentPorts().host.awaitQuiescence(agentPorts().host.start({ shell: c.cmd, timeout: run.timeout, env: {
25
- ...process.env, RINGI_HOME: hubPorts().state.root, RINGI_SESSION: c.raw,
26
- RINGI_MSGS: run.msgs.map(m => m.id).join('\n'), RINGI_GRANT: run.grant ?? ''
27
- } })).then(result => result ?? fail('the process host did not settle the turn'))
28
- };
29
- });
30
- export const dispatch = (c, R, run) => locked(`grant-${R.hub.name}`, () => {
31
- const why = grantProblem(c, R, run);
32
- if (why)
33
- return { why };
34
- readerLock(R, c.me, () => {
35
- const st = loadState(R, c.me);
36
- saveState(R, c.me, { ...st, inflight: { seen: st.seen ?? {} } });
37
- });
38
- return spawnTurn(c, run);
39
- });
40
- //# sourceMappingURL=dispatch.js.map
@@ -1,16 +0,0 @@
1
- // exhaustion: a piece of work whose grant is spent or revoked waits for a new grant, said once in its thread.
2
- import { isOpen, taskOf } from '../../hub/work/index.js';
3
- import { OPERATOR, append } from '../../hub/rooms/index.js';
4
- import { compose } from '../../hub/protocol/index.js';
5
- export const reportExhausted = ({ c, R, all, grp, r, p }) => {
6
- const key = `${grp.task?.id}/${grp.grant}`;
7
- if (!r.exhausted || !grp.task || p.exhausted[key])
8
- return;
9
- p.exhausted[key] = true;
10
- const t = taskOf(all, grp.task, R.profile), last = grp.msgs.at(-1);
11
- if (isOpen(t, R.profile))
12
- append(R, c.me, compose(`${r.why}; waiting for a new grant`, [
13
- ['To', [...new Set([t.requester, OPERATOR])].join(', ')], ['Re', last.id], ['State', R.profile.waiting], ['Hop', String(last.hopEff + 1)]
14
- ]));
15
- };
16
- //# sourceMappingURL=exhaustion.js.map
@@ -1,36 +0,0 @@
1
- // grants: a run reserved under a grant: approved, not revoked, not spent, within its concurrency.
2
- import * as D from '../../hub/decisions/index.js';
3
- import { OPERATOR, append } from '../../hub/rooms/index.js';
4
- import { short } from '../../../lib/index.js';
5
- import { compose } from '../../hub/protocol/index.js';
6
- import { readSession } from '../sessions/index.js';
7
- const grantStatus = (gr) => {
8
- if (gr.revoked)
9
- return { why: `grant ${short(gr.id)} was revoked`, exhausted: true, gr };
10
- return { gr };
11
- };
12
- const remaining = (all, gr) => {
13
- const u = D.usageOf(all, gr);
14
- if (u.left <= 0)
15
- return { why: `grant ${short(gr.id)} is spent: ${u.runs}/${gr.budget.runs} runs`, exhausted: true, gr };
16
- if (u.open >= gr.budget.concurrency)
17
- return { why: `grant ${short(gr.id)} has ${u.open} run(s) open, concurrency ${gr.budget.concurrency}`, wait: true };
18
- return { gr };
19
- };
20
- export const reserveGrant = (c, R, all, grp) => {
21
- if (readSession(c.handle)?.pid !== process.pid)
22
- return { why: 'this watcher no longer holds the seat', lost: true };
23
- const gr = D.grantOf(all, R.authorityAt, grp.grant);
24
- if (!gr)
25
- return { why: `grant ${short(grp.grant)} is not an approved decision with a budget` };
26
- const status = grantStatus(gr);
27
- if (status.why)
28
- return status;
29
- const balance = remaining(all, gr);
30
- if (balance.why)
31
- return balance;
32
- const run = `${c.raw}.${process.pid}.${Date.now().toString(36)}.${c.runSeq++}`;
33
- append(R, c.me, compose(`usage: reserve a run of ${c.me}`, [['To', OPERATOR], ['Kind', 'usage'], ['Grant', gr.id], ['Usage', 'reserve'], ['Run', run]]));
34
- return { gr, run };
35
- };
36
- //# sourceMappingURL=grants.js.map
@@ -1,33 +0,0 @@
1
- // turns: the headless watcher: it holds one agent's seat, wakes a command for what arrives, and charges only its own
2
- // reservations and receipts.
3
- import { note, sleep } from '../../../lib/index.js';
4
- import { planTurns } from './plan.js';
5
- import { pollWatcher } from './poll.js';
6
- import { runTurns } from './results.js';
7
- import { createWatcher } from './seat.js';
8
- export const watch = async (w, log = note) => {
9
- const c = createWatcher(w, log);
10
- for (;;) {
11
- const done = await watchTick(c);
12
- if (done !== undefined)
13
- return done;
14
- await sleep(c.interval);
15
- }
16
- };
17
- const watchTick = async (c) => {
18
- const poll = pollWatcher(c);
19
- if (poll.done)
20
- return 0;
21
- if (poll.R) {
22
- const plan = planTurns(c, poll.R);
23
- if (plan.lost) {
24
- c.log(`watch: the seat of ${c.me} was taken over; stopping`);
25
- return 0;
26
- }
27
- const exitCode = await runTurns(c, poll.R, plan.runs);
28
- if (c.w.once)
29
- return exitCode || (plan.hold ? 4 : 0);
30
- }
31
- return undefined;
32
- };
33
- //# sourceMappingURL=index.js.map
@@ -1,11 +0,0 @@
1
- // limits: the watcher's options checked before it starts.
2
- import { fail } from '../../../lib/index.js';
3
- export const validateWatch = (w) => {
4
- for (const [name, value] of [['interval', w.interval], ['window', w.window]])
5
- if (value !== undefined && (!Number.isFinite(value) || value <= 0))
6
- fail(`watch ${name} must be positive seconds`);
7
- for (const [name, value] of [['max-runs', w.maxRuns], ['max-hop', w.maxHop]])
8
- if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
9
- fail(`watch ${name} must be a nonnegative integer`);
10
- };
11
- //# sourceMappingURL=limits.js.map
@@ -1,32 +0,0 @@
1
- // messages: what wakes the watcher: new messages for its agent and retries of failed turns, grouped by grant and work.
2
- import { isOpen, taskFor, taskOf } from '../../hub/work/index.js';
3
- import { HIDDEN } from '../../hub/rooms/index.js';
4
- import { note, short } from '../../../lib/index.js';
5
- import { unread, wakes } from '../../hub/protocol/index.js';
6
- export const collectMessages = (c, R, logs, st, all) => {
7
- const { msgs: fresh, cursors } = unread(logs, c.me, st.watched ?? st.seen ?? {}, Infinity, HIDDEN);
8
- const retry = (st.pending ?? []).flatMap(id => {
9
- const m = all.get(id);
10
- if (!m)
11
- return [];
12
- const task = taskFor(all, m, R.profile);
13
- return !task || isOpen(taskOf(all, task, R.profile), R.profile) ? [m] : [];
14
- });
15
- const msgs = [...new Map([...retry, ...fresh].map(m => [m.id, m])).values()];
16
- const go = msgs.filter(m => wakes(m, c.me, HIDDEN) && m.hopEff < c.maxHop && (!c.allow || c.allow.has(m.from)));
17
- for (const m of msgs)
18
- if (!go.includes(m))
19
- note(`watch: not waking for ${short(m.id)} from ${m.from} (to ${m.to.join(',')}, hop ${m.hopEff})`);
20
- return { retry, cursors, go };
21
- };
22
- export const groupMessages = (R, all, go) => {
23
- const groups = new Map();
24
- for (const m of go) {
25
- const task = taskFor(all, m, R.profile), g = task ? taskOf(all, task, R.profile).grant : null;
26
- if (!groups.has(g))
27
- groups.set(g, { grant: g, msgs: [], task });
28
- groups.get(g).msgs.push(m);
29
- }
30
- return groups;
31
- };
32
- //# sourceMappingURL=messages.js.map
@@ -1,30 +0,0 @@
1
- // pings: the watcher answers pings for its agent without waking a model.
2
- import { append, pongText } from '../../hub/rooms/index.js';
3
- import { note, short } from '../../../lib/index.js';
4
- const allowed = (c, m, answered) => {
5
- const ping = !m.bad && m.kind === 'ping' && m.to.includes(c.me);
6
- const wake = m.hopEff < c.maxHop && (!c.allow || c.allow.has(m.from));
7
- return ping && !answered.has(m.id) && wake;
8
- };
9
- const answerAuthor = (c, R, msgs, last, answered) => {
10
- const at = msgs.findIndex(m => m.id === last);
11
- for (const m of msgs.slice(at + 1)) {
12
- if (!allowed(c, m, answered))
13
- continue;
14
- append(R, c.me, pongText(c.me, m, 'has a watcher'));
15
- note(`watch: answered heartbeat ${short(m.id)} from ${m.from}`);
16
- }
17
- };
18
- export const answerWatchPings = (c, R, logs, st) => {
19
- const heartbeatSeen = { ...st.heartbeatSeen };
20
- const answered = new Set((logs.get(c.me) ?? []).filter(m => m.kind === 'pong').map(m => m.reId));
21
- for (const [id, msgs] of logs) {
22
- if (id === c.me)
23
- continue;
24
- answerAuthor(c, R, msgs, heartbeatSeen[id], answered);
25
- if (msgs.length)
26
- heartbeatSeen[id] = msgs.at(-1).id;
27
- }
28
- return heartbeatSeen;
29
- };
30
- //# sourceMappingURL=pings.js.map
@@ -1,29 +0,0 @@
1
- // plan: one loop's turns: what arrived, grouped and reserved; planning and dispatch take locks in the order grant,
2
- // reader, sessions.
3
- import { locked } from '../../hub/ports/index.js';
4
- import { link } from '../../hub/protocol/index.js';
5
- import { loadRoomUnlocked, loadState, readerLock, saveState } from '../../hub/history/index.js';
6
- import { reserveGroups } from './budgets.js';
7
- import { collectMessages, groupMessages } from './messages.js';
8
- import { answerWatchPings } from './pings.js';
9
- const planUnlocked = (c, R) => {
10
- const { logs, st } = loadRoomUnlocked(R, c.me);
11
- if (st.inflight) {
12
- st.seen = st.inflight.seen;
13
- st.inflight = undefined;
14
- saveState(R, c.me, st);
15
- }
16
- const all = link(logs), heartbeatSeen = answerWatchPings(c, R, logs, st);
17
- const { retry, cursors, go } = collectMessages(c, R, logs, st, all);
18
- const p = reserveGroups(c, R, all, groupMessages(R, all, go), st);
19
- if (p.lost)
20
- return { runs: [], hold: true, lost: true };
21
- const pending = [...new Set([...retry.map(m => m.id), ...p.runs.flatMap(r => r.msgs.map(m => m.id))])];
22
- saveState(R, c.me, {
23
- ...loadState(R, c.me), heartbeatSeen, exhausted: p.exhausted, pending, runs: p.ownRuns,
24
- ...(p.hold ? {} : { watched: cursors })
25
- });
26
- return { runs: p.runs, hold: p.hold, lost: false };
27
- };
28
- export const planTurns = (c, R) => locked(`grant-${R.hub.name}`, () => readerLock(R, c.me, () => planUnlocked(c, R)));
29
- //# sourceMappingURL=plan.js.map
@@ -1,48 +0,0 @@
1
- // poll: each loop renews the watcher's seat and finds its room still there, the same, with its agent a member.
2
- import { locked } from '../../hub/ports/index.js';
3
- import { openHub, openRoom, start } from '../../hub/rooms/index.js';
4
- import { stamp } from '../../../lib/index.js';
5
- import { dropSession, readSession, saveSession } from '../sessions/index.js';
6
- const refreshSeat = (c) => locked('sessions', () => {
7
- const cur = readSession(c.handle);
8
- if (cur?.state === 'active')
9
- saveSession({ ...cur, lastSeen: stamp(), pid: process.pid });
10
- return cur;
11
- });
12
- const currentRoom = (c) => {
13
- const H = openHub(c.H0.name, { refresh: true }), R = openRoom(H, c.R0.name, { quiet: true });
14
- if (!R || R.genesis !== c.R0.genesis) {
15
- locked('sessions', () => { if (readSession(c.handle)?.genesis === c.R0.genesis)
16
- dropSession(c.handle); });
17
- c.log(`watch: ${c.R0.name} is gone, recreated or archived; ${c.me} left it`);
18
- return null;
19
- }
20
- if (!R.members[c.me]) {
21
- c.log(`watch: ${c.me} was removed from ${c.R0.name}; stopping`);
22
- return null;
23
- }
24
- return R;
25
- };
26
- const quorum = (c, R) => {
27
- const st = start(R);
28
- if (st.started)
29
- return { done: false, R };
30
- if (c.w.once)
31
- c.log(`watch: ${c.R0.name} waits for a quorum of ${st.quorum} agents and has ${st.agents}; nothing to do yet`);
32
- return { done: !!c.w.once };
33
- };
34
- export const pollWatcher = (c) => {
35
- const s = refreshSeat(c);
36
- if (!s) {
37
- c.log(`watch: the seat of ${c.me} was released or taken over; stopping`);
38
- return { done: true };
39
- }
40
- if (s.state === 'stopped') {
41
- if (c.w.once)
42
- c.log('watch: stopped by the operator');
43
- return { done: !!c.w.once };
44
- }
45
- const R = currentRoom(c);
46
- return R ? quorum(c, R) : { done: true };
47
- };
48
- //# sourceMappingURL=poll.js.map
@@ -1,40 +0,0 @@
1
- // results: a turn's end: its usage receipt settled, and its messages delivered or kept for a retry.
2
- import { OPERATOR, append } from '../../hub/rooms/index.js';
3
- import { note } from '../../../lib/index.js';
4
- import { compose } from '../../hub/protocol/index.js';
5
- import { loadState, readerLock, saveState } from '../../hub/history/index.js';
6
- import { dispatch } from './dispatch.js';
7
- const settle = (c, R, run, result) => append(R, c.me, compose(`usage: settle a run of ${c.me}${result.why ? ` (${result.why})` : ''}`, [['To', OPERATOR], ['Kind', 'usage'], ['Grant', run.grant], ['Usage', 'settle'], ['Run', run.run], ['Seconds', String(result.seconds)], ['Exit', String(result.exit)]]));
8
- const delivered = (c, R, run, r) => readerLock(R, c.me, () => {
9
- const st = loadState(R, c.me), ids = new Set(run.msgs.map(m => m.id));
10
- const success = r.code === 0 || r.timedOut;
11
- saveState(R, c.me, {
12
- ...st, inflight: undefined, ...(success
13
- ? { pending: (st.pending ?? []).filter(id => !ids.has(id)) }
14
- : { seen: st.inflight?.seen ?? st.seen })
15
- });
16
- });
17
- const ended = (c, R, run, r) => {
18
- note(`watch: exit ${r.code ?? r.signal}${r.timedOut ? ' (stopped at the grant\'s run-seconds)' : ''} after ${r.seconds}s`);
19
- if (run.run)
20
- settle(c, R, run, { seconds: r.seconds, exit: r.code ?? 128 });
21
- delivered(c, R, run, r);
22
- };
23
- export const runTurns = async (c, R, runs) => {
24
- let exitCode = 0;
25
- for (const run of runs) {
26
- const next = dispatch(c, R, run);
27
- if (next.why) {
28
- note(`watch: not starting the next turn: ${next.why}`);
29
- if (run.run)
30
- settle(c, R, run, { seconds: 0, exit: 125, why: `not started: ${next.why}` });
31
- continue;
32
- }
33
- const r = await next.done;
34
- ended(c, R, run, r);
35
- if (r.code !== 0 || r.timedOut)
36
- exitCode ||= r.code || 128;
37
- }
38
- return exitCode;
39
- };
40
- //# sourceMappingURL=results.js.map
@@ -1,44 +0,0 @@
1
- // seat: the watcher takes its agent's seat, from a dead watcher or by an explicit handoff from the agent's own chat.
2
- import { locked } from '../../hub/ports/index.js';
3
- import { config, openHub, openRoom } from '../../hub/rooms/index.js';
4
- import { fail, stamp } from '../../../lib/index.js';
5
- import { dropSession, liveness, parseSelector, pidAlive, saveSession, seatHolder, whoAmI } from '../sessions/index.js';
6
- import { validateWatch } from './limits.js';
7
- const handoff = (c, holder) => {
8
- const own = c.w.handoff && holder?.product !== 'watch' && holder?.handle === whoAmI().handle;
9
- if (c.w.handoff && !own)
10
- fail('watch --handoff needs the invoking chat to own this seat');
11
- if (own && holder.state === 'stopped')
12
- fail('this chat is stopped; continue it before a handoff');
13
- const live = holder && (holder.product !== 'watch' || (holder.pid && pidAlive(holder.pid)));
14
- if (holder && !own && live)
15
- fail(`${c.R0.name}/${c.me} is held by session ${holder.handle} (${liveness(holder)}); one seat per agent. Its chat may use watch --handoff`);
16
- return !!own;
17
- };
18
- const claim = (c) => locked('sessions', () => {
19
- const { w, H0, R0, me, handle } = c;
20
- const holder = seatHolder(H0.name, R0.genesis, me, handle), handed = handoff(c, holder);
21
- if (holder)
22
- dropSession(holder.handle);
23
- saveSession({
24
- handle, product: 'watch', hub: H0.name, room: R0.name, genesis: R0.genesis, agent: me, scope: w.scope ?? holder?.scope ?? null,
25
- state: holder?.state === 'stopped' ? 'stopped' : 'active', since: stamp(), lastSeen: stamp(), pid: process.pid,
26
- takenFrom: handed ? holder.handle : holder?.takenFrom
27
- });
28
- });
29
- export const createWatcher = (w, log) => {
30
- validateWatch(w);
31
- const sel = parseSelector(w.seat), H0 = openHub(sel.hub ?? w.hub ?? config().hub), R0 = openRoom(H0, sel.room), me = sel.agent;
32
- if (R0.archived)
33
- fail(`${R0.name} is archived`);
34
- if (!R0.members[me])
35
- fail(`${me} is not a member of ${R0.name}; the operator adds it with a join, or watch another agent`);
36
- const raw = `watch-${H0.name}-${R0.genesis.slice(0, 8)}-${me}-${process.pid}`, handle = `env:${raw}`;
37
- const c = {
38
- w, H0, R0, me, raw, handle, cmd: w.exec, log, interval: (w.interval ?? 5) * 1000,
39
- allow: w.from ? new Set(w.from) : null, maxHop: w.maxHop ?? 8, maxRuns: w.maxRuns ?? 10, span: w.window ?? 86400, runSeq: 0
40
- };
41
- claim(c);
42
- return c;
43
- };
44
- //# sourceMappingURL=seat.js.map
@@ -1,3 +0,0 @@
1
- // types: what the watcher's modules share: its options and context, groups of messages, runs and reservations.
2
- export {};
3
- //# sourceMappingURL=types.js.map
@@ -1,40 +0,0 @@
1
- // budget: what a decision grants (a budget), until when a proposal waits (its due time), and who may decide it.
2
- import { fail } from '../../../lib/index.js';
3
- export const ROOT = 'operator';
4
- const BUDGET_KEYS = ['runs', 'run-seconds', 'concurrency'];
5
- export const ids = (v) => v === null ? [] : [...new Set(v.split(',').map(x => x.trim()).filter(Boolean))];
6
- const PART = /^([a-z][a-z-]*)=(\d{1,9})$/;
7
- // `runs=5, run-seconds=600, concurrency=1`; undefined if malformed. `runs` is required: a budget without a run count
8
- // bounds nothing; a zero run time or concurrency would disable the bound it names.
9
- export const parseBudget = (s) => {
10
- if (s === null || s === undefined)
11
- return null;
12
- const b = {};
13
- for (const part of String(s).split(',').map(x => x.trim()).filter(Boolean)) {
14
- const m = PART.exec(part);
15
- if (!m || !BUDGET_KEYS.includes(m[1]) || m[1] in b)
16
- return undefined;
17
- b[m[1]] = Number(m[2]);
18
- }
19
- return bounded(b) ? { 'run-seconds': 1800, concurrency: 1, ...b } : undefined;
20
- };
21
- const bounded = (b) => 'runs' in b && b['run-seconds'] !== 0 && b.concurrency !== 0;
22
- export const budgetText = (b) => BUDGET_KEYS.map(k => `${k}=${b[k]}`).join(', ');
23
- const UNIT = { m: 60e3, h: 3600e3, d: 86400e3 };
24
- // Relative (`90m`, `2h`, `3d`) or an ISO 8601 time → ISO UTC to the second.
25
- export const parseDue = (s) => {
26
- const rel = /^(\d{1,6})([mhd])$/.exec(s);
27
- const t = rel ? Date.now() + Number(rel[1]) * UNIT[rel[2]] : Date.parse(s);
28
- if (!Number.isFinite(t))
29
- fail(`--due takes 90m, 2h, 3d or an ISO time, not ${s}`);
30
- return new Date(t).toISOString().slice(0, 19) + 'Z';
31
- };
32
- const within = (cap, budget) => BUDGET_KEYS.every(k => Number.isFinite(cap[k]) && budget[k] <= cap[k]);
33
- // The root always may; anyone else only within the authority table, and never on their own proposal.
34
- export const mayDecide = (authority, decider, proposer, budget) => {
35
- if (decider === ROOT)
36
- return true;
37
- const cap = decider === proposer ? undefined : authority?.decide?.[decider];
38
- return !!cap && (!budget || within(cap, budget));
39
- };
40
- //# sourceMappingURL=budget.js.map
@@ -1,43 +0,0 @@
1
- // check: the extension's trailers are part of a record's validity; a malformed decision record routes nowhere.
2
- import { ID_RE, one } from '../protocol/index.js';
3
- import { ids, parseBudget } from './budget.js';
4
- export const STAMPS = ['approve', 'object', 'abstain'];
5
- export const DECISIONS = ['approved', 'rejected', 'revoked'];
6
- const SINGLE = ['decider', 'reviewers', 'due', 'budget', 'supersedes', 'stamp', 'decision', 'control', 'grant', 'usage', 'run', 'seconds', 'exit'];
7
- const RUN = /^[A-Za-z0-9._:-]{1,128}$/;
8
- // A trailer absent, or a message id.
9
- const refOk = (m, k, isRef) => one(m, k) === null || isRef(one(m, k));
10
- const answers = (m, values, k) => !!m.re && values.includes(one(m, k) ?? '');
11
- const receipt = (m, isRef) => isRef(one(m, 'grant') ?? '') && ['reserve', 'settle'].includes(one(m, 'usage') ?? '') && RUN.test(one(m, 'run') ?? '');
12
- const dueOk = (v) => v === null || Number.isFinite(Date.parse(v));
13
- // What each kind of record must carry, as (wrong, why) in the order the reasons are reported.
14
- const BY_KIND = {
15
- proposal: (m, isRef) => [
16
- [() => !ID_RE.test(one(m, 'decider') ?? ''), 'proposal without a valid Decider'],
17
- [() => !ids(one(m, 'reviewers')).every(x => ID_RE.test(x)), 'malformed Reviewers'],
18
- [() => !dueOk(one(m, 'due')), 'malformed Due'],
19
- [() => !refOk(m, 'supersedes', isRef), 'malformed Supersedes'],
20
- ],
21
- stamp: m => [[() => !answers(m, STAMPS, 'stamp'), 'stamp without Re or a valid Stamp']],
22
- decision: m => [[() => !answers(m, DECISIONS, 'decision'), 'decision without Re or a valid Decision']],
23
- usage: (m, isRef) => [
24
- [() => !receipt(m, isRef), 'malformed usage receipt'],
25
- [() => one(m, 'usage') === 'settle' && !/^\d{1,9}$/.test(one(m, 'seconds') ?? ''), 'usage settle without Seconds'],
26
- ],
27
- };
28
- // Why a record is malformed for this extension, or null.
29
- export const check = (m, isRef) => {
30
- if (m.bad)
31
- return null;
32
- const twice = SINGLE.find(k => (m.trailers[k]?.length ?? 0) > 1);
33
- if (twice)
34
- return `duplicate ${twice}`;
35
- const rules = [
36
- [() => !refOk(m, 'grant', isRef), 'malformed Grant'],
37
- [() => !refOk(m, 'control', isRef), 'malformed Control'],
38
- [() => one(m, 'budget') !== null && !parseBudget(one(m, 'budget')), 'malformed Budget'],
39
- ...BY_KIND[m.kind ?? '']?.(m, isRef) ?? [],
40
- ];
41
- return rules.find(([wrong]) => wrong())?.[1] ?? null;
42
- };
43
- //# sourceMappingURL=check.js.map
@@ -1,10 +0,0 @@
1
- // decide: the extension for decisions and budgets (capability `decisions/1`, docs/spec/decisions-1.md). A proposal circulates
2
- // to named reviewers, who stamp it; only the named decider, holding authority from the hub's contract, decides. An
3
- // approval that carries a budget is a grant; work spends against it, and the runner that starts model runs writes
4
- // the usage receipts. Everything here is a pure fold over messages; nothing trusts a model's own report.
5
- export const CAPABILITY = 'decisions/1';
6
- export * from './budget.js';
7
- export * from './check.js';
8
- export * from './proposal.js';
9
- export * from './usage.js';
10
- //# sourceMappingURL=index.js.map