opencode-courier 0.2.3 → 0.4.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.
Files changed (143) hide show
  1. package/README.md +198 -193
  2. package/dist/index.d.ts +4 -8
  3. package/dist/index.js +66 -85
  4. package/dist/kernel/protocol.d.ts +20 -0
  5. package/dist/kernel/protocol.js +36 -0
  6. package/dist/kernel/tool.d.ts +5 -0
  7. package/dist/kernel/tool.js +5 -0
  8. package/dist/person/events.d.ts +23 -0
  9. package/dist/person/events.js +113 -0
  10. package/dist/person/form.d.ts +26 -0
  11. package/dist/person/form.js +64 -0
  12. package/dist/person/index.d.ts +17 -0
  13. package/dist/person/index.js +5 -0
  14. package/dist/{relay.d.ts → person/permission.d.ts} +29 -25
  15. package/dist/{relay.js → person/permission.js} +11 -9
  16. package/dist/person/ports.d.ts +58 -0
  17. package/dist/person/ports.js +2 -0
  18. package/dist/{question → person/question}/answer.d.ts +33 -4
  19. package/dist/{question → person/question}/answer.js +49 -17
  20. package/dist/person/question/index.d.ts +7 -0
  21. package/dist/{question → person/question}/index.js +3 -3
  22. package/dist/person/question/lifecycle.d.ts +43 -0
  23. package/dist/{question → person/question}/lifecycle.js +10 -19
  24. package/dist/{question → person/question}/pure.d.ts +4 -6
  25. package/dist/{question → person/question}/pure.js +3 -4
  26. package/dist/{question → person/question}/relay.d.ts +1 -1
  27. package/dist/{question → person/question}/relay.js +27 -35
  28. package/dist/{question → person/question}/runtime.d.ts +2 -3
  29. package/dist/{question → person/question}/runtime.js +2 -3
  30. package/dist/{question → person/question}/shared.d.ts +13 -3
  31. package/dist/person/question/shared.js +52 -0
  32. package/dist/person/question/types.d.ts +121 -0
  33. package/dist/person/question/types.js +1 -0
  34. package/dist/person/rpc-handlers.d.ts +160 -0
  35. package/dist/person/rpc-handlers.js +62 -0
  36. package/dist/person/rpc.d.ts +227 -0
  37. package/dist/person/rpc.js +70 -0
  38. package/dist/person/text.d.ts +68 -0
  39. package/dist/{notices.js → person/text.js} +53 -190
  40. package/dist/person/tools.d.ts +20 -0
  41. package/dist/person/tools.js +55 -0
  42. package/dist/person/tui.d.ts +46 -0
  43. package/dist/platform/bounded.d.ts +12 -0
  44. package/dist/{bounded.js → platform/bounded.js} +6 -4
  45. package/dist/platform/events.d.ts +34 -0
  46. package/dist/platform/events.js +121 -0
  47. package/dist/platform/hub.d.ts +195 -0
  48. package/dist/{hub.js → platform/hub.js} +78 -119
  49. package/dist/platform/json.d.ts +8 -0
  50. package/dist/platform/json.js +15 -0
  51. package/dist/platform/version.d.ts +16 -0
  52. package/dist/{version.js → platform/version.js} +5 -11
  53. package/dist/rpc.d.ts +2 -0
  54. package/dist/rpc.js +3 -0
  55. package/dist/scheduling/index.d.ts +6 -0
  56. package/dist/scheduling/index.js +5 -0
  57. package/dist/{later.d.ts → scheduling/later.d.ts} +13 -9
  58. package/dist/{later.js → scheduling/later.js} +18 -9
  59. package/dist/scheduling/text.d.ts +6 -0
  60. package/dist/scheduling/text.js +10 -0
  61. package/dist/scheduling/tools.d.ts +14 -0
  62. package/dist/scheduling/tools.js +48 -0
  63. package/dist/skill.d.ts +17 -0
  64. package/dist/skill.js +62 -0
  65. package/dist/{cleanup.d.ts → tree/cleanup.d.ts} +5 -26
  66. package/dist/{cleanup.js → tree/cleanup.js} +20 -15
  67. package/dist/tree/context.d.ts +15 -0
  68. package/dist/tree/context.js +34 -0
  69. package/dist/tree/domain.d.ts +101 -0
  70. package/dist/tree/domain.js +68 -0
  71. package/dist/tree/events.d.ts +71 -0
  72. package/dist/tree/events.js +204 -0
  73. package/dist/tree/forget.d.ts +24 -0
  74. package/dist/tree/forget.js +59 -0
  75. package/dist/tree/group.d.ts +82 -0
  76. package/dist/tree/group.js +203 -0
  77. package/dist/tree/index.d.ts +10 -0
  78. package/dist/tree/index.js +11 -0
  79. package/dist/tree/limits.d.ts +10 -0
  80. package/dist/tree/limits.js +60 -0
  81. package/dist/tree/panel.d.ts +13 -0
  82. package/dist/tree/panel.js +75 -0
  83. package/dist/tree/ports.d.ts +276 -0
  84. package/dist/tree/ports.js +1 -0
  85. package/dist/tree/report.d.ts +23 -0
  86. package/dist/tree/report.js +33 -0
  87. package/dist/tree/roster.d.ts +27 -0
  88. package/dist/tree/roster.js +62 -0
  89. package/dist/tree/rpc-handlers.d.ts +201 -0
  90. package/dist/tree/rpc-handlers.js +20 -0
  91. package/dist/tree/rpc.d.ts +344 -0
  92. package/dist/tree/rpc.js +83 -0
  93. package/dist/tree/send.d.ts +14 -0
  94. package/dist/tree/send.js +99 -0
  95. package/dist/tree/spawn.d.ts +21 -0
  96. package/dist/tree/spawn.js +173 -0
  97. package/dist/tree/status.d.ts +128 -0
  98. package/dist/tree/status.js +62 -0
  99. package/dist/tree/stop.d.ts +33 -0
  100. package/dist/tree/stop.js +116 -0
  101. package/dist/tree/store.d.ts +24 -0
  102. package/dist/tree/store.js +264 -0
  103. package/dist/tree/subtree.d.ts +101 -0
  104. package/dist/tree/subtree.js +60 -0
  105. package/dist/tree/summary.d.ts +7 -0
  106. package/dist/tree/summary.js +35 -0
  107. package/dist/tree/text.d.ts +77 -0
  108. package/dist/tree/text.js +287 -0
  109. package/dist/tree/tools.d.ts +54 -0
  110. package/dist/tree/tools.js +167 -0
  111. package/dist/tree/tui.d.ts +53 -0
  112. package/dist/tui.d.ts +3 -0
  113. package/dist/tui.js +1219 -0
  114. package/dist/webhook/index.d.ts +5 -0
  115. package/dist/webhook/index.js +3 -0
  116. package/dist/{webhook.d.ts → webhook/receiver.d.ts} +25 -36
  117. package/dist/{webhook.js → webhook/receiver.js} +66 -67
  118. package/dist/webhook/subscriptions.d.ts +29 -0
  119. package/dist/webhook/subscriptions.js +52 -0
  120. package/dist/webhook/text.d.ts +23 -0
  121. package/dist/webhook/text.js +82 -0
  122. package/dist/webhook/tools.d.ts +19 -0
  123. package/dist/webhook/tools.js +46 -0
  124. package/package.json +18 -7
  125. package/dist/bounded.d.ts +0 -10
  126. package/dist/courier.d.ts +0 -95
  127. package/dist/courier.js +0 -131
  128. package/dist/hub.d.ts +0 -325
  129. package/dist/json.d.ts +0 -4
  130. package/dist/json.js +0 -4
  131. package/dist/notices.d.ts +0 -246
  132. package/dist/question/index.d.ts +0 -6
  133. package/dist/question/lifecycle.d.ts +0 -52
  134. package/dist/question/shared.js +0 -24
  135. package/dist/roster.d.ts +0 -81
  136. package/dist/roster.js +0 -175
  137. package/dist/tools.d.ts +0 -74
  138. package/dist/tools.js +0 -228
  139. package/dist/version.d.ts +0 -22
  140. package/dist/watch.d.ts +0 -61
  141. package/dist/watch.js +0 -289
  142. /package/dist/{storage.d.ts → platform/storage.d.ts} +0 -0
  143. /package/dist/{storage.js → platform/storage.js} +0 -0
@@ -0,0 +1,52 @@
1
+ import { addBounded } from "../../platform/bounded.js";
2
+ import { hub, memberAt, sharedState } from "../../platform/hub.js";
3
+ /** OpenCode's own question tool, which the relay wraps. */
4
+ export const QUESTION_TOOL = "question";
5
+ export const PREFIX = "question/";
6
+ /** At most this many questions are kept in storage; the oldest go first. */
7
+ export const MAX_STORED = 100;
8
+ export const storedOf = ({ directory: _directory, call: _call, link: _link, relaying: _relaying, settling: _settling, ...asked }) => asked;
9
+ export const isPassing = (requestID) => shared.passing.has(requestID) || shared.answered.has(requestID);
10
+ /**
11
+ * The relay's state in `registry`, the hub's `questions`, with the fields it lacks made: field by
12
+ * field, since a copy before the hub may have made it without the fields added later.
13
+ */
14
+ export function questionState(registry) {
15
+ const state = sharedState("questions", () => ({}), registry);
16
+ state.questions ??= new Map();
17
+ state.noticed ??= new Set();
18
+ state.shown ??= new Map();
19
+ state.passing ??= new Map();
20
+ state.answered ??= new Set();
21
+ state.loaded ??= new Set();
22
+ state.following ??= 0;
23
+ state.followed ??= false;
24
+ state.shutdowns ??= new Map();
25
+ state.closingWaiters ??= new Set();
26
+ return state;
27
+ }
28
+ // Shared by every plugin instance in the process, since a child and the session it asks can be in
29
+ // different locations.
30
+ export const shared = questionState();
31
+ /** A member's question ports: every hub-v2 member's `questions` are its instance's, whichever copy joined it. */
32
+ const portsOf = (member) => member?.questions;
33
+ /**
34
+ * The ports of an instance still loaded, if any: a member of this copy's hub, or else one that a
35
+ * copy of another hub version, or a release before the hub, keeps in `loaded`.
36
+ */
37
+ export const anyLoaded = () => portsOf(hub.members.values().next().value) ?? shared.loaded.values().next().value;
38
+ /**
39
+ * The question ports of the member that just joined at `location`, while it is loaded: resolved once,
40
+ * so a later instance at the same location does not take its place while this one is still loaded.
41
+ */
42
+ export function portsAt(location) {
43
+ const own = memberAt(location);
44
+ return () => (own && hub.members.has(own) ? portsOf(own) : undefined);
45
+ }
46
+ export const keyOf = (requestID) => `${PREFIX}${requestID}`;
47
+ /** Whether a request id is a question's rather than a permission request's. */
48
+ export const isQuestion = (requestID) => requestID.startsWith("question_");
49
+ /** How many noticed and answered questions are remembered. */
50
+ const REMEMBERED = 1_000;
51
+ /** Adds to one of the shared bounded sets. */
52
+ export const claim = (set, value) => addBounded(set, value, REMEMBERED);
@@ -0,0 +1,121 @@
1
+ import type { Plugin } from "@opencode/plugin";
2
+ import type { Storage } from "../../platform/storage.js";
3
+ type Context = Plugin.Context;
4
+ /** One question of OpenCode's question tool, as the asking model wrote it. */
5
+ export interface Prompt {
6
+ readonly question: string;
7
+ readonly header: string;
8
+ readonly options: ReadonlyArray<{
9
+ readonly label: string;
10
+ readonly description: string;
11
+ }>;
12
+ readonly multiple?: boolean;
13
+ }
14
+ /** The answers to a question call, as the question tool returns them: one list per question. */
15
+ export type Answers = string[][];
16
+ /** A question a spawned session asked with its question tool, stored until it is answered. */
17
+ export interface Asked {
18
+ readonly requestID: string;
19
+ readonly sessionID: string;
20
+ /** The session told, at the top of the sessions started with courier_spawn; it alone answers. */
21
+ readonly top: string;
22
+ readonly title: string;
23
+ /** The session that started the asking one, when that is not the top session itself. */
24
+ readonly startedBy?: string;
25
+ readonly questions: ReadonlyArray<Prompt>;
26
+ readonly askedAt: number;
27
+ /** A native child's, whose form OpenCode shows the person in the top session's own view; not linked while shown. */
28
+ readonly native?: true;
29
+ }
30
+ /**
31
+ * How a question was settled without the top session: in the asking session (or, `via` "inbox", by the
32
+ * person in the TUI's asks inbox), or not at all.
33
+ */
34
+ export type Elsewhere = {
35
+ readonly by: "child";
36
+ readonly answers: Answers;
37
+ readonly via?: "inbox";
38
+ } | {
39
+ readonly by: "dismissed";
40
+ } | {
41
+ readonly by: "failed";
42
+ readonly error: string;
43
+ };
44
+ /** What the top session's answer does to a call that waits: answers it, or dismisses it. */
45
+ export type Outcome = {
46
+ readonly answers: Answers;
47
+ } | {
48
+ readonly dismissed: true;
49
+ };
50
+ /**
51
+ * Why a linked call is withdrawn: its question was settled elsewhere, or the top session's outcome
52
+ * reached it another way (courier_answer, or a dismissal through an earlier call linked to it).
53
+ */
54
+ export type Withdrawal = Elsewhere | {
55
+ readonly by: "top";
56
+ readonly outcome: Outcome;
57
+ };
58
+ /**
59
+ * A question this process knows: its call waits (`call`), or was cut off and is answered by message.
60
+ * `link` withdraws the top session's call passing the answer on, once settled without that call.
61
+ */
62
+ export interface Question extends Asked {
63
+ /** The directory of the location the call runs in, whose shutdown withdraws its form. */
64
+ directory?: string;
65
+ /** Hands the waiting call the top session's outcome; true once the call has ended with it. */
66
+ call?: (outcome: Outcome) => Promise<boolean>;
67
+ link?: (how: Withdrawal) => void;
68
+ /** Storing the question and telling the top session, while that is under way. */
69
+ relaying?: Promise<boolean>;
70
+ /** Working out what became of it once its call ended without the top session's answer. */
71
+ settling?: Promise<void>;
72
+ }
73
+ /**
74
+ * The relay's timings, carried by its ports: the closing grace before a cut-off question is told, the
75
+ * wait for an answer still being passed on, the wait for a notice to go out, and the dismissal hold.
76
+ */
77
+ export interface QuestionTiming {
78
+ readonly closingGraceMs: number;
79
+ readonly passingWaitMs: number;
80
+ readonly relayWaitMs: number;
81
+ readonly dismissalGraceMs: number;
82
+ }
83
+ export interface QuestionPorts {
84
+ readonly storage: Storage;
85
+ readonly session: Pick<Context["session"], "synthetic">;
86
+ /** The directory of the instance's location. */
87
+ readonly directory: string;
88
+ /** The clock, in epoch milliseconds, as OpenCode's location shutdowns are recorded. */
89
+ readonly now: () => number;
90
+ readonly timing: QuestionTiming;
91
+ readonly newID: () => string;
92
+ readonly log: (message: string) => void;
93
+ }
94
+ /**
95
+ * The question relay's state, shared by every instance, as a child and the session it asks can be in
96
+ * different locations: open questions, cut-off ones already told, and calls waiting for their form.
97
+ */
98
+ export interface QuestionState {
99
+ readonly questions: Map<string, Question>;
100
+ readonly noticed: Set<string>;
101
+ readonly shown: Map<string, () => void>;
102
+ /** The questions whose answer is being passed on, each until that is done, and those whose answer went out. */
103
+ readonly passing: Map<string, Promise<unknown>>;
104
+ readonly answered: Set<string>;
105
+ /**
106
+ * The ports of every loaded instance of every copy. A release before the hub picks one here to tell
107
+ * a cut-off question through, so every copy fills it; this copy reads it only for another copy.
108
+ */
109
+ readonly loaded: Set<QuestionPorts>;
110
+ /** How many instances follow OpenCode's events now, and whether any has since the process started. */
111
+ following: number;
112
+ followed: boolean;
113
+ /**
114
+ * When OpenCode last reported each location shutting down (`location.shutdown`), by directory
115
+ * in epoch milliseconds by the clock of the watcher that saw it; under `""` when the event named no location.
116
+ */
117
+ readonly shutdowns: Map<string, number>;
118
+ /** Woken when a location shuts down or an instance unloads: dismissals held to see whether one follows. */
119
+ readonly closingWaiters: Set<() => void>;
120
+ }
121
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,160 @@
1
+ import type { Plugin } from "@opencode/plugin";
2
+ import { type Permissions } from "./permission.js";
3
+ import { type QuestionPorts } from "./question/index.js";
4
+ import { type Answered, type Asks } from "./rpc.js";
5
+ /** What the person's RPC reads and answers through: the relay's ports, which carry the storage, and every location's permissions. */
6
+ export interface PersonRpcPorts {
7
+ readonly questions: QuestionPorts;
8
+ readonly permissions: () => Iterable<Permissions>;
9
+ /** At most this many sessions of a tree are read, as courier_tree reads them. */
10
+ readonly maxTotal: number;
11
+ }
12
+ /**
13
+ * The permission requests and questions waiting in the courier tree a session is in, from the roster: read from the
14
+ * top of its lineage down, so each session of the tree sees the same inbox.
15
+ */
16
+ export declare function asks(ports: PersonRpcPorts, sessionID: string): Promise<Asks>;
17
+ /** Passes the person's answers to a question on, as courier_answer does; a refusal, such as answers that do not fit, comes back as `problem`. */
18
+ export declare function answer(ports: PersonRpcPorts, input: {
19
+ sessionID: string;
20
+ requestID: string;
21
+ answers: ReadonlyArray<ReadonlyArray<string>>;
22
+ }): Promise<Answered>;
23
+ /** Serves the person's RPC at the plugin's location; disposed with the plugin. */
24
+ export declare function registerPersonRpc(rpc: Pick<Plugin.Context["rpc"], "register">, ports: PersonRpcPorts): Promise<import("@opencode/plugin/promise/rpc").RpcRegistration<{
25
+ readonly id: "courier-person";
26
+ readonly methods: {
27
+ readonly asks: {
28
+ readonly input: import("effect/StandardSchema").StandardSchemaV1<{
29
+ readonly sessionID: string;
30
+ }, {
31
+ readonly sessionID: string;
32
+ }> & import("effect/Schema").Struct<{
33
+ readonly sessionID: import("effect/Schema").String;
34
+ }>;
35
+ readonly output: import("effect/StandardSchema").StandardSchemaV1<{
36
+ readonly questions: readonly {
37
+ readonly questions: readonly {
38
+ readonly options: readonly {
39
+ readonly label: string;
40
+ readonly description: string;
41
+ }[];
42
+ readonly question: string;
43
+ readonly header: string;
44
+ readonly multiple?: boolean | undefined;
45
+ }[];
46
+ readonly sessionID: string;
47
+ readonly requestID: string;
48
+ readonly stopped?: boolean | undefined;
49
+ }[];
50
+ readonly permissions: readonly {
51
+ readonly sessionID: string;
52
+ readonly resources: readonly string[];
53
+ readonly action: string;
54
+ readonly requestID: string;
55
+ readonly save?: readonly string[] | undefined;
56
+ }[];
57
+ readonly top: string;
58
+ readonly sessions: readonly {
59
+ readonly sessionID: string;
60
+ readonly title: string;
61
+ readonly parentID: string;
62
+ readonly depth: number;
63
+ readonly group?: string | undefined;
64
+ }[];
65
+ readonly truncated?: number | undefined;
66
+ }, {
67
+ readonly questions: readonly {
68
+ readonly questions: readonly {
69
+ readonly options: readonly {
70
+ readonly label: string;
71
+ readonly description: string;
72
+ }[];
73
+ readonly question: string;
74
+ readonly header: string;
75
+ readonly multiple?: boolean | undefined;
76
+ }[];
77
+ readonly sessionID: string;
78
+ readonly requestID: string;
79
+ readonly stopped?: boolean | undefined;
80
+ }[];
81
+ readonly permissions: readonly {
82
+ readonly sessionID: string;
83
+ readonly resources: readonly string[];
84
+ readonly action: string;
85
+ readonly requestID: string;
86
+ readonly save?: readonly string[] | undefined;
87
+ }[];
88
+ readonly top: string;
89
+ readonly sessions: readonly {
90
+ readonly sessionID: string;
91
+ readonly title: string;
92
+ readonly parentID: string;
93
+ readonly depth: number;
94
+ readonly group?: string | undefined;
95
+ }[];
96
+ readonly truncated?: number | undefined;
97
+ }> & import("effect/Schema").Struct<{
98
+ readonly top: import("effect/Schema").String;
99
+ readonly sessions: import("effect/Schema").$Array<import("effect/Schema").Struct<{
100
+ readonly sessionID: import("effect/Schema").String;
101
+ readonly title: import("effect/Schema").String;
102
+ readonly parentID: import("effect/Schema").String;
103
+ readonly depth: import("effect/Schema").Int;
104
+ readonly group: import("effect/Schema").optionalKey<import("effect/Schema").String>;
105
+ }>>;
106
+ readonly permissions: import("effect/Schema").$Array<import("effect/Schema").Struct<{
107
+ readonly sessionID: import("effect/Schema").String;
108
+ readonly requestID: import("effect/Schema").String;
109
+ readonly action: import("effect/Schema").String;
110
+ readonly resources: import("effect/Schema").$Array<import("effect/Schema").String>;
111
+ readonly save: import("effect/Schema").optionalKey<import("effect/Schema").$Array<import("effect/Schema").String>>;
112
+ }>>;
113
+ readonly questions: import("effect/Schema").$Array<import("effect/Schema").Struct<{
114
+ readonly sessionID: import("effect/Schema").String;
115
+ readonly requestID: import("effect/Schema").String;
116
+ readonly questions: import("effect/Schema").$Array<import("effect/Schema").Struct<{
117
+ readonly question: import("effect/Schema").String;
118
+ readonly header: import("effect/Schema").String;
119
+ readonly options: import("effect/Schema").$Array<import("effect/Schema").Struct<{
120
+ readonly label: import("effect/Schema").String;
121
+ readonly description: import("effect/Schema").String;
122
+ }>>;
123
+ readonly multiple: import("effect/Schema").optionalKey<import("effect/Schema").Boolean>;
124
+ }>>;
125
+ readonly stopped: import("effect/Schema").optionalKey<import("effect/Schema").Boolean>;
126
+ }>>;
127
+ readonly truncated: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
128
+ }>;
129
+ };
130
+ readonly answer: {
131
+ readonly input: import("effect/StandardSchema").StandardSchemaV1<{
132
+ readonly sessionID: string;
133
+ readonly requestID: string;
134
+ readonly answers: readonly (readonly string[])[];
135
+ }, {
136
+ readonly sessionID: string;
137
+ readonly requestID: string;
138
+ readonly answers: readonly (readonly string[])[];
139
+ }> & import("effect/Schema").Struct<{
140
+ readonly sessionID: import("effect/Schema").String;
141
+ readonly requestID: import("effect/Schema").String;
142
+ readonly answers: import("effect/Schema").$Array<import("effect/Schema").$Array<import("effect/Schema").String>>;
143
+ }>;
144
+ readonly output: import("effect/StandardSchema").StandardSchemaV1<{
145
+ readonly answered: boolean;
146
+ readonly by?: "message" | "result" | undefined;
147
+ readonly problem?: string | undefined;
148
+ }, {
149
+ readonly answered: boolean;
150
+ readonly by?: "message" | "result" | undefined;
151
+ readonly problem?: string | undefined;
152
+ }> & import("effect/Schema").Struct<{
153
+ readonly answered: import("effect/Schema").Boolean;
154
+ readonly by: import("effect/Schema").optionalKey<import("effect/Schema").Literals<readonly ["result", "message"]>>;
155
+ readonly problem: import("effect/Schema").optionalKey<import("effect/Schema").String>;
156
+ }>;
157
+ };
158
+ };
159
+ readonly events: {};
160
+ }>>;
@@ -0,0 +1,62 @@
1
+ import { lineage, subtree } from "../tree/index.js";
2
+ import { pendingOf } from "./permission.js";
3
+ import { answerForPerson, pendingQuestions } from "./question/index.js";
4
+ import { personRpc } from "./rpc.js";
5
+ /**
6
+ * The permission requests and questions waiting in the courier tree a session is in, from the roster: read from the
7
+ * top of its lineage down, so each session of the tree sees the same inbox.
8
+ */
9
+ export async function asks(ports, sessionID) {
10
+ const { storage } = ports.questions;
11
+ const top = (await lineage(storage, sessionID)).at(-1)?.parentID ?? sessionID;
12
+ const { nodes, truncated } = await subtree(storage, top, ports.maxTotal);
13
+ const waiting = await Promise.all(nodes.map(({ entry }) => Promise.all([
14
+ pendingOf(ports.permissions(), entry.sessionID).catch(() => []),
15
+ pendingQuestions(storage, entry.sessionID).catch(() => []),
16
+ ])));
17
+ return {
18
+ top,
19
+ sessions: nodes.map(({ entry, depth }) => ({
20
+ sessionID: entry.sessionID,
21
+ title: entry.title,
22
+ parentID: entry.parentID,
23
+ depth,
24
+ ...(entry.group ? { group: entry.group } : {}),
25
+ })),
26
+ permissions: nodes.flatMap(({ entry }, index) => waiting[index][0].flatMap((pending) => pending.type === "permission"
27
+ ? [
28
+ {
29
+ sessionID: entry.sessionID,
30
+ requestID: pending.requestID,
31
+ action: pending.action,
32
+ resources: pending.resources,
33
+ ...(pending.save ? { save: pending.save } : {}),
34
+ },
35
+ ]
36
+ : [])),
37
+ questions: nodes.flatMap(({ entry }, index) => waiting[index][1].map(({ requestID, questions, stopped }) => ({
38
+ sessionID: entry.sessionID,
39
+ requestID,
40
+ questions,
41
+ ...(stopped ? { stopped } : {}),
42
+ }))),
43
+ ...(truncated ? { truncated: ports.maxTotal } : {}),
44
+ };
45
+ }
46
+ /** Passes the person's answers to a question on, as courier_answer does; a refusal, such as answers that do not fit, comes back as `problem`. */
47
+ export async function answer(ports, input) {
48
+ try {
49
+ const result = await answerForPerson(ports.questions, input);
50
+ return result.answered ? { answered: true, by: result.by } : { answered: false };
51
+ }
52
+ catch (error) {
53
+ return { answered: false, problem: error instanceof Error ? error.message : String(error) };
54
+ }
55
+ }
56
+ /** Serves the person's RPC at the plugin's location; disposed with the plugin. */
57
+ export function registerPersonRpc(rpc, ports) {
58
+ return rpc.register(personRpc, {
59
+ asks: ({ sessionID }) => asks(ports, sessionID),
60
+ answer: (input) => answer(ports, input),
61
+ });
62
+ }
@@ -0,0 +1,227 @@
1
+ import { Schema } from "effect";
2
+ /** A session of the tree, as the inbox names where an ask comes from. */
3
+ declare const Session: Schema.Struct<{
4
+ readonly sessionID: Schema.String;
5
+ readonly title: Schema.String;
6
+ readonly parentID: Schema.String;
7
+ /** A child of the tree's top session is at depth 1. */
8
+ readonly depth: Schema.Int;
9
+ readonly group: Schema.optionalKey<Schema.String>;
10
+ }>;
11
+ /** A permission request a session of the tree waits on. */
12
+ declare const Permission: Schema.Struct<{
13
+ readonly sessionID: Schema.String;
14
+ readonly requestID: Schema.String;
15
+ readonly action: Schema.String;
16
+ readonly resources: Schema.$Array<Schema.String>;
17
+ /** What "always" would allow from now on; absent when it is not offered. */
18
+ readonly save: Schema.optionalKey<Schema.$Array<Schema.String>>;
19
+ }>;
20
+ /** A question a session of the tree waits on; `stopped` when its call was cut off and the answer goes as a message. */
21
+ declare const Question: Schema.Struct<{
22
+ readonly sessionID: Schema.String;
23
+ readonly requestID: Schema.String;
24
+ readonly questions: Schema.$Array<Schema.Struct<{
25
+ readonly question: Schema.String;
26
+ readonly header: Schema.String;
27
+ readonly options: Schema.$Array<Schema.Struct<{
28
+ readonly label: Schema.String;
29
+ readonly description: Schema.String;
30
+ }>>;
31
+ readonly multiple: Schema.optionalKey<Schema.Boolean>;
32
+ }>>;
33
+ readonly stopped: Schema.optionalKey<Schema.Boolean>;
34
+ }>;
35
+ /** What the sessions of a courier tree wait on the person for, but forms, which the TUI lists itself. */
36
+ declare const Asks: Schema.Struct<{
37
+ /** The session at the top of the tree, which the sessions below were started from. */
38
+ readonly top: Schema.String;
39
+ /** The sessions below the top, in the order a tree is drawn. */
40
+ readonly sessions: Schema.$Array<Schema.Struct<{
41
+ readonly sessionID: Schema.String;
42
+ readonly title: Schema.String;
43
+ readonly parentID: Schema.String;
44
+ /** A child of the tree's top session is at depth 1. */
45
+ readonly depth: Schema.Int;
46
+ readonly group: Schema.optionalKey<Schema.String>;
47
+ }>>;
48
+ readonly permissions: Schema.$Array<Schema.Struct<{
49
+ readonly sessionID: Schema.String;
50
+ readonly requestID: Schema.String;
51
+ readonly action: Schema.String;
52
+ readonly resources: Schema.$Array<Schema.String>;
53
+ /** What "always" would allow from now on; absent when it is not offered. */
54
+ readonly save: Schema.optionalKey<Schema.$Array<Schema.String>>;
55
+ }>>;
56
+ readonly questions: Schema.$Array<Schema.Struct<{
57
+ readonly sessionID: Schema.String;
58
+ readonly requestID: Schema.String;
59
+ readonly questions: Schema.$Array<Schema.Struct<{
60
+ readonly question: Schema.String;
61
+ readonly header: Schema.String;
62
+ readonly options: Schema.$Array<Schema.Struct<{
63
+ readonly label: Schema.String;
64
+ readonly description: Schema.String;
65
+ }>>;
66
+ readonly multiple: Schema.optionalKey<Schema.Boolean>;
67
+ }>>;
68
+ readonly stopped: Schema.optionalKey<Schema.Boolean>;
69
+ }>>;
70
+ /** Set when the tree had more sessions than `maxTotal`, which were not read. */
71
+ readonly truncated: Schema.optionalKey<Schema.Int>;
72
+ }>;
73
+ export type Asks = typeof Asks.Type;
74
+ export type AskedQuestion = typeof Question.Type;
75
+ export type AskedPermission = typeof Permission.Type;
76
+ export type TreeSession = typeof Session.Type;
77
+ /** What answering a question did; `problem` says why nothing was passed on, when it was refused. */
78
+ declare const Answered: Schema.Struct<{
79
+ readonly answered: Schema.Boolean;
80
+ readonly by: Schema.optionalKey<Schema.Literals<readonly ["result", "message"]>>;
81
+ readonly problem: Schema.optionalKey<Schema.String>;
82
+ }>;
83
+ export type Answered = typeof Answered.Type;
84
+ export declare const personRpc: {
85
+ readonly id: "courier-person";
86
+ readonly methods: {
87
+ readonly asks: {
88
+ readonly input: import("effect/StandardSchema").StandardSchemaV1<{
89
+ readonly sessionID: string;
90
+ }, {
91
+ readonly sessionID: string;
92
+ }> & Schema.Struct<{
93
+ readonly sessionID: Schema.String;
94
+ }>;
95
+ readonly output: import("effect/StandardSchema").StandardSchemaV1<{
96
+ readonly questions: readonly {
97
+ readonly sessionID: string;
98
+ readonly requestID: string;
99
+ readonly questions: readonly {
100
+ readonly options: readonly {
101
+ readonly label: string;
102
+ readonly description: string;
103
+ }[];
104
+ readonly question: string;
105
+ readonly header: string;
106
+ readonly multiple?: boolean | undefined;
107
+ }[];
108
+ readonly stopped?: boolean | undefined;
109
+ }[];
110
+ readonly sessions: readonly {
111
+ readonly sessionID: string;
112
+ readonly title: string;
113
+ readonly parentID: string;
114
+ readonly depth: number;
115
+ readonly group?: string | undefined;
116
+ }[];
117
+ readonly permissions: readonly {
118
+ readonly sessionID: string;
119
+ readonly resources: readonly string[];
120
+ readonly requestID: string;
121
+ readonly action: string;
122
+ readonly save?: readonly string[] | undefined;
123
+ }[];
124
+ readonly top: string;
125
+ readonly truncated?: number | undefined;
126
+ }, {
127
+ readonly questions: readonly {
128
+ readonly sessionID: string;
129
+ readonly requestID: string;
130
+ readonly questions: readonly {
131
+ readonly options: readonly {
132
+ readonly label: string;
133
+ readonly description: string;
134
+ }[];
135
+ readonly question: string;
136
+ readonly header: string;
137
+ readonly multiple?: boolean | undefined;
138
+ }[];
139
+ readonly stopped?: boolean | undefined;
140
+ }[];
141
+ readonly sessions: readonly {
142
+ readonly sessionID: string;
143
+ readonly title: string;
144
+ readonly parentID: string;
145
+ readonly depth: number;
146
+ readonly group?: string | undefined;
147
+ }[];
148
+ readonly permissions: readonly {
149
+ readonly sessionID: string;
150
+ readonly resources: readonly string[];
151
+ readonly requestID: string;
152
+ readonly action: string;
153
+ readonly save?: readonly string[] | undefined;
154
+ }[];
155
+ readonly top: string;
156
+ readonly truncated?: number | undefined;
157
+ }> & Schema.Struct<{
158
+ /** The session at the top of the tree, which the sessions below were started from. */
159
+ readonly top: Schema.String;
160
+ /** The sessions below the top, in the order a tree is drawn. */
161
+ readonly sessions: Schema.$Array<Schema.Struct<{
162
+ readonly sessionID: Schema.String;
163
+ readonly title: Schema.String;
164
+ readonly parentID: Schema.String;
165
+ /** A child of the tree's top session is at depth 1. */
166
+ readonly depth: Schema.Int;
167
+ readonly group: Schema.optionalKey<Schema.String>;
168
+ }>>;
169
+ readonly permissions: Schema.$Array<Schema.Struct<{
170
+ readonly sessionID: Schema.String;
171
+ readonly requestID: Schema.String;
172
+ readonly action: Schema.String;
173
+ readonly resources: Schema.$Array<Schema.String>;
174
+ /** What "always" would allow from now on; absent when it is not offered. */
175
+ readonly save: Schema.optionalKey<Schema.$Array<Schema.String>>;
176
+ }>>;
177
+ readonly questions: Schema.$Array<Schema.Struct<{
178
+ readonly sessionID: Schema.String;
179
+ readonly requestID: Schema.String;
180
+ readonly questions: Schema.$Array<Schema.Struct<{
181
+ readonly question: Schema.String;
182
+ readonly header: Schema.String;
183
+ readonly options: Schema.$Array<Schema.Struct<{
184
+ readonly label: Schema.String;
185
+ readonly description: Schema.String;
186
+ }>>;
187
+ readonly multiple: Schema.optionalKey<Schema.Boolean>;
188
+ }>>;
189
+ readonly stopped: Schema.optionalKey<Schema.Boolean>;
190
+ }>>;
191
+ /** Set when the tree had more sessions than `maxTotal`, which were not read. */
192
+ readonly truncated: Schema.optionalKey<Schema.Int>;
193
+ }>;
194
+ };
195
+ readonly answer: {
196
+ readonly input: import("effect/StandardSchema").StandardSchemaV1<{
197
+ readonly sessionID: string;
198
+ readonly requestID: string;
199
+ readonly answers: readonly (readonly string[])[];
200
+ }, {
201
+ readonly sessionID: string;
202
+ readonly requestID: string;
203
+ readonly answers: readonly (readonly string[])[];
204
+ }> & Schema.Struct<{
205
+ readonly sessionID: Schema.String;
206
+ readonly requestID: Schema.String;
207
+ readonly answers: Schema.$Array<Schema.$Array<Schema.String>>;
208
+ }>;
209
+ readonly output: import("effect/StandardSchema").StandardSchemaV1<{
210
+ readonly answered: boolean;
211
+ readonly by?: "result" | "message" | undefined;
212
+ readonly problem?: string | undefined;
213
+ }, {
214
+ readonly answered: boolean;
215
+ readonly by?: "result" | "message" | undefined;
216
+ readonly problem?: string | undefined;
217
+ }> & Schema.Struct<{
218
+ readonly answered: Schema.Boolean;
219
+ readonly by: Schema.optionalKey<Schema.Literals<readonly ["result", "message"]>>;
220
+ readonly problem: Schema.optionalKey<Schema.String>;
221
+ }>;
222
+ };
223
+ };
224
+ readonly events: {};
225
+ };
226
+ export type PersonRpc = typeof personRpc;
227
+ export {};