@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.
- package/README.md +7 -18
- package/package.json +1 -1
- package/src/main/resources/profiles/a2a-1.json +0 -4
- package/src/main/resources/skill/ringi/SKILL.md +2 -18
- package/target/dts/adapters/host-posix/index.d.ts +2 -2
- package/target/dts/adapters/transport-git/branches.d.ts +2 -3
- package/target/dts/adapters/transport-git/index.d.ts +1 -2
- package/target/dts/adapters/transport-git/remote/admit.d.ts +2 -2
- package/target/dts/adapters/transport-git/remote/heartbeat.d.ts +2 -2
- package/target/dts/adapters/transport-git/remote/keys.d.ts +3 -3
- package/target/dts/adapters/transport-git/remote/types.d.ts +0 -1
- package/target/dts/adapters/transport-git/remote.d.ts +1 -1
- package/target/dts/adapters/transport-git/repo.d.ts +0 -1
- package/target/dts/adapters/transport-git/signing.d.ts +3 -3
- package/target/dts/clients/cli/agent/index.d.ts +0 -2
- package/target/dts/clients/cli/shared/index.d.ts +0 -1
- package/target/dts/core/agents/heartbeat/index.d.ts +2 -5
- package/target/dts/core/agents/heartbeat/pulse.d.ts +1 -3
- package/target/dts/core/agents/overview.d.ts +1 -5
- package/target/dts/core/agents/ports/host.d.ts +5 -37
- package/target/dts/core/agents/sessions/agent.d.ts +0 -17
- package/target/dts/core/agents/sessions/index.d.ts +0 -1
- package/target/dts/core/hub/history/index.d.ts +1 -11
- package/target/dts/core/hub/ports/store.d.ts +1 -5
- package/target/dts/core/hub/protocol/model.d.ts +0 -1
- package/target/dts/core/hub/protocol/reading.d.ts +0 -1
- package/target/dts/core/hub/rooms/index.d.ts +0 -4
- package/target/dts/core/hub/rooms/messages.d.ts +0 -1
- package/target/dts/core/hub/rooms/rooms.d.ts +1 -1
- package/target/dts/core/hub/signals/model.d.ts +0 -2
- package/target/dts/core/hub/usecases/hubs.d.ts +0 -1
- package/target/dts/core/hub/usecases/room.d.ts +0 -1
- package/target/dts/core/hub/usecases/rooms.d.ts +1 -5
- package/target/dts/core/hub/usecases/send.d.ts +0 -1
- package/target/dts/core/hub/usecases/view.d.ts +0 -22
- package/target/dts/core/hub/work/index.d.ts +0 -1
- package/target/dts/index.d.ts +0 -1
- package/target/esm/adapters/host-posix/index.js +30 -32
- package/target/esm/adapters/transport-git/branches.js +13 -20
- package/target/esm/adapters/transport-git/index.js +8 -9
- package/target/esm/adapters/transport-git/marks.js +4 -10
- package/target/esm/adapters/transport-git/remote/admit.js +8 -10
- package/target/esm/adapters/transport-git/remote/heartbeat-push.js +1 -1
- package/target/esm/adapters/transport-git/remote/heartbeat.js +4 -4
- package/target/esm/adapters/transport-git/remote/keys.js +7 -32
- package/target/esm/adapters/transport-git/remote/main.js +1 -4
- package/target/esm/adapters/transport-git/remote.js +1 -1
- package/target/esm/adapters/transport-git/repo.js +5 -3
- package/target/esm/adapters/transport-git/signing.js +12 -17
- package/target/esm/adapters/transport-git/transport.js +6 -6
- package/target/esm/clients/cli/agent/index.js +4 -10
- package/target/esm/clients/cli/arg-parse.js +1 -1
- package/target/esm/clients/cli/commands.js +6 -7
- package/target/esm/clients/cli/help.js +6 -13
- package/target/esm/clients/cli/operator/heartbeat-format.js +1 -1
- package/target/esm/clients/cli/operator/hub-commands.js +1 -1
- package/target/esm/clients/cli/operator/index.js +1 -1
- package/target/esm/clients/cli/operator/room.js +46 -7
- package/target/esm/clients/cli/operator/where-format.js +1 -3
- package/target/esm/clients/cli/shared/index.js +1 -2
- package/target/esm/clients/cli/shared/messages.js +1 -1
- package/target/esm/clients/cli/shared/profile.js +2 -2
- package/target/esm/clients/cli/shared/work.js +1 -1
- package/target/esm/composition/index.js +1 -3
- package/target/esm/core/agents/heartbeat/index.js +16 -23
- package/target/esm/core/agents/heartbeat/pulse.js +18 -33
- package/target/esm/core/agents/heartbeat/tools.js +1 -2
- package/target/esm/core/agents/overview.js +5 -6
- package/target/esm/core/agents/ports/host.js +2 -2
- package/target/esm/core/agents/sessions/agent.js +3 -23
- package/target/esm/core/agents/sessions/binding.js +5 -7
- package/target/esm/core/agents/sessions/index.js +3 -7
- package/target/esm/core/agents/sessions/join.js +1 -5
- package/target/esm/core/agents/sessions/resume.js +3 -4
- package/target/esm/core/agents/sessions/wait.js +10 -31
- package/target/esm/core/hub/capabilities.js +4 -2
- package/target/esm/core/hub/history/index.js +3 -1
- package/target/esm/core/hub/ports/store.js +3 -3
- package/target/esm/core/hub/protocol/index.js +3 -3
- package/target/esm/core/hub/protocol/model.js +2 -3
- package/target/esm/core/hub/protocol/reading.js +1 -3
- package/target/esm/core/hub/rooms/compat.js +1 -2
- package/target/esm/core/hub/rooms/hubs.js +2 -5
- package/target/esm/core/hub/rooms/index.js +3 -3
- package/target/esm/core/hub/rooms/messages.js +5 -10
- package/target/esm/core/hub/rooms/rooms.js +9 -38
- package/target/esm/core/hub/signals/effective.js +1 -21
- package/target/esm/core/hub/signals/index.js +2 -2
- package/target/esm/core/hub/signals/pins.js +1 -1
- package/target/esm/core/hub/usecases/hubs.js +0 -3
- package/target/esm/core/hub/usecases/room.js +2 -3
- package/target/esm/core/hub/usecases/rooms.js +4 -17
- package/target/esm/core/hub/usecases/roster.js +1 -1
- package/target/esm/core/hub/usecases/send.js +2 -12
- package/target/esm/core/hub/usecases/view.js +2 -6
- package/target/esm/core/hub/work/task.js +4 -11
- package/target/esm/index.js +1 -2
- package/target/esm/lib/json.js +1 -1
- package/target/dts/adapters/host-posix/group-state.d.ts +0 -7
- package/target/dts/adapters/host-posix/groups.d.ts +0 -1
- package/target/dts/adapters/host-posix/launch.d.ts +0 -7
- package/target/dts/adapters/host-posix/probes.d.ts +0 -5
- package/target/dts/adapters/host-posix/stats.d.ts +0 -9
- package/target/dts/adapters/transport-kafka/index.d.ts +0 -2
- package/target/dts/adapters/transport-temporal/index.d.ts +0 -2
- package/target/dts/clients/cli/operator/room-lifecycle.d.ts +0 -5
- package/target/dts/clients/cli/operator/room-list.d.ts +0 -5
- package/target/dts/clients/cli/operator/room-members.d.ts +0 -4
- package/target/dts/clients/cli/operator/watch.d.ts +0 -2
- package/target/dts/clients/cli/shared/budget.d.ts +0 -7
- package/target/dts/core/agents/turns/budgets.d.ts +0 -5
- package/target/dts/core/agents/turns/dispatch.d.ts +0 -9
- package/target/dts/core/agents/turns/exhaustion.d.ts +0 -11
- package/target/dts/core/agents/turns/grants.d.ts +0 -4
- package/target/dts/core/agents/turns/index.d.ts +0 -4
- package/target/dts/core/agents/turns/limits.d.ts +0 -2
- package/target/dts/core/agents/turns/messages.d.ts +0 -10
- package/target/dts/core/agents/turns/pings.d.ts +0 -5
- package/target/dts/core/agents/turns/plan.d.ts +0 -3
- package/target/dts/core/agents/turns/poll.d.ts +0 -8
- package/target/dts/core/agents/turns/results.d.ts +0 -3
- package/target/dts/core/agents/turns/seat.d.ts +0 -2
- package/target/dts/core/agents/turns/types.d.ts +0 -59
- package/target/dts/core/hub/decisions/budget.d.ts +0 -15
- package/target/dts/core/hub/decisions/check.d.ts +0 -6
- package/target/dts/core/hub/decisions/index.d.ts +0 -5
- package/target/dts/core/hub/decisions/proposal.d.ts +0 -38
- package/target/dts/core/hub/decisions/usage.d.ts +0 -12
- package/target/dts/core/hub/usecases/budget.d.ts +0 -23
- package/target/dts/core/hub/usecases/propose.d.ts +0 -17
- package/target/esm/adapters/host-posix/group-state.js +0 -24
- package/target/esm/adapters/host-posix/groups.js +0 -43
- package/target/esm/adapters/host-posix/launch.js +0 -80
- package/target/esm/adapters/host-posix/probes.js +0 -66
- package/target/esm/adapters/host-posix/stats.js +0 -27
- package/target/esm/adapters/transport-kafka/index.js +0 -4
- package/target/esm/adapters/transport-temporal/index.js +0 -4
- package/target/esm/clients/cli/operator/room-lifecycle.js +0 -23
- package/target/esm/clients/cli/operator/room-list.js +0 -27
- package/target/esm/clients/cli/operator/room-members.js +0 -15
- package/target/esm/clients/cli/operator/watch.js +0 -15
- package/target/esm/clients/cli/shared/budget.js +0 -50
- package/target/esm/core/agents/turns/budgets.js +0 -48
- package/target/esm/core/agents/turns/dispatch.js +0 -40
- package/target/esm/core/agents/turns/exhaustion.js +0 -16
- package/target/esm/core/agents/turns/grants.js +0 -36
- package/target/esm/core/agents/turns/index.js +0 -33
- package/target/esm/core/agents/turns/limits.js +0 -11
- package/target/esm/core/agents/turns/messages.js +0 -32
- package/target/esm/core/agents/turns/pings.js +0 -30
- package/target/esm/core/agents/turns/plan.js +0 -29
- package/target/esm/core/agents/turns/poll.js +0 -48
- package/target/esm/core/agents/turns/results.js +0 -40
- package/target/esm/core/agents/turns/seat.js +0 -44
- package/target/esm/core/agents/turns/types.js +0 -3
- package/target/esm/core/hub/decisions/budget.js +0 -40
- package/target/esm/core/hub/decisions/check.js +0 -43
- package/target/esm/core/hub/decisions/index.js +0 -10
- package/target/esm/core/hub/decisions/proposal.js +0 -73
- package/target/esm/core/hub/decisions/usage.js +0 -39
- package/target/esm/core/hub/usecases/budget.js +0 -78
- package/target/esm/core/hub/usecases/propose.js +0 -39
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Parse one option at a time, keeping positional arguments and repeated values in order.
|
|
2
2
|
import { fail } from '../../lib/index.js';
|
|
3
|
-
const BOOLS = new Set(['task', 'peek', 'all', 'json', 'help', 'once', 'project', 'follow', 'short', 'yes', '
|
|
3
|
+
const BOOLS = new Set(['task', 'peek', 'all', 'json', 'help', 'once', 'project', 'follow', 'short', 'yes', 'md', 'signed', 'repair', 'clear', 'published']);
|
|
4
4
|
const MULTI = new Set(['m', 'attach', 'set', 'drop']);
|
|
5
5
|
const SHORT = { m: 'm', F: 'file', n: 'n', o: 'out', h: 'help' };
|
|
6
6
|
const option = (a) => {
|
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
import { about } from '../../core/hub/usecases/context.js';
|
|
4
4
|
import { agent } from '../../core/agents/sessions/context.js';
|
|
5
5
|
import { fail } from '../../lib/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { inbox, take, wait, whoami } from './agent/index.js';
|
|
7
7
|
import { flag, out, parseArgs } from './args.js';
|
|
8
8
|
import { operatorCtx } from './context.js';
|
|
9
9
|
import { helpMarkdown, helpText } from './help.js';
|
|
10
10
|
import { forget, heartbeats, hub, pulse, roomCmd, sessions, where } from './operator/index.js';
|
|
11
11
|
import { join, leave, resume, stop } from './agent/session.js';
|
|
12
|
-
import { add, cat,
|
|
12
|
+
import { add, cat, get, log, profileCmd, read, reply, send, signals, tasks, thread, who } from './shared/index.js';
|
|
13
13
|
import { skill } from './skill/index.js';
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const AGENT = { ...SHARED, inbox, whoami, status: whoami, card, ping, wait, take };
|
|
14
|
+
const SHARED = { add, send, reply, read, thread, tasks, log, cat, get, who, signals, profile: profileCmd };
|
|
15
|
+
const AGENT = { ...SHARED, inbox, whoami, status: whoami, wait, take };
|
|
17
16
|
const agentCommand = (_, rest) => {
|
|
18
17
|
const [sub = 'whoami', ...more] = rest;
|
|
19
18
|
const fn = AGENT[sub] ?? fail(`unknown: ringi agent ${sub}; see ringi help`);
|
|
@@ -28,10 +27,10 @@ const DIRECT = new Map([
|
|
|
28
27
|
['--version', version], ['-v', version], ['version', version],
|
|
29
28
|
['help', help], ['--help', help], ['-h', help], ['skill', skill], ['agent', agentCommand],
|
|
30
29
|
['hub', hub], ['room', roomCmd], ['where', where], ['sessions', sessions], ['heartbeats', heartbeats],
|
|
31
|
-
['pulse', pulse], ['forget', forget], ['
|
|
30
|
+
['pulse', pulse], ['forget', forget], ['join', join], ['continue', resume], ['stop', stop], ['leave', leave]
|
|
32
31
|
]);
|
|
33
32
|
const unknown = (cmd) => {
|
|
34
|
-
if (['inbox', 'whoami', '
|
|
33
|
+
if (['inbox', 'whoami', 'wait', 'take'].includes(cmd))
|
|
35
34
|
fail(`${cmd} is an agent command: ringi agent ${cmd}`);
|
|
36
35
|
return fail(`unknown command ${cmd}; see ringi help`);
|
|
37
36
|
};
|
|
@@ -7,21 +7,16 @@ export const TABLE = [
|
|
|
7
7
|
['hub create <name> [--signed [--url <git url>]]', 'operator', 'another hub: asserted senders, or signed with a root key made on this machine; --url publishes it'],
|
|
8
8
|
['hub add <name> <git url> --root <public key|file> [--genesis <sha>]', 'operator', 'a replica of a signed hub; the root key comes from you, out of band, never from the hub'],
|
|
9
9
|
['hub switch <name>', 'operator', 'change the terminal\'s current hub; agents stay where they are'],
|
|
10
|
-
['hub amend -m <subject> [--set <key>=<value>]... [--
|
|
11
|
-
['room list
|
|
10
|
+
['hub amend -m <subject> [--set <key>=<value>]... [--attach <file>:profiles/<name>.json]...', 'operator', 'refine the shared contract: version, capabilities, default profile, profiles'],
|
|
11
|
+
['room list', 'operator', 'rooms of the current hub'],
|
|
12
12
|
['room create <id> [--profile <name>] [--quorum <agents>]', 'operator', 'a room for one piece of work, following a profile; becomes the current room; --quorum: its work starts by itself once that many agents joined (1: at once)'],
|
|
13
13
|
['room switch <id>', 'operator', 'change the terminal\'s current room; agents stay where they are'],
|
|
14
14
|
['room admit <agent> <public key|file>', 'operator', 'signed hubs: admit the key an agent made on its own machine'],
|
|
15
15
|
['room remove <agent>', 'operator', 'local hubs: take an agent out of the current room'],
|
|
16
|
-
['room archive [<id>]', 'operator', 'local hubs: a room (default: the current one) is read-only from now on, wakes nobody; its sessions leave on their own'],
|
|
17
16
|
['room delete [<id>] --yes', 'operator', 'local hubs: erase a room (default: the current one) and everything kept for it; its sessions leave on their own'],
|
|
18
17
|
['room rebase-contract <id>', 'operator', 'move a room onto the hub\'s current contract'],
|
|
19
|
-
['where [-n n] [--short] [--follow]', 'operator', 'hub, room, sessions (active, stopped, gone), open work
|
|
20
|
-
['send -m <subject> [-m <body>]... [--to <ids>] [--task] [--re <msg>] [--state <s>] [--assign <ids>] [--roles <id>=<label>,...] [--
|
|
21
|
-
['propose -m <subject> [-m <body>] --decider <id> [--reviewers <ids>] [--due 2h|<ISO>] [--budget runs=<n>,run-seconds=<n>,concurrency=<n>] [--supersedes <proposal>]', 'both', 'a proposal; it circulates to the reviewers and the decider'],
|
|
22
|
-
['stamp <proposal> approve|object|abstain -m <reason>', 'both', 'a named reviewer\'s stamp on that exact revision'],
|
|
23
|
-
['decide <proposal> approved|rejected|revoked -m <reason>', 'both', 'the named decider, with authority; an approval with a budget is a grant'],
|
|
24
|
-
['proposals [--all] | grants', 'both', 'open proposals with their stamps; grants with their usage'],
|
|
18
|
+
['where [-n n] [--short] [--follow]', 'operator', 'hub, room, sessions (active, stopped, gone), open work, latest messages'],
|
|
19
|
+
['send -m <subject> [-m <body>]... [--to <ids>] [--task] [--re <msg>] [--state <s>] [--assign <ids>] [--roles <id>=<label>,...] [--attach <src>[:<dest>]]...', 'operator', 'a message as operator; without --to it goes to the room and wakes nobody; --task opens work of the room\'s profile'],
|
|
25
20
|
['add <src>[:<dest>]... | add [--drop <path>]... [--clear]', 'both', 'stage files for your next message, like git add: their content is taken now; results and state travel only as attachments'],
|
|
26
21
|
['tasks [--all] | log [-n n] | read <msg> | thread <msg> | cat <msg> [<path>] | get <msg> [-o <dir>] | who', 'both', 'reading the room (agents: ringi agent …)'],
|
|
27
22
|
['signals [--all] [--repair]', 'both', 'what happened (signals) and where attention is (read and taken marks), each checked; --repair restores your missing signals'],
|
|
@@ -30,7 +25,6 @@ export const TABLE = [
|
|
|
30
25
|
['heartbeats [--json] | heartbeats --published [--room <id>]', 'operator', 'each session\'s pulse on this machine, from its process and its tool\'s trace, without the model: working, waiting (for what) or gone; --published: the room\'s heartbeat tags as an orchestrator reads them'],
|
|
31
26
|
['pulse [--beat <s>] [--once]', 'operator', 'beat for every session here: sync its hub, put its heartbeat tag on the last pulled commit of its room; join and continue start it (RINGI_PULSE=off: not)'],
|
|
32
27
|
['forget <room>/<log>', 'operator', 'after a changed-history alarm: accept a new baseline for that log'],
|
|
33
|
-
['watch [<hub>:]<room>/<agent> --exec <cmd> [--handoff] [--scope <text>] [--once] [--require-grant] [--from <ids>] [--max-hop n] [--max-runs n] [--window s] [--interval s]', 'operator', 'headless turns for one agent: answers pings, spends grants, writes usage receipts; --handoff, run from the agent\'s own chat, moves its seat and scope to the watcher; exits with the failed turn\'s status'],
|
|
34
28
|
['skill install [--project] | uninstall | status | show', 'operator', 'the skill for Claude Code, Codex and Cursor'],
|
|
35
29
|
['join <hub> <room> [--as <id>] [--scope <text>]', 'session', 'bind this chat to a room as an agent, with what the operator delegates'],
|
|
36
30
|
['continue [[<hub>:]<room>/<agent>]', 'session', 'resume this chat\'s binding, also after a restart; naming one takes over a paused or gone one'],
|
|
@@ -39,10 +33,9 @@ export const TABLE = [
|
|
|
39
33
|
['agent whoami', 'agent', 'identity, room, profile, scope'],
|
|
40
34
|
['agent inbox [--peek] [--limit n]', 'agent', 'what is new for you; marks it seen and leaves a read mark on each (--peek: neither)'],
|
|
41
35
|
['agent take <work>', 'agent', 'a taken mark: you are on it; others see it in tasks and read. It changes no state'],
|
|
42
|
-
['agent send … | reply <msg> -m … [--state <s>] [--assign <ids>] [--roles …] [--
|
|
43
|
-
['agent propose | stamp | decide | proposals | grants', 'agent', 'decisions, as the joined agent'],
|
|
36
|
+
['agent send … | reply <msg> -m … [--state <s>] [--assign <ids>] [--roles …] [--attach …] [--all]', 'agent', 'write; answer the message you act on'],
|
|
44
37
|
['agent tasks | log | read | thread | cat | get | who | signals | profile', 'agent', 'reading, as the joined agent'],
|
|
45
|
-
['agent
|
|
38
|
+
['agent wait [--max <s>]', 'agent', 'hold the line only when the operator asks'],
|
|
46
39
|
];
|
|
47
40
|
export const helpText = () => {
|
|
48
41
|
const groups = [['operator', 'operator: in a terminal, in the current hub and room'], ['session', 'session: in a chat, on the operator\'s words'], ['agent', 'agent: during a turn, in the session\'s own room'], ['both', 'operator in a terminal, or an agent as ringi agent …']];
|
|
@@ -3,6 +3,6 @@ import { short } from '../../../lib/index.js';
|
|
|
3
3
|
export const publishedLine = (h) => `${h.agent.padEnd(12)} ${(h.waits ? `${h.state} (${h.waits})` : h.state).padEnd(22)} ${h.age}s ago${h.overdue ? ', overdue' : ''} on ${short(h.target)}`;
|
|
4
4
|
export const pulseLine = ({ session: s, tool, beat: b }) => {
|
|
5
5
|
const state = b.waits ? `${b.state} (${b.waits})` : b.state;
|
|
6
|
-
return `${s.agent.padEnd(12)} ${`${s.hub}/${s.room}`.padEnd(24)} ${state.padEnd(22)} alive ${b.alive === null ? '?' : b.alive ? 'yes' : 'no'} quiet ${b.quiet}s
|
|
6
|
+
return `${s.agent.padEnd(12)} ${`${s.hub}/${s.room}`.padEnd(24)} ${state.padEnd(22)} alive ${b.alive === null ? '?' : b.alive ? 'yes' : 'no'} quiet ${b.quiet}s ${tool} ${s.handle}`;
|
|
7
7
|
};
|
|
8
8
|
//# sourceMappingURL=heartbeat-format.js.map
|
|
@@ -31,7 +31,7 @@ export const addHub = (o) => {
|
|
|
31
31
|
export const amendHub = (o) => {
|
|
32
32
|
const H = Hubs.hubOf(opt(o, 'hub'));
|
|
33
33
|
const files = list(o, 'attach').flatMap(attachments).map(e => ({ path: e.path, content: fs.readFileSync(e.file) }));
|
|
34
|
-
const r = Hubs.amend(H, { text: textOf(o), set: list(o, 'set'),
|
|
34
|
+
const r = Hubs.amend(H, { text: textOf(o), set: list(o, 'set'), files });
|
|
35
35
|
return out(`amended hub ${H.name}: contract ${r.version}; rooms keep their base until ringi room rebase-contract <room>`);
|
|
36
36
|
};
|
|
37
37
|
//# sourceMappingURL=hub-commands.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// operator: the commands only the operator runs, as the operator of this machine in its terminal context: hubs, rooms,
|
|
2
|
-
// where things stand, sessions, heartbeats
|
|
2
|
+
// where things stand, sessions, heartbeats and the pulse.
|
|
3
3
|
export { heartbeats, pulse } from './heartbeat.js';
|
|
4
4
|
export { hub } from './hub.js';
|
|
5
5
|
export { roomCmd } from './room.js';
|
|
@@ -1,11 +1,50 @@
|
|
|
1
|
-
// room: `ringi room
|
|
1
|
+
// room: `ringi room list | create | switch | admit | remove | delete | rebase-contract`.
|
|
2
|
+
import { roomsActive } from '../../../core/agents/overview.js';
|
|
2
3
|
import * as Hubs from '../../../core/hub/usecases/hubs.js';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import * as Rooms from '../../../core/hub/usecases/rooms.js';
|
|
5
|
+
import { fail, short } from '../../../lib/index.js';
|
|
6
|
+
import { flag, opt, out } from '../args.js';
|
|
7
|
+
const list = H => {
|
|
8
|
+
const rs = roomsActive(H);
|
|
9
|
+
for (const r of rs)
|
|
10
|
+
out(`${r.current ? '*' : ' '} ${r.name.padEnd(16)} ${r.members} member(s), ${r.active} active session(s), profile ${r.profile} genesis ${short(r.genesis)}`);
|
|
11
|
+
if (!rs.length)
|
|
12
|
+
out(`no rooms in hub ${H.name}: ringi room create <id>`);
|
|
13
|
+
};
|
|
14
|
+
const create = (H, arg, o) => {
|
|
15
|
+
const quorum = opt(o, 'quorum');
|
|
16
|
+
const r = Rooms.create(H, arg ?? '', { profile: opt(o, 'profile'), quorum: quorum === undefined ? undefined : Number(quorum) });
|
|
17
|
+
const start = quorum === undefined ? '' : ` Its work starts by itself at a quorum of ${quorum} agent(s).`;
|
|
18
|
+
out(`created room ${arg} in hub ${H.name} with profile ${r.profile}; it is the current room.${start} In each chat that should take part, say "join ${arg} as <id>"`);
|
|
19
|
+
};
|
|
20
|
+
const use = (H, arg) => {
|
|
21
|
+
const R = Rooms.use(H, arg ?? fail('usage: ringi room switch <id>'));
|
|
22
|
+
out(`current room: ${H.name}/${R.name}; agents keep their own rooms`);
|
|
23
|
+
};
|
|
24
|
+
const admit = (H, arg, o) => {
|
|
25
|
+
const R = Rooms.roomIn(H, opt(o, 'room'));
|
|
26
|
+
Rooms.admit(H, R, arg ?? fail('usage: ringi room admit <agent> <public key|file>'), o._[2] ?? fail('usage: ringi room admit <agent> <public key|file>'));
|
|
27
|
+
out(`admitted ${arg} to ${H.name}/${R.name}: its signed messages are accepted from now on; it can join`);
|
|
28
|
+
};
|
|
29
|
+
const remove = (H, arg, o) => {
|
|
30
|
+
const R = Rooms.roomIn(H, opt(o, 'room'));
|
|
31
|
+
Rooms.remove(H, R, arg ?? fail('usage: ringi room remove <agent>'));
|
|
32
|
+
out(`removed ${arg} from ${R.name}: it can no longer write or be woken there; returning needs a fresh join`);
|
|
33
|
+
};
|
|
34
|
+
// Without an id, the room of the current context.
|
|
35
|
+
const erase = (H, arg, o) => {
|
|
36
|
+
const R = Rooms.roomIn(H, arg ?? opt(o, 'room'));
|
|
37
|
+
if (!flag(o, 'yes'))
|
|
38
|
+
fail(`deleting ${R.name} in hub ${H.name} erases it and everything kept for it, for good. Run again with --yes`);
|
|
39
|
+
Rooms.erase(H, R);
|
|
40
|
+
out(`deleted room ${R.name}: its branch, signals, pins, evidence and readers' state. Sessions bound to it leave it on their next turn`);
|
|
41
|
+
};
|
|
42
|
+
const rebase = (H, arg) => {
|
|
43
|
+
const R = Rooms.roomIn(H, arg ?? fail('usage: ringi room rebase-contract <id>'));
|
|
44
|
+
Rooms.rebase(H, R);
|
|
45
|
+
out(`room ${R.name} now follows the hub contract at ${short(H.main)} (${H.contract.version})`);
|
|
46
|
+
};
|
|
47
|
+
const COMMANDS = new Map([['list', list], ['create', create], ['switch', use], ['admit', admit], ['remove', remove], ['delete', erase], ['rebase-contract', rebase]]);
|
|
9
48
|
export const roomCmd = (o) => {
|
|
10
49
|
const [sub, arg] = o._;
|
|
11
50
|
const H = Hubs.hubOf(opt(o, 'hub'));
|
|
@@ -12,13 +12,11 @@ const quorumLine = (R) => {
|
|
|
12
12
|
const agents = Object.values(R.members).filter(m => m.role !== 'root').length;
|
|
13
13
|
return agents >= R.quorum ? `; started at its quorum of ${R.quorum} agents` : `; waiting for a quorum of ${R.quorum} agents, has ${agents}`;
|
|
14
14
|
};
|
|
15
|
-
const roomLine = (R) => `room ${R.name}
|
|
15
|
+
const roomLine = (R) => `room ${R.name}, genesis ${short(R.genesis)}, profile ${R.profile.name}; members: ${Object.entries(R.members).map(([id, i]) => i.role === 'root' ? `${id} (root)` : id).join(', ')}` + quorumLine(R);
|
|
16
16
|
export const printOverview = (w, R) => {
|
|
17
17
|
out(roomLine(R));
|
|
18
18
|
out(w.sessions.length ? `sessions:\n${w.sessions.map(s => ` ${sessionLine(s)}`).join('\n')}` : 'sessions: none joined');
|
|
19
19
|
out(w.work.length ? `open work:\n${w.work.map(t => ` ${taskLine(t)}`).join('\n')}` : 'open work: none');
|
|
20
|
-
if (w.proposals.length)
|
|
21
|
-
out(`open proposals:\n${w.proposals.map(x => ` ${short(x.proposal.id)} ${x.state} → ${x.decider} ${x.proposal.subject}`).join('\n')}`);
|
|
22
20
|
};
|
|
23
21
|
export const showMessage = (R, o, all, m) => out(flag(o, 'short') ? ` ${oneLine(m)}` : `\n${render(all, m, { max: 2000, files: () => withSizes(R, m) })}`);
|
|
24
22
|
//# sourceMappingURL=where-format.js.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// shared: the commands both run: `ringi send` acts as the operator, `ringi agent send` as the agent its session is
|
|
2
|
-
// bound to; messages, files, work
|
|
3
|
-
export { decide, grants, proposals, propose, stamp_ } from './budget.js';
|
|
2
|
+
// bound to; messages, files, work and signals.
|
|
4
3
|
export { cat, get } from './files.js';
|
|
5
4
|
export { add, read, reply, send, thread } from './messages.js';
|
|
6
5
|
export { profileCmd, who } from './profile.js';
|
|
@@ -6,7 +6,7 @@ import { bytes, fail, short, words } from '../../../lib/index.js';
|
|
|
6
6
|
import { attachments, dump, flag, list, opt, out, textOf } from '../args.js';
|
|
7
7
|
const sendInput = (o) => ({
|
|
8
8
|
text: textOf(o), to: words(opt(o, 'to')), all: flag(o, 'all'), task: flag(o, 'task'),
|
|
9
|
-
state: opt(o, 'state'), assign: opt(o, 'assign') ? words(opt(o, 'assign')) : undefined, roles: opt(o, 'roles'),
|
|
9
|
+
state: opt(o, 'state'), assign: opt(o, 'assign') ? words(opt(o, 'assign')) : undefined, roles: opt(o, 'roles'),
|
|
10
10
|
files: list(o, 'attach').flatMap(attachments)
|
|
11
11
|
});
|
|
12
12
|
const sent = (r) => out(`sent ${short(r.id)} to ${r.to.join(', ')}${r.files ? ` with ${r.files} file(s)` : ''}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// profile: `profile` (what work is in the room) and `who` (its members
|
|
1
|
+
// profile: `profile` (what work is in the room) and `who` (its members).
|
|
2
2
|
import * as Room from '../../../core/hub/usecases/room.js';
|
|
3
3
|
import { dump, flag, out } from '../args.js';
|
|
4
4
|
export const profileCmd = ({ R }, o) => {
|
|
@@ -12,6 +12,6 @@ export const profileCmd = ({ R }, o) => {
|
|
|
12
12
|
};
|
|
13
13
|
export const who = (a) => {
|
|
14
14
|
for (const x of Room.members(a))
|
|
15
|
-
out(`${x.id.padEnd(12)} ${x.role.padEnd(7)} ${String(x.messages).padStart(4)} msg
|
|
15
|
+
out(`${x.id.padEnd(12)} ${x.role.padEnd(7)} ${String(x.messages).padStart(4)} msg`);
|
|
16
16
|
};
|
|
17
17
|
//# sourceMappingURL=profile.js.map
|
|
@@ -5,7 +5,7 @@ import { short, when } from '../../../lib/index.js';
|
|
|
5
5
|
import { dump, flag, opt, out } from '../args.js';
|
|
6
6
|
export const taskLine = (t) => {
|
|
7
7
|
const roles = Object.keys(t.roles).length ? ` [${Object.entries(t.roles).map(([id, label]) => `${id}=${label}`).join(' ')}]` : '';
|
|
8
|
-
return `${short(t.id)} ${t.state.padEnd(14)} ${t.requester} → ${t.assignees.join(',') || '(open)'}${roles}${t.taken.length ? ` taken by ${t.taken.join(',')}` : ''}
|
|
8
|
+
return `${short(t.id)} ${t.state.padEnd(14)} ${t.requester} → ${t.assignees.join(',') || '(open)'}${roles}${t.taken.length ? ` taken by ${t.taken.join(',')}` : ''} ${when(Date.parse(t.at) / 1000).slice(5)} by ${t.by} ${t.subject}`;
|
|
9
9
|
};
|
|
10
10
|
export const tasks = (a, o) => {
|
|
11
11
|
const { views } = Room.tasks(a, { all: flag(o, 'all') });
|
|
@@ -15,8 +15,6 @@ import { cursor } from '../adapters/tool-cursor/index.js';
|
|
|
15
15
|
import { hostPosix } from '../adapters/host-posix/index.js';
|
|
16
16
|
import { keysFile } from '../adapters/keys-file/index.js';
|
|
17
17
|
import { gitTransport } from '../adapters/transport-git/transport.js';
|
|
18
|
-
import { kafka } from '../adapters/transport-kafka/index.js';
|
|
19
|
-
import { temporal } from '../adapters/transport-temporal/index.js';
|
|
20
18
|
// The package this file belongs to, run from its source (src/main/ts) or its build (target/esm).
|
|
21
19
|
const packageOf = (dir) => fs.existsSync(path.join(dir, 'package.json')) ? dir : packageOf(path.dirname(dir));
|
|
22
20
|
const PACKAGE = packageOf(path.dirname(fileURLToPath(import.meta.url)));
|
|
@@ -26,7 +24,7 @@ export const build = {
|
|
|
26
24
|
};
|
|
27
25
|
const root = path.resolve(process.env.RINGI_HOME || path.join(os.homedir(), '.ringi'));
|
|
28
26
|
// The git store signs with keys kept as files under the home, made only for a signed hub.
|
|
29
|
-
const transports = { git: gitTransport(root, { signer: () => keysFile(root) })
|
|
27
|
+
const transports = { git: gitTransport(root, { signer: () => keysFile(root) }) };
|
|
30
28
|
wireHub({ build, state: stateFs({ root, build }), transports, store: 'git' });
|
|
31
29
|
// In the order a session is recognised by.
|
|
32
30
|
wireAgents({ tools: [claude, codex, cursor], host: hostPosix() });
|
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
// heartbeat: whether an agent is alive, and working or waiting, judged from what its machine shows without asking the
|
|
2
|
-
// model, so no tokens are spent on it. The facts: its process
|
|
3
|
-
//
|
|
2
|
+
// model, so no tokens are spent on it. The facts: whether its process lives (ProcessHost), when its tool last wrote the
|
|
3
|
+
// session's trace (tools.ts) and its last ringi command. What to do about a silent agent is the orchestrator's.
|
|
4
4
|
import { agentPorts } from '../ports/index.js';
|
|
5
5
|
import { toolOf, traceTime } from './tools.js';
|
|
6
|
-
// The facts of one session now
|
|
7
|
-
export const probe = (s, waits = null
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
export const tracedBy = (s) => toolOf(s)?.tool ?? (s.product === 'watch' ? 'watch' : 'none');
|
|
19
|
-
// working: its process tree used CPU since the previous look, or its trace or a ringi command is younger than
|
|
20
|
-
// `active` seconds; waiting: alive but neither, or kept waiting by the operator or its room; gone: its process is
|
|
21
|
-
// known to have ended.
|
|
22
|
-
export const judge = (cur, prev = null, { active = 120, next = 30 } = {}) => {
|
|
6
|
+
// The facts of one session now.
|
|
7
|
+
export const probe = (s, waits = null) => ({
|
|
8
|
+
at: Date.now() / 1000,
|
|
9
|
+
alive: s.pid && agentPorts().host.supported ? agentPorts().host.alive(s.pid) : null,
|
|
10
|
+
trace: traceTime(s),
|
|
11
|
+
seen: Date.parse(s.lastSeen) / 1000,
|
|
12
|
+
stopped: s.state === 'stopped', waits,
|
|
13
|
+
});
|
|
14
|
+
export const tracedBy = (s) => toolOf(s)?.tool ?? 'none';
|
|
15
|
+
// working: its trace or a ringi command is younger than `active` seconds; waiting: alive but quiet, or kept waiting by
|
|
16
|
+
// the operator or its room; gone: its process is known to have ended.
|
|
17
|
+
export const judge = (cur, { active = 120, next = 30 } = {}) => {
|
|
23
18
|
const quiet = Math.max(0, Math.round(cur.at - Math.max(cur.seen, cur.trace ?? 0)));
|
|
24
|
-
const base = { alive: cur.alive, quiet,
|
|
19
|
+
const base = { alive: cur.alive, quiet, next };
|
|
25
20
|
if (cur.alive === false)
|
|
26
21
|
return { ...base, state: 'gone', waits: null };
|
|
27
22
|
if (cur.stopped)
|
|
28
23
|
return { ...base, state: 'waiting', waits: 'stopped' };
|
|
29
24
|
if (cur.waits)
|
|
30
25
|
return { ...base, state: 'waiting', waits: cur.waits };
|
|
31
|
-
|
|
32
|
-
return spent || quiet < active ? { ...base, state: 'working', waits: null } : { ...base, state: 'waiting', waits: 'inbox' };
|
|
26
|
+
return quiet < active ? { ...base, state: 'working', waits: null } : { ...base, state: 'waiting', waits: 'inbox' };
|
|
33
27
|
};
|
|
34
28
|
// The text of a heartbeat: facts as trailers, no word of the work itself.
|
|
35
29
|
export const body = (b) => [
|
|
@@ -37,7 +31,6 @@ export const body = (b) => [
|
|
|
37
31
|
...(b.waits ? [`Waits: ${b.waits}`] : []),
|
|
38
32
|
`Alive: ${b.alive === null ? 'unknown' : b.alive ? 'yes' : 'no'}`,
|
|
39
33
|
`Quiet: ${b.quiet}`,
|
|
40
|
-
...(b.cpu === null ? [] : [`CPU: ${b.cpu.toFixed(1)}`]),
|
|
41
34
|
`Next: ${b.next}`,
|
|
42
35
|
].join('\n') + '\n';
|
|
43
36
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// heartbeat: the pulse of every session this machine holds, judged from its process and its tool's trace without the
|
|
2
2
|
// model (index.ts). It reads session records only: it never binds a session, renews its lease or marks anything read.
|
|
3
3
|
import * as HB from './index.js';
|
|
4
|
-
import { agentPorts } from '../ports/index.js';
|
|
5
4
|
import { hubPorts } from '../../hub/ports/index.js';
|
|
6
5
|
import { claim, release } from './singleton.js';
|
|
7
6
|
export { ensurePulse } from './singleton.js';
|
|
@@ -18,41 +17,27 @@ const roomWaits = (s) => {
|
|
|
18
17
|
return null;
|
|
19
18
|
}
|
|
20
19
|
};
|
|
21
|
-
//
|
|
22
|
-
export const heartbeats =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!R || R.genesis !== s.genesis || !H.store.heartbeat)
|
|
39
|
-
return [];
|
|
40
|
-
const f = HB.probe(s, start(R).started ? null : 'quorum', rows);
|
|
41
|
-
const b = HB.judge(f, prev.get(s.handle) ?? null, { next });
|
|
42
|
-
prev.set(s.handle, f);
|
|
43
|
-
H.store.heartbeat(R.s, s.agent, R.s.tip, HB.body(b));
|
|
44
|
-
return [{ session: s, beat: b }];
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
return [{ session: s, error: e.message }];
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
};
|
|
20
|
+
// Every session here, judged now.
|
|
21
|
+
export const heartbeats = () => allSessions().map(s => ({ session: s, tool: HB.tracedBy(s), beat: HB.judge(HB.probe(s, roomWaits(s))) }));
|
|
22
|
+
const beatAll = (next) => allSessions().flatMap((s) => {
|
|
23
|
+
try {
|
|
24
|
+
const H = openHub(s.hub);
|
|
25
|
+
const R = openRoom(H, s.room, { quiet: true });
|
|
26
|
+
// A gone room: the session leaves it on its own; nothing to beat for.
|
|
27
|
+
if (!R || R.genesis !== s.genesis || !H.store.heartbeat)
|
|
28
|
+
return [];
|
|
29
|
+
const b = HB.judge(HB.probe(s, start(R).started ? null : 'quorum'), { next });
|
|
30
|
+
H.store.heartbeat(R.s, s.agent, R.s.tip, HB.body(b));
|
|
31
|
+
return [{ session: s, beat: b }];
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return [{ session: s, error: e.message }];
|
|
35
|
+
}
|
|
36
|
+
});
|
|
51
37
|
// Beats until no session is left twice in a row, or the home is gone; `once`: one round.
|
|
52
38
|
const beatLoop = async (every, once, log) => {
|
|
53
|
-
const prev = new Map();
|
|
54
39
|
for (let idle = 0; idle < 2;) {
|
|
55
|
-
const beats = beatAll(
|
|
40
|
+
const beats = beatAll(every);
|
|
56
41
|
for (const b of beats)
|
|
57
42
|
if (b.error)
|
|
58
43
|
log(`pulse: ${b.session.agent} in ${b.session.hub}/${b.session.room}: ${b.error}`);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// tools: which agent tool a session runs in, and when that tool last wrote the session's trace, asked of the tools
|
|
2
2
|
// this machine knows (core/agents/ports/tools.ts). Its modification time is the session's own activity, unlike the CPU of an app
|
|
3
|
-
// process that hosts many sessions.
|
|
4
|
-
// its turn (index.ts).
|
|
3
|
+
// process that hosts many sessions.
|
|
5
4
|
import { agentPorts } from '../ports/index.js';
|
|
6
5
|
const traced = (s) => {
|
|
7
6
|
for (const tool of agentPorts().tools) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// overview: where things stand for the operator: the hub, the room, who is in, open work
|
|
1
|
+
// overview: where things stand for the operator: the hub, the room, who is in, open work, the latest
|
|
2
2
|
// messages, and new ones as they land; and the sessions of this machine.
|
|
3
|
-
import * as D from '../hub/decisions/index.js';
|
|
4
3
|
import { hubPorts, locked } from '../hub/ports/index.js';
|
|
5
4
|
import { HIDDEN, OPERATOR, openHub, room as roomOf } from '../hub/rooms/index.js';
|
|
6
5
|
import { fail } from '../../lib/index.js';
|
|
@@ -12,11 +11,11 @@ import { rooms } from '../hub/usecases/rooms.js';
|
|
|
12
11
|
const sessionView = (s) => ({ ...s, liveness: liveness(s) });
|
|
13
12
|
export const where = (H, name) => {
|
|
14
13
|
if (!name)
|
|
15
|
-
return { hub: H, room: null, sessions: [], work: [],
|
|
14
|
+
return { hub: H, room: null, sessions: [], work: [], all: new Map(), messages: [] };
|
|
16
15
|
const R = roomOf(H, name);
|
|
17
16
|
const { all } = linked(R, OPERATOR);
|
|
18
17
|
return { hub: H, room: R, sessions: allSessions().filter(s => s.hub === H.name && s.genesis === R.genesis).map(sessionView),
|
|
19
|
-
work: tasks({ R, me: OPERATOR }).views,
|
|
18
|
+
work: tasks({ R, me: OPERATOR }).views,
|
|
20
19
|
all, messages: ordered([...all.values()].filter(m => !(m.kind && HIDDEN.has(m.kind)))) };
|
|
21
20
|
};
|
|
22
21
|
// The visible messages that landed since `seen` (which it extends), read afresh from the store.
|
|
@@ -28,9 +27,9 @@ export const arrived = (H, R, seen) => {
|
|
|
28
27
|
return { all, messages };
|
|
29
28
|
};
|
|
30
29
|
// The hub's rooms, each with how many of its sessions on this machine are active.
|
|
31
|
-
export const roomsActive = (H
|
|
30
|
+
export const roomsActive = (H) => {
|
|
32
31
|
const ss = allSessions().filter(s => s.hub === H.name && liveness(s) === 'active');
|
|
33
|
-
return rooms(H
|
|
32
|
+
return rooms(H).map(v => ({ ...v, active: ss.filter(s => s.genesis === v.genesis).length }));
|
|
34
33
|
};
|
|
35
34
|
export const sessions = () => {
|
|
36
35
|
hubPorts().state.ensure();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// host:
|
|
2
|
-
//
|
|
1
|
+
// host: what the operating system says about this machine's processes, and a detached start, apart from any operating
|
|
2
|
+
// system or agent tool. ringi starts one process of its own (the pulse); agents' processes are their tools'.
|
|
3
3
|
export {};
|
|
4
4
|
//# sourceMappingURL=host.js.map
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// agent: what an agent does in its room as itself: read its inbox (marking what it read), take a piece of work, show
|
|
2
|
-
// who it is,
|
|
3
|
-
import { HIDDEN,
|
|
2
|
+
// who it is, wait.
|
|
3
|
+
import { HIDDEN, start, withSizes } from '../../hub/rooms/index.js';
|
|
4
4
|
import * as Signals from '../../hub/signals/write.js';
|
|
5
5
|
import { fail } from '../../../lib/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { link, lookup, unread } from '../../hub/protocol/index.js';
|
|
7
7
|
import { linked, loadRoomUnlocked, loadState, readerLock, saveState } from '../../hub/history/index.js';
|
|
8
|
-
import { isRef } from '../../hub/usecases/room.js';
|
|
9
8
|
export { wait } from './wait.js';
|
|
10
9
|
export { bound } from './index.js';
|
|
11
|
-
export { watch } from '../turns/index.js';
|
|
12
10
|
// Whether the agent's room has its quorum and started its work; a room that has not is not read.
|
|
13
11
|
export const started = ({ R }) => start(R);
|
|
14
12
|
export const inbox = ({ R, me }, { peek = false, limit = 20 } = {}) => {
|
|
@@ -33,22 +31,4 @@ export const take = ({ R, me }, ref) => {
|
|
|
33
31
|
export const whoami = ({ s, R }) => ({
|
|
34
32
|
session: s ?? fail('agent identity needs a joined session'), profile: R.profile.name, members: Object.keys(R.members),
|
|
35
33
|
});
|
|
36
|
-
export const card = ({ R, me }, text) => {
|
|
37
|
-
const message = compose(text, [['To', '*'], ['Kind', 'card']]);
|
|
38
|
-
checkOutgoing(message, isRef(R));
|
|
39
|
-
return append(R, me, message);
|
|
40
|
-
};
|
|
41
|
-
export const ping = ({ R, me }, { to, text = 'ping: still working', next = 600, re }) => {
|
|
42
|
-
if (!ID_RE.test(to))
|
|
43
|
-
fail(`bad recipient ${to}`);
|
|
44
|
-
if (!Number.isFinite(next) || next <= 0)
|
|
45
|
-
fail('ping next must be positive seconds');
|
|
46
|
-
const { all } = linked(R, me);
|
|
47
|
-
const mine = [...all.values()].filter(m => m.from === me && m.kind === 'ping' && m.to.includes(to)).at(-1);
|
|
48
|
-
const pong = mine && [...all.values()].find(m => m.kind === 'pong' && m.from === to && m.reId === mine.id);
|
|
49
|
-
const parent = re ? lookup(all, re) : null;
|
|
50
|
-
const message = compose(text, [['To', to], ['Re', parent?.id], ['Kind', 'ping'], ['Next', String(next)], ['Hop', parent && String(parent.hopEff + 1)]]);
|
|
51
|
-
checkOutgoing(message, isRef(R));
|
|
52
|
-
return { id: append(R, me, message), to, previous: mine ? { id: mine.id, answeredAt: pong?.time ?? null } : null };
|
|
53
|
-
};
|
|
54
34
|
//# sourceMappingURL=agent.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// binding: the seat a chat holds, checked on every agent command: joined, not stopped, its room alive and the same
|
|
2
2
|
// room, still a member; a chat whose room is gone leaves it on its own.
|
|
3
3
|
import { locked } from '../../hub/ports/index.js';
|
|
4
|
-
import { openHub, openRoom
|
|
4
|
+
import { openHub, openRoom } from '../../hub/rooms/index.js';
|
|
5
5
|
import { fail, stamp } from '../../../lib/index.js';
|
|
6
6
|
import { dropSession, readSession, saveSession, whoAmI } from './index.js';
|
|
7
7
|
// The binding, checked on every agent command: joined, not stopped, room alive and the same room, still a member.
|
|
@@ -22,14 +22,12 @@ export const bound = () => {
|
|
|
22
22
|
};
|
|
23
23
|
const checkRoomIdentity = (me, s, H, R) => {
|
|
24
24
|
if (!R || R.genesis !== s.genesis) {
|
|
25
|
-
// The room's branch is gone (deleted,
|
|
26
|
-
//
|
|
25
|
+
// The room's branch is gone (deleted, or deleted and made anew): the session leaves it on its own. A hub that
|
|
26
|
+
// cannot be reached fails above and leaves nothing.
|
|
27
27
|
locked('sessions', () => { if (readSession(me.handle)?.genesis === s.genesis)
|
|
28
28
|
dropSession(me.handle); });
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
: R ? `room ${s.room} was deleted and created again; this session left the old room. The operator joins it anew`
|
|
32
|
-
: `room ${s.room} is gone from hub ${s.hub}; this session left it`);
|
|
29
|
+
fail(R ? `room ${s.room} was deleted and created again; this session left the old room. The operator joins it anew`
|
|
30
|
+
: `room ${s.room} is gone from hub ${s.hub}; this session left it`);
|
|
33
31
|
}
|
|
34
32
|
};
|
|
35
33
|
const renewSession = (me, s) => {
|
|
@@ -43,15 +43,11 @@ export const saveSession = (s) => hubPorts().state.write(sessionKey(s.handle), s
|
|
|
43
43
|
export const dropSession = (handle) => hubPorts().state.remove(sessionKey(handle));
|
|
44
44
|
export const pidAlive = (pid) => agentPorts().host.alive(pid);
|
|
45
45
|
// active: in use within the lease by a living process; stopped: paused by the operator; gone: neither. A gone or
|
|
46
|
-
// stopped binding keeps its seat and scope.
|
|
47
|
-
// lease.
|
|
46
|
+
// stopped binding keeps its seat and scope.
|
|
48
47
|
export const liveness = (s) => s.state === 'stopped' ? 'stopped'
|
|
49
|
-
: s.
|
|
50
|
-
: Date.now() - Date.parse(s.lastSeen) < LEASE && (!s.pid || pidAlive(s.pid)) ? 'active' : 'gone';
|
|
48
|
+
: Date.now() - Date.parse(s.lastSeen) < LEASE && (!s.pid || pidAlive(s.pid)) ? 'active' : 'gone';
|
|
51
49
|
export const seatHolder = (hub, genesis, agent, except) => allSessions().find(s => s.hub === hub && s.genesis === genesis && s.agent === agent && s.handle !== except);
|
|
52
|
-
|
|
53
|
-
export const ownSession = (handle) => readSession(handle)
|
|
54
|
-
?? allSessions().find(s => s.product === 'watch' && s.takenFrom === handle) ?? null;
|
|
50
|
+
export const ownSession = (handle) => readSession(handle);
|
|
55
51
|
export const parseSelector = (sel) => {
|
|
56
52
|
const m = /^(?:([a-z0-9._-]+):)?([^/]+)\/([^/]+)$/.exec(sel ?? '');
|
|
57
53
|
return m ? { hub: m[1] ?? null, room: m[2], agent: m[3] } : fail('a session is named <room>/<agent> or <hub>:<room>/<agent>');
|
|
@@ -3,15 +3,13 @@ import { locked } from '../../hub/ports/index.js';
|
|
|
3
3
|
import { OPERATOR, controlChange, openHub, room as roomOf } from '../../hub/rooms/index.js';
|
|
4
4
|
import { fail, note, stamp } from '../../../lib/index.js';
|
|
5
5
|
import { AGENT_RE } from '../../hub/protocol/index.js';
|
|
6
|
-
import { dropSession, liveness, ownSession,
|
|
6
|
+
import { dropSession, liveness, ownSession, readSession, saveSession, seatHolder, whoAmI } from './index.js';
|
|
7
7
|
export const join = ({ hub, room, agent: id, scope }) => {
|
|
8
8
|
const me = whoAmI();
|
|
9
9
|
const agent = id ?? me.product ?? fail('--as <id> is required: this session\'s product is unknown');
|
|
10
10
|
if (!AGENT_RE.test(agent) || agent === OPERATOR)
|
|
11
11
|
fail(`bad agent id ${agent}: lowercase letters, digits, - and _, no dot`);
|
|
12
12
|
const H = openHub(hub), R = roomOf(H, room);
|
|
13
|
-
if (R.archived)
|
|
14
|
-
fail(`${room} is archived`);
|
|
15
13
|
if (H.signed) {
|
|
16
14
|
if (!H.store.joinKey)
|
|
17
15
|
fail(`hub ${H.name}: its store cannot make keys`);
|
|
@@ -39,8 +37,6 @@ const previousSession = ({ me, H, R, agent, room }) => {
|
|
|
39
37
|
if (holder)
|
|
40
38
|
fail(`${room}/${agent} is held by another session (${holder.handle}, ${liveness(holder)}). A seat ends only with leave, or the operator's takeover: "continue ${room}/${agent}" in this chat`);
|
|
41
39
|
const mine = ownSession(me.handle);
|
|
42
|
-
if (mine?.product === 'watch' && mine.pid && pidAlive(mine.pid))
|
|
43
|
-
fail('this chat handed its seat to a live watcher; stop or leave it before joining another room');
|
|
44
40
|
if (mine && mine.handle !== me.handle)
|
|
45
41
|
dropSession(mine.handle);
|
|
46
42
|
return mine;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// resume: a chat continues its own seat after a restart, or takes over one the operator names.
|
|
2
2
|
import { hubPorts, locked } from '../../hub/ports/index.js';
|
|
3
3
|
import { fail, note, stamp } from '../../../lib/index.js';
|
|
4
|
-
import { allSessions, dropSession, liveness, ownSession, parseSelector,
|
|
4
|
+
import { allSessions, dropSession, liveness, ownSession, parseSelector, saveSession, whoAmI } from './index.js';
|
|
5
5
|
export const resume = (selector) => {
|
|
6
6
|
const me = whoAmI();
|
|
7
7
|
hubPorts().state.ensure();
|
|
@@ -37,7 +37,7 @@ const ownTarget = (me) => {
|
|
|
37
37
|
};
|
|
38
38
|
const takeOver = (me, target) => {
|
|
39
39
|
if (target.handle !== me.handle) {
|
|
40
|
-
if (liveness(target) === 'active'
|
|
40
|
+
if (liveness(target) === 'active')
|
|
41
41
|
fail(`${target.room}/${target.agent} is held by a live session (${target.handle}); it is not taken over`);
|
|
42
42
|
dropSession(target.handle);
|
|
43
43
|
note(`took over ${target.hub}/${target.room}/${target.agent} from ${target.handle}`);
|
|
@@ -46,8 +46,7 @@ const takeOver = (me, target) => {
|
|
|
46
46
|
const resumed = (me, target) => {
|
|
47
47
|
const session = {
|
|
48
48
|
...target, handle: me.handle, product: target.handle === me.handle ? target.product : me.product, state: 'active',
|
|
49
|
-
since: target.handle === me.handle ? target.since : stamp(), lastSeen: stamp(), pid: me.pid ?? (target.handle === me.handle ? target.pid : null)
|
|
50
|
-
takenFrom: target.handle === me.handle ? target.takenFrom : undefined
|
|
49
|
+
since: target.handle === me.handle ? target.since : stamp(), lastSeen: stamp(), pid: me.pid ?? (target.handle === me.handle ? target.pid : null)
|
|
51
50
|
};
|
|
52
51
|
return session;
|
|
53
52
|
};
|