@gavin-hu/my-pi-agent 0.17.0 → 0.19.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 +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The review's event streams: which pages are listening, what they are told, and
|
|
3
|
+
* when a closed tab ends the review.
|
|
4
|
+
*
|
|
5
|
+
* A frame carries only a kind, so the registry is the only thing that knows who
|
|
6
|
+
* is connected. It is keyed by sink — a page may hold more than one stream — and
|
|
7
|
+
* records the caller it belongs to, so an owner-only frame, or one revoked link,
|
|
8
|
+
* can be addressed without the caller reaching into the map. Liveness is the
|
|
9
|
+
* sink's own (a write after the end is a no-op), so the registry never asks the
|
|
10
|
+
* socket whether it is still writable.
|
|
11
|
+
*
|
|
12
|
+
* The closed-tab rule lives here because it is a fact about streams: a reviewer's
|
|
13
|
+
* tab opening or closing changes nothing, while the owner's own page going away
|
|
14
|
+
* ends the review once the grace a reload needs has passed. The grace is a race
|
|
15
|
+
* against that reload, so it takes the timer as a seam a test can drive by hand.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { EventSink } from "../../lib/http/index.ts";
|
|
19
|
+
import type { ArmTimer } from "../../lib/timer.ts";
|
|
20
|
+
import type { ReviewFrame, ReviewRole } from "./types.ts";
|
|
21
|
+
|
|
22
|
+
/** Who a stream belongs to: the role it may act as, and the token that names it. */
|
|
23
|
+
export interface StreamRecord {
|
|
24
|
+
role: ReviewRole;
|
|
25
|
+
token: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Which streams a frame is for; absent means every open page. */
|
|
29
|
+
export interface StreamFilter {
|
|
30
|
+
role?: ReviewRole;
|
|
31
|
+
token?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** What the registry needs from the session whose lifetime it reports on. */
|
|
35
|
+
export interface StreamRegistryDeps {
|
|
36
|
+
/** The review is over because its owner's page went away. */
|
|
37
|
+
onAbandoned: () => void;
|
|
38
|
+
/** How long a closed owner tab waits for a reload before it counts as gone. */
|
|
39
|
+
graceMs: number;
|
|
40
|
+
/** The timer the grace is armed on. */
|
|
41
|
+
schedule: ArmTimer;
|
|
42
|
+
/** A settled review arms nothing, so a stream closing during teardown is silent. */
|
|
43
|
+
settled: () => boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The stream set one running review holds. */
|
|
47
|
+
export interface StreamRegistry {
|
|
48
|
+
/** Register a stream, and stand down a pending closed-tab grace. */
|
|
49
|
+
add(sink: EventSink, record: StreamRecord): void;
|
|
50
|
+
/** Forget a stream, arming the grace when it was the owner's last one. */
|
|
51
|
+
remove(sink: EventSink): void;
|
|
52
|
+
/** Tell the open pages something changed. */
|
|
53
|
+
broadcast(kind: ReviewFrame, only?: StreamFilter): void;
|
|
54
|
+
/** Send one last frame and end the stream with it. */
|
|
55
|
+
end(kind: ReviewFrame, only?: StreamFilter): void;
|
|
56
|
+
/** Drop every stream without ending it, after they have all been ended. */
|
|
57
|
+
clear(): void;
|
|
58
|
+
/** Stand down the closed-tab grace, because the review is ending anyway. */
|
|
59
|
+
cancelDrop(): void;
|
|
60
|
+
/** How many owner streams are open. */
|
|
61
|
+
ownerOpen(): number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createStreams(deps: StreamRegistryDeps): StreamRegistry {
|
|
65
|
+
const clients = new Map<EventSink, StreamRecord>();
|
|
66
|
+
let sawOwner = false;
|
|
67
|
+
let cancelDrop: (() => void) | undefined;
|
|
68
|
+
|
|
69
|
+
const ownerOpen = (): number => {
|
|
70
|
+
let open = 0;
|
|
71
|
+
for (const record of clients.values()) if (record.role === "owner") open += 1;
|
|
72
|
+
return open;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const cancel = (): void => {
|
|
76
|
+
cancelDrop?.();
|
|
77
|
+
cancelDrop = undefined;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const scheduleDrop = (): void => {
|
|
81
|
+
if (ownerOpen() > 0 || !sawOwner || deps.settled() || cancelDrop) return;
|
|
82
|
+
cancelDrop = deps.schedule(deps.graceMs, () => {
|
|
83
|
+
cancelDrop = undefined;
|
|
84
|
+
if (ownerOpen() === 0) deps.onAbandoned();
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const matches = (record: StreamRecord, only: StreamFilter | undefined): boolean => {
|
|
89
|
+
if (only === undefined) return true;
|
|
90
|
+
if (only.role !== undefined && record.role !== only.role) return false;
|
|
91
|
+
if (only.token !== undefined && record.token !== only.token) return false;
|
|
92
|
+
return true;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
add(sink, record) {
|
|
97
|
+
clients.set(sink, record);
|
|
98
|
+
if (record.role === "owner") {
|
|
99
|
+
sawOwner = true;
|
|
100
|
+
cancel();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
remove(sink) {
|
|
104
|
+
const record = clients.get(sink);
|
|
105
|
+
clients.delete(sink);
|
|
106
|
+
if (record?.role === "owner") scheduleDrop();
|
|
107
|
+
},
|
|
108
|
+
broadcast(kind, only) {
|
|
109
|
+
for (const [client, record] of clients) {
|
|
110
|
+
if (!matches(record, only)) continue;
|
|
111
|
+
client.write(`data: ${kind}\n\n`);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
end(kind, only) {
|
|
115
|
+
for (const [client, record] of clients) {
|
|
116
|
+
if (!matches(record, only)) continue;
|
|
117
|
+
client.end(`data: ${kind}\n\n`);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
clear: () => clients.clear(),
|
|
121
|
+
cancelDrop: cancel,
|
|
122
|
+
ownerOpen,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The token table: who a review's bearer token names.
|
|
3
|
+
*
|
|
4
|
+
* A review has two roles and, once shared, one token per reviewer link. The role
|
|
5
|
+
* travels with the token, and a reviewer's name is added to it the first time that
|
|
6
|
+
* link writes. Everything that decides "may this caller approve, save, or close"
|
|
7
|
+
* reads this table, so the rules live here rather than in the session that mints
|
|
8
|
+
* them:
|
|
9
|
+
*
|
|
10
|
+
* - two roles may never share one token, or a reviewer token could displace the
|
|
11
|
+
* owner's and hand a reviewer the approval route;
|
|
12
|
+
* - a removed token is forgotten rather than tombstoned, so a re-shared link may
|
|
13
|
+
* claim the name the revoked one released;
|
|
14
|
+
* - a name already bound elsewhere is visible, because the marker in the plan
|
|
15
|
+
* carries only the name, so a shared name is a shared marker.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { mintToken } from "../../lib/http/index.ts";
|
|
19
|
+
import type { ReviewCaller, ReviewRole } from "./types.ts";
|
|
20
|
+
|
|
21
|
+
/** The token → caller table one running review holds. */
|
|
22
|
+
export interface TokenTable {
|
|
23
|
+
/** Mint a token for `role`, replacing a colliding one until it is unique. */
|
|
24
|
+
mint(role: ReviewRole): string;
|
|
25
|
+
/** The caller a token names, or undefined when it is not one of this review's. */
|
|
26
|
+
get(token: string): ReviewCaller | undefined;
|
|
27
|
+
/** Forget a token, so the link that used it stops answering. */
|
|
28
|
+
revoke(token: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* The names every other live link has bound, so one review cannot hold two
|
|
31
|
+
* reviewers called `alice`: the marker carries only the name, so a shared name
|
|
32
|
+
* is a shared marker.
|
|
33
|
+
*/
|
|
34
|
+
namesExcept(self: ReviewCaller): string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** How many times a colliding generator is retried before the table names it itself. */
|
|
38
|
+
const COLLISION_ATTEMPTS = 8;
|
|
39
|
+
|
|
40
|
+
/** A 128-bit token; the one credential a reviewer holds. */
|
|
41
|
+
const defaultToken = (): string => mintToken();
|
|
42
|
+
|
|
43
|
+
/** Build the table for one review. `mint` is the generator a test replaces. */
|
|
44
|
+
export function createTokens(mint: () => string = defaultToken): TokenTable {
|
|
45
|
+
const callers = new Map<string, ReviewCaller>();
|
|
46
|
+
return {
|
|
47
|
+
mint(role) {
|
|
48
|
+
// A role is keyed by its token, so two roles may never share one: a
|
|
49
|
+
// generator that repeats would otherwise let a reviewer token displace the
|
|
50
|
+
// owner's and hand a reviewer the approval route.
|
|
51
|
+
let value = mint();
|
|
52
|
+
for (let attempt = 0; callers.has(value) && attempt < COLLISION_ATTEMPTS; attempt++) value = mint();
|
|
53
|
+
if (callers.has(value)) value = `${value}-${callers.size}`;
|
|
54
|
+
callers.set(value, { role });
|
|
55
|
+
return value;
|
|
56
|
+
},
|
|
57
|
+
get: (token) => callers.get(token),
|
|
58
|
+
revoke: (token) => {
|
|
59
|
+
callers.delete(token);
|
|
60
|
+
},
|
|
61
|
+
namesExcept: (self) => [...callers.values()].flatMap((entry) => (entry !== self && entry.name ? [entry.name] : [])),
|
|
62
|
+
};
|
|
63
|
+
}
|