opencode-courier 0.13.8 → 0.15.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 (163) hide show
  1. package/README.md +7 -9
  2. package/dist/index.d.ts +0 -8
  3. package/dist/index.js +7 -54
  4. package/dist/kernel/protocol.d.ts +3 -17
  5. package/dist/kernel/protocol.js +3 -17
  6. package/dist/kernel/tool.d.ts +0 -2
  7. package/dist/kernel/tool.js +0 -1
  8. package/dist/memory/domain.d.ts +6 -85
  9. package/dist/memory/domain.js +7 -63
  10. package/dist/memory/events.d.ts +0 -11
  11. package/dist/memory/events.js +1 -12
  12. package/dist/memory/forget.d.ts +0 -5
  13. package/dist/memory/forget.js +0 -4
  14. package/dist/memory/index.js +0 -2
  15. package/dist/memory/lessons.d.ts +1 -9
  16. package/dist/memory/lessons.js +2 -11
  17. package/dist/memory/note.d.ts +2 -11
  18. package/dist/memory/note.js +5 -11
  19. package/dist/memory/pin.d.ts +0 -5
  20. package/dist/memory/pin.js +0 -4
  21. package/dist/memory/ports.d.ts +2 -22
  22. package/dist/memory/recall.d.ts +0 -5
  23. package/dist/memory/recall.js +0 -12
  24. package/dist/memory/record.d.ts +0 -4
  25. package/dist/memory/record.js +8 -31
  26. package/dist/memory/rpc-handlers.d.ts +3 -6
  27. package/dist/memory/rpc-handlers.js +0 -6
  28. package/dist/memory/rpc.d.ts +6 -19
  29. package/dist/memory/rpc.js +1 -16
  30. package/dist/memory/store.d.ts +0 -5
  31. package/dist/memory/store.js +1 -16
  32. package/dist/memory/sweep.d.ts +0 -4
  33. package/dist/memory/sweep.js +0 -4
  34. package/dist/memory/text.d.ts +1 -26
  35. package/dist/memory/text.js +8 -38
  36. package/dist/memory/tools.d.ts +0 -2
  37. package/dist/memory/tools.js +4 -6
  38. package/dist/memory/tui.d.ts +1 -35
  39. package/dist/person/events.d.ts +0 -16
  40. package/dist/person/events.js +3 -30
  41. package/dist/person/form.d.ts +0 -9
  42. package/dist/person/form.js +0 -10
  43. package/dist/person/index.d.ts +0 -2
  44. package/dist/person/permission.d.ts +0 -34
  45. package/dist/person/permission.js +2 -28
  46. package/dist/person/ports.d.ts +0 -16
  47. package/dist/person/ports.js +0 -1
  48. package/dist/person/question/answer.d.ts +0 -28
  49. package/dist/person/question/answer.js +0 -49
  50. package/dist/person/question/index.js +0 -2
  51. package/dist/person/question/lifecycle.d.ts +0 -26
  52. package/dist/person/question/lifecycle.js +1 -32
  53. package/dist/person/question/pure.d.ts +0 -11
  54. package/dist/person/question/pure.js +0 -11
  55. package/dist/person/question/relay.d.ts +0 -1
  56. package/dist/person/question/relay.js +5 -67
  57. package/dist/person/question/runtime.d.ts +0 -14
  58. package/dist/person/question/runtime.js +1 -26
  59. package/dist/person/question/shared.d.ts +0 -17
  60. package/dist/person/question/shared.js +0 -20
  61. package/dist/person/question/types.d.ts +0 -44
  62. package/dist/person/rpc-handlers.d.ts +0 -7
  63. package/dist/person/rpc-handlers.js +0 -6
  64. package/dist/person/rpc.d.ts +0 -20
  65. package/dist/person/rpc.js +0 -16
  66. package/dist/person/text.d.ts +0 -45
  67. package/dist/person/text.js +2 -54
  68. package/dist/person/tools.d.ts +0 -3
  69. package/dist/person/tools.js +3 -4
  70. package/dist/person/tui.d.ts +0 -21
  71. package/dist/platform/bounded.d.ts +0 -8
  72. package/dist/platform/bounded.js +0 -8
  73. package/dist/platform/events.d.ts +0 -18
  74. package/dist/platform/events.js +3 -22
  75. package/dist/platform/hub.d.ts +0 -113
  76. package/dist/platform/hub.js +1 -76
  77. package/dist/platform/json.d.ts +0 -3
  78. package/dist/platform/json.js +1 -3
  79. package/dist/platform/storage.d.ts +0 -7
  80. package/dist/platform/storage.js +0 -6
  81. package/dist/platform/version.d.ts +0 -9
  82. package/dist/platform/version.js +0 -8
  83. package/dist/rpc.js +0 -1
  84. package/dist/scheduling/index.d.ts +0 -1
  85. package/dist/scheduling/index.js +0 -1
  86. package/dist/scheduling/later.d.ts +0 -11
  87. package/dist/scheduling/later.js +0 -14
  88. package/dist/scheduling/text.d.ts +0 -1
  89. package/dist/scheduling/text.js +0 -1
  90. package/dist/scheduling/tools.d.ts +0 -1
  91. package/dist/scheduling/tools.js +6 -8
  92. package/dist/skill.d.ts +1 -9
  93. package/dist/skill.js +40 -97
  94. package/dist/tree/cleanup.d.ts +0 -20
  95. package/dist/tree/cleanup.js +0 -26
  96. package/dist/tree/context.d.ts +0 -5
  97. package/dist/tree/context.js +17 -13
  98. package/dist/tree/domain.d.ts +0 -64
  99. package/dist/tree/domain.js +7 -45
  100. package/dist/tree/events.d.ts +5 -36
  101. package/dist/tree/events.js +23 -55
  102. package/dist/tree/forget.d.ts +0 -15
  103. package/dist/tree/forget.js +0 -18
  104. package/dist/tree/group.d.ts +0 -22
  105. package/dist/tree/group.js +6 -39
  106. package/dist/tree/index.d.ts +2 -2
  107. package/dist/tree/index.js +1 -3
  108. package/dist/tree/limits.d.ts +1 -4
  109. package/dist/tree/limits.js +9 -13
  110. package/dist/tree/mascot.d.ts +0 -25
  111. package/dist/tree/mascot.js +1 -32
  112. package/dist/tree/model.d.ts +0 -5
  113. package/dist/tree/model.js +2 -15
  114. package/dist/tree/nudge.d.ts +0 -11
  115. package/dist/tree/nudge.js +3 -16
  116. package/dist/tree/panel.d.ts +0 -8
  117. package/dist/tree/panel.js +3 -16
  118. package/dist/tree/ports.d.ts +5 -139
  119. package/dist/tree/report.d.ts +1 -10
  120. package/dist/tree/report.js +4 -11
  121. package/dist/tree/roster.d.ts +0 -21
  122. package/dist/tree/roster.js +0 -21
  123. package/dist/tree/rpc-handlers.d.ts +6 -1
  124. package/dist/tree/rpc-handlers.js +0 -5
  125. package/dist/tree/rpc.d.ts +9 -109
  126. package/dist/tree/rpc.js +2 -38
  127. package/dist/tree/running.d.ts +0 -6
  128. package/dist/tree/running.js +2 -10
  129. package/dist/tree/send.d.ts +0 -4
  130. package/dist/tree/send.js +4 -31
  131. package/dist/tree/spawn.d.ts +1 -7
  132. package/dist/tree/spawn.js +15 -56
  133. package/dist/tree/standing.d.ts +0 -8
  134. package/dist/tree/standing.js +1 -11
  135. package/dist/tree/status.d.ts +0 -8
  136. package/dist/tree/status.js +1 -9
  137. package/dist/tree/stop.d.ts +2 -9
  138. package/dist/tree/stop.js +58 -25
  139. package/dist/tree/store.d.ts +0 -8
  140. package/dist/tree/store.js +4 -38
  141. package/dist/tree/subtree.d.ts +8 -26
  142. package/dist/tree/subtree.js +37 -35
  143. package/dist/tree/summary.d.ts +0 -4
  144. package/dist/tree/summary.js +0 -8
  145. package/dist/tree/text.d.ts +4 -46
  146. package/dist/tree/text.js +44 -83
  147. package/dist/tree/tools.d.ts +0 -6
  148. package/dist/tree/tools.js +15 -15
  149. package/dist/tree/tui.d.ts +0 -81
  150. package/dist/tree/wake.d.ts +0 -30
  151. package/dist/tree/wake.js +5 -39
  152. package/dist/tui.js +24 -10
  153. package/package.json +8 -9
  154. package/dist/webhook/index.d.ts +0 -5
  155. package/dist/webhook/index.js +0 -3
  156. package/dist/webhook/receiver.d.ts +0 -80
  157. package/dist/webhook/receiver.js +0 -339
  158. package/dist/webhook/subscriptions.d.ts +0 -29
  159. package/dist/webhook/subscriptions.js +0 -52
  160. package/dist/webhook/text.d.ts +0 -23
  161. package/dist/webhook/text.js +0 -82
  162. package/dist/webhook/tools.d.ts +0 -19
  163. package/dist/webhook/tools.js +0 -46
@@ -4,7 +4,6 @@ import { standingIn } from "./status.js";
4
4
  import { childStore } from "./store.js";
5
5
  import { readTop, readTree } from "./subtree.js";
6
6
  import { stalledAmong } from "./wake.js";
7
- /** The last tool call in a session's messages, and when it was made. */
8
7
  export function lastTool(messages) {
9
8
  for (const message of messages.toReversed()) {
10
9
  if (message.type !== "assistant")
@@ -15,7 +14,7 @@ export function lastTool(messages) {
15
14
  }
16
15
  return undefined;
17
16
  }
18
- /** How full a session's context is, as OpenCode's session view reads it (packages/tui/src/util/session.ts, v2.1.0): the last usage since the last compaction over its model's window. */
17
+ // as OpenCode's session view reads it (packages/tui/src/util/session.ts)
19
18
  export function contextOf(messages, windows) {
20
19
  const compacted = messages.findLastIndex((message) => message.type === "compaction" && message.status === "completed");
21
20
  const last = messages.findLast((message, index) => index > compacted && message.type === "assistant" && message.tokens !== undefined);
@@ -26,13 +25,10 @@ export function contextOf(messages, windows) {
26
25
  const window = windows.find((model) => model.providerID === last.model.providerID && model.id === last.model.id)?.context;
27
26
  return used > 0 && window ? used / window : undefined;
28
27
  }
29
- /** A request as the panel says what a paused session waits on: a permission request, or its first question. */
30
28
  export const waitOf = (pending) => pending.type === "permission"
31
29
  ? { type: "permission", action: pending.action, resources: [...pending.resources] }
32
30
  : { type: "question", question: pending.questions[0]?.question ?? "" };
33
- /** A form a session shows, as the panel says what it waits on. */
34
31
  export const formWait = (form) => ({ type: "form", title: form.title });
35
- /** A session's report since its last prompt, as the panel shows it; none for one spawned before this was kept. */
36
32
  function reportRow(report, status) {
37
33
  if (!report)
38
34
  return {};
@@ -42,10 +38,6 @@ function reportRow(report, status) {
42
38
  return { report: { state: "ended", progressed: report.progressed } };
43
39
  return { report: { state: "reported", progressed: report.progressed, ...(status ? { status } : {}) } };
44
40
  }
45
- /**
46
- * What a session does: waiting on a request to the person or a form it shows them, stalled, running, idle, or gone when
47
- * OpenCode no longer knows it. A message waiting in its inbox is no turn (`runs`); a stalled one reads stalled.
48
- */
49
41
  function stateOf(info, waits, stalled, running) {
50
42
  if (!info)
51
43
  return "gone";
@@ -55,8 +47,7 @@ function stateOf(info, waits, stalled, running) {
55
47
  return "stalled";
56
48
  return running ? "running" : "idle";
57
49
  }
58
- /** One session of the panel: what it is doing, its report and group standing, its last tool call, context use and cost. */
59
- async function row(ports, windows, { entry, depth, info, running, endedAt, pending, forms, report }, stalled) {
50
+ async function row(ports, windows, { entry, depth, subagent, info, running, endedAt, pending, forms, report }, stalled) {
60
51
  const { sessionID } = entry;
61
52
  const [messages, status, standing, stopped] = await Promise.all([
62
53
  ports.session.context({ sessionID }).catch(() => []),
@@ -67,7 +58,6 @@ async function row(ports, windows, { entry, depth, info, running, endedAt, pendi
67
58
  const tool = lastTool(messages);
68
59
  const context = contextOf(messages, windows);
69
60
  const state = stateOf(info, pending.length + forms.length, stalled !== undefined, running);
70
- // A prompt since the failure drops it from the report, so `resumesAt` here is that of the last turn alone.
71
61
  const resumes = state === "idle" && !stopped && report?.resumesAt !== undefined && report.resumesAt > ports.now() ? report.resumesAt : undefined;
72
62
  return {
73
63
  sessionID,
@@ -82,6 +72,7 @@ async function row(ports, windows, { entry, depth, info, running, endedAt, pendi
82
72
  ...reportRow(report, status),
83
73
  ...(entry.group && standing ? { group: { name: entry.group, standing } } : {}),
84
74
  ...startedAs(entry),
75
+ ...(subagent ? { subagent } : {}),
85
76
  created: info?.time.created ?? entry.createdAt,
86
77
  ...(info && !running && endedAt !== undefined ? { idle: endedAt } : {}),
87
78
  ...(info ? { cost: info.cost } : {}),
@@ -90,10 +81,6 @@ async function row(ports, windows, { entry, depth, info, running, endedAt, pendi
90
81
  waits: [...pending.map(waitOf), ...forms.map(formWait)],
91
82
  };
92
83
  }
93
- /**
94
- * The tree panel's rows: the sessions under a session as courier_tree lists them, at most `maxTotal`, live ones first,
95
- * then the newest, each as `row` reads it; `top` the session itself, `truncated` the ones left out, `stalled` its stall.
96
- */
97
84
  export async function panel(ports, rootID) {
98
85
  const [{ kept, leftOut }, root] = await Promise.all([readTree(ports, rootID), readTop(ports, rootID)]);
99
86
  const stalled = await stalledAmong(ports, [rootID, ...kept.map((node) => node.entry.sessionID)]);
@@ -1,7 +1,3 @@
1
- /**
2
- * The tree's ports, what it needs of the plugin context and of other modules, and the data types they and
3
- * its texts take; those its pure rules take are declared in `domain.ts` and re-exported here.
4
- */
5
1
  import type { Plugin } from "@opencode/plugin";
6
2
  import type { Status } from "../kernel/protocol.js";
7
3
  import type { Storage } from "../platform/storage.js";
@@ -9,236 +5,143 @@ import type { Limits, Member, ModelRef, RosterEntry } from "./domain.js";
9
5
  export type { Artifacts, HeldReport, LeftMember, Limits, Member, ModelRef, RosterEntry, Standing } from "./domain.js";
10
6
  type Context = Plugin.Context;
11
7
  export type RosterStorage = Storage;
12
- /** How a session's wait for a report ended: it reported to its parent, or its turn failed or was interrupted. */
13
8
  export type Settled = "report" | "failed" | "interrupted";
14
- /** The notes kept of a spawned session's report state; docs/maintainers/architecture.md has each. */
15
9
  export type ReportField = "prompt" | "settled" | "progress" | "told" | "stopped" | "nudged";
16
- /** A report note as read: when it was made, and for `settled` how the wait ended, the report's status or a failure's `resumesAt`, as stored. */
17
10
  export interface ReportNote {
18
11
  readonly at: number | undefined;
19
12
  readonly by: unknown;
20
13
  readonly status: unknown;
21
14
  readonly resumesAt: unknown;
22
15
  }
23
- /** A stored value, as `Storage` gives and takes it. */
24
16
  export type Stored = Parameters<Storage["set"]>[1];
25
- /** The report keys found, by the session they are about. */
26
17
  export type ReportKeys = ReadonlyMap<string, ReadonlyArray<string>>;
27
- /**
28
- * A spawned session as kept: its roster entry under its parent, the reverse index that finds it, and its report
29
- * state, which go together. The reverse index is only an index: a session it does not name is looked for in the roster.
30
- */
31
18
  export interface ChildStore {
32
- /** Records a child under its parent, then indexes it; a failed index write is left to a scan and the next load. */
33
19
  record(entry: RosterEntry): Promise<void>;
34
- /** Removes a child's entry and, at once and best effort, its reverse key and report state; only the entry's removal can fail it. */
35
20
  remove(parentID: string, sessionID: string): Promise<void>;
36
- /** A session's roster entry under a parent, if it is there. */
37
21
  entry(parentID: string, sessionID: string): Promise<RosterEntry | undefined>;
38
- /** A parent's children, oldest first. */
39
22
  children(parentID: string): Promise<RosterEntry[]>;
40
- /** Every parent's children. */
41
23
  all(): Promise<RosterEntry[]>;
42
- /** A session's roster entries: one for the parent that started it, none if courier_spawn did not. */
43
24
  entriesOf(sessionID: string): Promise<RosterEntry[]>;
44
- /** The session that started a session, by the reverse index alone, without scanning the roster. */
45
25
  indexedParent(sessionID: string): Promise<string | undefined>;
46
- /** The sessions above a session, its parent first, by the reverse index alone: empty when it does not name the session. */
47
26
  indexedAncestors(sessionID: string): Promise<ReadonlyArray<string>>;
48
- /** A session's roster entry, by the reverse index alone, without scanning the roster. */
49
27
  indexedEntry(sessionID: string): Promise<RosterEntry | undefined>;
50
- /**
51
- * The roster entries from a session up to the top, its own first, ending with the entry whose parent
52
- * courier_spawn did not start; empty when courier_spawn did not start the session.
53
- */
54
28
  lineage(sessionID: string): Promise<RosterEntry[]>;
55
- /** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
56
29
  indexedLineage(sessionID: string): Promise<RosterEntry[]>;
57
- /** Writes the reverse keys missing or stale for the given entries, the whole roster, and drops those whose entry is gone. */
58
30
  backfill(entries: ReadonlyArray<RosterEntry>): Promise<void>;
59
- /** The given notes of a session's report state, read at once. */
60
31
  notes<F extends ReportField>(sessionID: string, fields: ReadonlyArray<F>): Promise<{
61
32
  readonly [K in F]: ReportNote;
62
33
  }>;
63
- /** Notes that a prompt or message reached a spawned session at `at`, unless a later one is noted. */
64
34
  prompted(sessionID: string, at: number): Promise<void>;
65
- /**
66
- * Notes how a spawned session's wait for a report ended at `at`, with its report's status or, for a failed turn, when the
67
- * provider's limit lifts (`resumesAt`), unless a later end is noted.
68
- */
69
35
  settled(sessionID: string, by: Settled, at: number, status?: Status, resumesAt?: number): Promise<void>;
70
- /** Notes that a spawned session messaged its parent without a status at `at`, unless a later one is noted. */
71
36
  progressed(sessionID: string, at: number): Promise<void>;
72
- /** Notes that a spawned session's parent was told at `at` that a turn ended without a report, unless a later telling is noted. */
73
37
  told(sessionID: string, at: number): Promise<void>;
74
- /** Takes back the note `told` made at `at`; a later one stays, and one that cannot be read goes. */
75
38
  untold(sessionID: string, at: number): Promise<void>;
76
- /** Notes that courier_stop stopped a spawned session at `at`. */
77
39
  stopped(sessionID: string, at: number): Promise<void>;
78
- /** Notes that a spawned session was reminded at `at` to report, with the reminder's inbox id once it is admitted. */
79
40
  nudged(sessionID: string, at: number, inboxID?: string): Promise<void>;
80
- /** When a spawned session was last reminded to report, and the reminder's inbox id. */
81
41
  nudgeOf(sessionID: string): Promise<{
82
42
  readonly at: number | undefined;
83
43
  readonly inboxID?: string;
84
44
  }>;
85
- /** A `settled` or `progress` note as stored, for `restore` to put back. */
86
45
  peek(sessionID: string, field: "settled" | "progress"): Promise<Stored | undefined>;
87
- /** Puts back a note `peek` read; one that was not there is removed. */
88
46
  restore(sessionID: string, field: "settled" | "progress", value: Stored | undefined): Promise<void>;
89
- /** Drops a session's report state. */
90
47
  forgetReport(sessionID: string): Promise<void>;
91
- /** Every report key, by the session it is about. */
92
48
  reported(): Promise<ReportKeys>;
93
- /** Removes report keys `reported` found, each best effort. */
94
49
  dropReported(keys: ReadonlyArray<string>): Promise<void>;
95
50
  }
96
- /**
97
- * A courier message waiting in a session's inbox, watched until it is delivered: `running` while the session runs a
98
- * turn, which takes it at its next step; `idleAt` since the session was last seen idle with it waiting.
99
- */
100
51
  export interface Wake {
101
52
  readonly sessionID: string;
102
53
  readonly inboxID: string;
103
- /** When OpenCode enqueued it. */
104
54
  readonly at: number;
105
- /** Its text and description, which waking the session again sends under the same inbox id. */
106
55
  readonly text: string;
107
56
  readonly description?: string;
108
57
  readonly running: boolean;
109
58
  readonly idleAt?: number;
110
- /** When the scheduler woke the session again, the message still waiting. */
111
59
  readonly rewoken?: number;
112
- /** When the session was found still not woken after that: it is stalled. */
113
60
  readonly stalled?: number;
114
61
  }
115
62
  export interface WakeStore {
116
- /** Writes a watched message, replacing what was kept of it. */
117
63
  put(wake: Wake): Promise<void>;
118
- /** The messages watched in a session's inbox. */
119
64
  of(sessionID: string): Promise<Wake[]>;
120
- /** Every message watched. */
121
65
  all(): Promise<Wake[]>;
122
- /** Stops watching a message: delivered, cancelled, or its session gone. */
123
66
  remove(sessionID: string, inboxID: string): Promise<void>;
124
67
  }
125
- /** A member of a join group with where it is kept. */
126
68
  export interface Membership extends Member {
127
69
  readonly parentID: string;
128
70
  readonly group: string;
129
71
  readonly sessionID: string;
130
72
  }
131
- /** The members of parents' join groups, each with its held report or how it left; a value that is not a member reads as none. */
132
73
  export interface GroupStore {
133
- /** A session's member in a parent's group, if it is one. */
134
74
  member(parentID: string, group: string, sessionID: string): Promise<Member | undefined>;
135
- /** The members of every group. */
136
75
  all(): Promise<Membership[]>;
137
- /** The members of a parent's groups. */
138
76
  ofParent(parentID: string): Promise<Membership[]>;
139
- /** The members of one group. */
140
77
  ofGroup(parentID: string, group: string): Promise<Membership[]>;
141
- /** Writes a member in place of what was kept for it. */
142
78
  put(parentID: string, group: string, sessionID: string, member: Member): Promise<void>;
143
- /** Removes a member whatever it holds. */
144
79
  remove(parentID: string, group: string, sessionID: string): Promise<void>;
145
80
  }
146
- /** A report held with its group: which group, and how many of its members have reported so far, when that could be read. */
147
81
  export interface Held {
148
82
  readonly group: string;
149
83
  readonly reported?: number;
150
84
  readonly members?: number;
151
85
  }
152
- /**
153
- * What courier_send did; `report` says whether the message was noted as the sender's report to the session
154
- * that started it, and `held` that it was held with the sender's group rather than delivered, with no `messageID`.
155
- */
156
86
  export interface Sent {
157
87
  readonly messageID?: string;
158
88
  readonly status?: Status;
159
89
  readonly report?: boolean;
160
90
  readonly held?: Held;
161
- /** The status it was sent with to a session below the sender, where a status means nothing. */
162
91
  readonly dropped?: Status;
163
92
  }
164
- /**
165
- * What the `context` hook remembers of a session: its depth, null for one nobody spawned that has started none, and
166
- * whether it carries the role entry (`ROLE_KEY`), which then names its role in place of the hook's system part.
167
- */
168
93
  export interface Role {
169
94
  readonly depth: number | null;
170
95
  readonly entry: boolean;
96
+ readonly subagent?: true;
171
97
  }
172
- /** The keyed session entries, missing on a host before 2.2.0 (every copy's ports carry the host's `session`): an entry is then skipped. */
173
98
  export type Instructions = Partial<Pick<Context["session"], "instructions">>;
174
- /** A keyed session instruction entry: its key, `^[a-z0-9][a-z0-9._-]*$`, and its text. */
99
+ export type Lister = Partial<Pick<Context["session"], "list">>;
175
100
  export interface Entry {
176
101
  readonly key: string;
177
102
  readonly value: string;
178
103
  }
179
- /**
180
- * Supplies the entries other modules write to a spawned session below `maxDepth` before its prompt, and to a root at its
181
- * first spawn beside its role; `src/index.ts` composes it from the modules' suppliers, as it does the `Waits` port.
182
- */
183
104
  export type Entries = () => Promise<ReadonlyArray<Entry>>;
184
- /** What writing a parent's `standing` entry needs: the tools', the watcher's and the scheduler's ports all have these. */
185
105
  export interface StandingPorts {
186
106
  readonly storage: RosterStorage;
187
107
  readonly now: () => number;
188
108
  readonly session?: Instructions;
189
109
  readonly log?: (message: string) => void;
190
110
  }
191
- /** The slice of the plugin context the courier tools use; tests pass a fake. */
192
111
  export interface CourierPorts {
193
- /** `instructions`, the keyed session entries, is missing on a host before 2.2.0: the role then goes in a system part. */
194
- readonly session: Pick<Context["session"], "create" | "prompt" | "synthetic" | "get" | "context" | "interrupt"> & Instructions;
195
- /** The entries of other modules a spawned session gets before its prompt, the memory's lessons; none when not given. */
112
+ readonly session: Pick<Context["session"], "create" | "prompt" | "synthetic" | "get" | "context" | "interrupt"> & Instructions & Lister;
196
113
  readonly entries?: Entries;
197
114
  readonly agent: Pick<Context["agent"], "get">;
198
115
  readonly worktree: Pick<Context["worktree"], "create" | "remove">;
199
- /** The project of the plugin's location; an isolated child's worktree is made in it, and removed from it. */
200
116
  readonly projectID: string;
201
- /** The commit a directory's checkout is on, or undefined; recorded as an isolated child's base. */
202
117
  readonly head: (directory: string) => Promise<string | undefined>;
203
- /** Whether a directory's checkout has uncommitted changes, or undefined when git cannot tell. */
204
118
  readonly dirty: (directory: string) => Promise<boolean | undefined>;
205
119
  readonly storage: RosterStorage;
206
120
  readonly directory: string;
207
121
  readonly now: () => number;
208
- /** The permission requests and questions a session waits on, wherever they are pending. */
209
122
  readonly pending: (sessionID: string) => Promise<ReadonlyArray<Pending>>;
210
- /** The forms a session shows, which only the person can answer: web search's, a plugin's; not the question tool's. */
211
123
  readonly forms: (sessionID: string) => Promise<ReadonlyArray<ShownForm>>;
212
- /** The models available, each with its context window and variants, as courier_spawn checks a choice and the panel reads a session's context use. */
213
124
  readonly models: () => Promise<ReadonlyArray<KnownModel>>;
214
- /** OpenCode's default model here, which a session with no model of its own runs on; undefined when there is none. */
215
125
  readonly defaultModel: () => Promise<ModelRef | undefined>;
216
126
  readonly limits: Limits;
217
- /** The `nativeChildren` option: a child started without isolate and without `native` is created with OpenCode's own `parentID`. */
218
127
  readonly nativeChildren?: boolean;
219
128
  readonly gate: SpawnGate;
220
- /** The roles the `context` hook remembers; a parent's is dropped when it starts a child, a root's kept once its entry is written. */
221
129
  readonly roles: Map<string, Role>;
222
- /** Has the scheduler deliver what is due now rather than at its next tick: a group a report has just completed. */
223
130
  readonly nudge: () => void;
224
131
  readonly log: (message: string) => void;
225
132
  }
226
- /** A model's context window in tokens, as the panel and the memory read a session's context use by. */
227
133
  export interface ModelWindow {
228
134
  readonly providerID: string;
229
135
  readonly id: string;
230
136
  readonly context: number;
231
137
  }
232
- /** A model OpenCode lists, as `CourierPorts.models` gives it: its window, and the ids of its variants. */
233
138
  export interface KnownModel extends ModelWindow {
234
139
  readonly variants: ReadonlyArray<string>;
235
140
  }
236
- /** A form a session shows, as `CourierPorts.forms` lists it; `person/` keeps them. */
237
141
  export interface ShownForm {
238
142
  readonly id: string;
239
143
  readonly title: string;
240
144
  }
241
- /** One question a session waits on, as `Pending` shows it; `person/`'s `Prompt` has these fields. */
242
145
  export interface PendingPrompt {
243
146
  readonly question: string;
244
147
  readonly header: string;
@@ -248,7 +151,6 @@ export interface PendingPrompt {
248
151
  }>;
249
152
  readonly multiple?: boolean;
250
153
  }
251
- /** A request a session waits on until someone answers it: a permission request, or a question it asked. */
252
154
  export type Pending = {
253
155
  readonly type: "permission";
254
156
  readonly requestID: string;
@@ -259,19 +161,13 @@ export type Pending = {
259
161
  readonly type: "question";
260
162
  readonly requestID: string;
261
163
  readonly questions: ReadonlyArray<PendingPrompt>;
262
- /** Its question call was cut off; the answer reaches it as a message. */
263
164
  readonly stopped?: true;
264
165
  };
265
- /** A spawn past its limit check: its parent, the top of its tree, and its child once created. */
266
166
  export interface Reservation {
267
167
  readonly parentID: string;
268
168
  readonly top: string;
269
169
  sessionID?: string;
270
170
  }
271
- /**
272
- * The spawns under way in the process, under a fixed process-wide key outside the hub, so spawns in
273
- * other locations and copies count them; `turn` runs the limit checks one at a time.
274
- */
275
171
  export interface SpawnGate {
276
172
  readonly reserved: Set<Reservation>;
277
173
  turn: Promise<unknown>;
@@ -282,7 +178,6 @@ export interface LimitPorts {
282
178
  readonly limits: Limits;
283
179
  readonly gate: SpawnGate;
284
180
  }
285
- /** What courier_cleanup did: removed the worktree, found it gone, kept it and why, or forgot a child with none; `group`, one it dropped the child from. */
286
181
  export type CleanupResult = {
287
182
  readonly sessionID: string;
288
183
  readonly directory: string;
@@ -296,86 +191,57 @@ export type CleanupResult = {
296
191
  readonly changes: readonly string[];
297
192
  readonly commits: readonly string[];
298
193
  };
299
- /** What would be lost by removing a worktree. */
300
194
  export interface WorktreeState {
301
- /** Paths with uncommitted changes, untracked files included, as `git status --porcelain` lists them. */
302
195
  readonly changes: readonly string[];
303
- /**
304
- * Commits reachable from the worktree's HEAD but from no branch, tag or remote-tracking ref, nor
305
- * from the commit the worktree was made from, newest first.
306
- */
307
196
  readonly commits: readonly string[];
308
197
  }
309
198
  export interface CleanupPorts {
310
199
  readonly storage: RosterStorage;
311
200
  readonly worktree: Pick<Context["worktree"], "remove">;
312
- /** The project of the plugin's location, for roster entries recorded before they carried their project. */
313
201
  readonly projectID: string;
314
- /** The worktree's state, or undefined when its directory no longer exists; `base` is the commit it was made from. */
315
202
  readonly inspect: (directory: string, base?: string) => Promise<WorktreeState | undefined>;
316
- /** When a member still out is dropped from its group. */
317
203
  readonly now: () => number;
318
- /** Has the scheduler deliver what is due now: a group the forgotten child may have been the last member out of. */
319
204
  readonly nudge: () => void;
320
- /** For the parent's `standing` entry once the child is forgotten; without them it is left as it was. */
321
205
  readonly session?: Instructions;
322
206
  readonly log?: (message: string) => void;
323
207
  }
324
208
  export interface StopPorts {
325
209
  readonly courier: CourierPorts;
326
- /** Cancels the messages scheduled for a session; the ids of those cancelled. */
327
210
  readonly cancelScheduled: (sessionID: string) => Promise<ReadonlyArray<string>>;
328
211
  readonly cleanup: CleanupPorts;
329
212
  }
330
213
  export interface GroupPorts {
331
214
  readonly storage: Storage;
332
- readonly session: Pick<Context["session"], "synthetic" | "get"> & Instructions;
215
+ readonly session: Pick<Context["session"], "synthetic" | "get" | "context"> & Instructions;
333
216
  readonly now: () => number;
334
217
  readonly log: (message: string) => void;
335
218
  }
336
- /** The error of OpenCode's `session.execution.failed` event. */
337
219
  export interface ExecutionError {
338
220
  readonly type: string;
339
221
  readonly message: string;
340
222
  readonly status?: number;
341
- /** How long the provider asked to wait before trying again, counted from the event's `created`; set by rate and quota limits and some provider errors. */
342
223
  readonly retryAfterMs?: number;
343
224
  }
344
225
  export interface WatchPorts {
345
226
  readonly storage: RosterStorage;
346
- /** `context` reads the last reply of a session that ended its turn without reporting; `get` is what every member joins with too. */
347
227
  readonly session: Pick<Context["session"], "synthetic" | "context" | "get"> & Instructions;
348
228
  readonly event: Pick<Context["event"], "subscribe">;
349
- /** The permission domains of every loaded location, which the `Waits` suppliers ask; an isolated child's are in its worktree's. */
350
229
  readonly permissions: () => Iterable<Pick<Context["permission"], "list">>;
351
- /** The clock, in epoch milliseconds, for an event that carries no `created`. */
352
230
  readonly now: () => number;
353
231
  readonly log: (message: string) => void;
354
- /** Has the scheduler deliver what is due now rather than at its next tick: a group completed, or its parent's turn ended. */
355
232
  readonly nudge: () => void;
356
233
  }
357
- /**
358
- * What every plugin instance in the process shares. Several subscriptions and copies see the same
359
- * events, so each is claimed synchronously and handled once.
360
- */
361
234
  export interface WatchState {
362
235
  readonly seen: Set<string>;
363
- /** The type of each item enqueued for a spawned session, by inbox id, for its delivery to look up. */
364
236
  readonly inbox: Map<string, string>;
365
237
  }
366
- /**
367
- * Whether a session waits, since `since`, on what the tree does not keep: asked by the silent-turn
368
- * rule after the tree's own checks, through the watcher's ports; `src/index.ts` composes it.
369
- */
370
238
  export type Waits = (ports: Pick<WatchPorts, "storage" | "permissions">, sessionID: string, since: number) => Promise<boolean>;
371
239
  export interface ContextPorts {
372
240
  readonly storage: RosterStorage;
373
241
  readonly limits: Limits;
374
- /** Each session's role once looked up; a session nobody spawned reads as depth null until it starts one. */
375
242
  readonly roles: Map<string, Role>;
376
- /** The keyed session entries, listed when a session's role is looked up; missing on a host before 2.2.0, which gets the system part. */
243
+ readonly session: Pick<Context["session"], "get">;
377
244
  readonly instructions?: Context["session"]["instructions"];
378
- /** Tools of other modules hidden from a leaf alongside courier_spawn; `src/index.ts` names courier_recall and courier_note. */
379
245
  readonly hiddenFromLeaf?: ReadonlyArray<string>;
380
246
  readonly log: (message: string) => void;
381
247
  }
@@ -1,14 +1,9 @@
1
1
  import { type Status } from "../kernel/protocol.js";
2
2
  import type { ChildStore } from "./ports.js";
3
3
  type Notes = Pick<ChildStore, "notes">;
4
- /** Whether courier_stop stopped a session after its last prompt, or with none noted, at all. */
5
4
  export declare function stoppedSincePrompt(children: Notes, sessionID: string): Promise<boolean>;
6
- /** The status of a spawned session's last report to its parent, if it has reported with one. */
5
+ export declare function reportedSincePrompt(children: Notes, sessionID: string): Promise<boolean>;
7
6
  export declare function lastStatus(children: Notes, sessionID: string): Promise<Status | undefined>;
8
- /**
9
- * What is known of a spawned session's report: when a prompt last reached it, and whether since then it owes one, has
10
- * messaged its parent without a status, or failed (`resumesAt`: until when) or was interrupted. Undefined before this was kept.
11
- */
12
7
  export declare function reportOf(children: Notes, sessionID: string): Promise<{
13
8
  resumesAt?: number | undefined;
14
9
  prompt: number;
@@ -16,9 +11,5 @@ export declare function reportOf(children: Notes, sessionID: string): Promise<{
16
11
  progressed: boolean;
17
12
  ended: boolean;
18
13
  } | undefined>;
19
- /**
20
- * Whether its parent still waits on a spawned session's report: it owes one, and the parent has not
21
- * been told since its last prompt that it ended a turn without one.
22
- */
23
14
  export declare function awaited(children: Notes, sessionID: string): Promise<boolean>;
24
15
  export {};
@@ -1,20 +1,17 @@
1
1
  import { isStatus } from "../kernel/protocol.js";
2
- /** Whether `at` is known and no earlier than the prompt at `prompt`. */
3
2
  const since = (at, prompt) => at !== undefined && at >= prompt;
4
- /** Whether courier_stop stopped a session after its last prompt, or with none noted, at all. */
5
3
  export async function stoppedSincePrompt(children, sessionID) {
6
4
  const { stopped, prompt } = await children.notes(sessionID, ["stopped", "prompt"]);
7
5
  return stopped.at !== undefined && (prompt.at === undefined || stopped.at >= prompt.at);
8
6
  }
9
- /** The status of a spawned session's last report to its parent, if it has reported with one. */
7
+ export async function reportedSincePrompt(children, sessionID) {
8
+ const { prompt, settled } = await children.notes(sessionID, ["prompt", "settled"]);
9
+ return prompt.at !== undefined && settled.by === "report" && settled.status !== "blocked" && since(settled.at, prompt.at);
10
+ }
10
11
  export async function lastStatus(children, sessionID) {
11
12
  const { status } = (await children.notes(sessionID, ["settled"])).settled;
12
13
  return isStatus(status) ? status : undefined;
13
14
  }
14
- /**
15
- * What is known of a spawned session's report: when a prompt last reached it, and whether since then it owes one, has
16
- * messaged its parent without a status, or failed (`resumesAt`: until when) or was interrupted. Undefined before this was kept.
17
- */
18
15
  export async function reportOf(children, sessionID) {
19
16
  const { prompt, settled, progress } = await children.notes(sessionID, ["prompt", "settled", "progress"]);
20
17
  const promptAt = prompt.at;
@@ -30,10 +27,6 @@ export async function reportOf(children, sessionID) {
30
27
  ...(resumesAt === undefined ? {} : { resumesAt }),
31
28
  };
32
29
  }
33
- /**
34
- * Whether its parent still waits on a spawned session's report: it owes one, and the parent has not
35
- * been told since its last prompt that it ended a turn without one.
36
- */
37
30
  export async function awaited(children, sessionID) {
38
31
  const { prompt, settled, told } = await children.notes(sessionID, ["prompt", "settled", "told"]);
39
32
  return prompt.at !== undefined && !since(settled.at, prompt.at) && !since(told.at, prompt.at);
@@ -2,21 +2,10 @@ import { type Status } from "../kernel/protocol.js";
2
2
  import { type Exists } from "./domain.js";
3
3
  import type { RosterEntry, RosterStorage } from "./ports.js";
4
4
  export { RETENTION_MS, type Exists } from "./domain.js";
5
- /** Every parent's children. */
6
5
  export declare function allEntries(storage: RosterStorage): Promise<RosterEntry[]>;
7
- /**
8
- * The roster entries from a session up to the top, its own first, ending with the entry whose parent
9
- * courier_spawn did not start; empty when courier_spawn did not start the session.
10
- */
11
6
  export declare function lineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
12
- /** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
13
7
  export declare function indexedLineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
14
- /** The sessions a session started, oldest first, expired entries included. */
15
8
  export declare function childrenOf(storage: RosterStorage, parentID: string): Promise<RosterEntry[]>;
16
- /**
17
- * What another module reads of a spawned session's report: `known` once a prompt was noted, whether it `owes` one, how
18
- * its wait `ended` when not by a report, the `status` of its last report, and whether courier_stop `stopped` it since its last prompt.
19
- */
20
9
  export interface ReportState {
21
10
  readonly known: boolean;
22
11
  readonly owes: boolean;
@@ -25,17 +14,7 @@ export interface ReportState {
25
14
  readonly stopped: boolean;
26
15
  }
27
16
  export declare function reportState(storage: RosterStorage, sessionID: string): Promise<ReportState>;
28
- /**
29
- * The top of a spawned session's lineage, where its permission requests and questions go, since the
30
- * person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
31
- */
32
17
  export declare function answeringTop(storage: RosterStorage, sessionID: string, callerID: string, what: string): Promise<string>;
33
- /** `domain.ts`'s `isExpired`, asking the file system whether an isolated child's worktree is still there unless a test passes a fake. */
34
18
  export declare const isExpired: (entry: RosterEntry, now: number, exists?: Exists) => boolean;
35
- /** A parent's children, oldest first, after dropping the expired ones. */
36
19
  export declare function current(storage: RosterStorage, parentID: string, now: number, exists?: Exists): Promise<RosterEntry[]>;
37
- /**
38
- * On loading: drops expired entries of every parent, then brings the reverse index in line with
39
- * what is left (`backfill`) and drops report state with no entry (`dropOrphanedReports`).
40
- */
41
20
  export declare function pruneExpired(storage: RosterStorage, now: number, exists?: Exists): Promise<void>;
@@ -5,22 +5,15 @@ import { forgetChild, forgetReports } from "./forget.js";
5
5
  import { stoppedSincePrompt } from "./report.js";
6
6
  import { childStore } from "./store.js";
7
7
  export { RETENTION_MS } from "./domain.js";
8
- /** Every parent's children. */
9
8
  export async function allEntries(storage) {
10
9
  return childStore(storage).all();
11
10
  }
12
- /**
13
- * The roster entries from a session up to the top, its own first, ending with the entry whose parent
14
- * courier_spawn did not start; empty when courier_spawn did not start the session.
15
- */
16
11
  export async function lineage(storage, sessionID) {
17
12
  return childStore(storage).lineage(sessionID);
18
13
  }
19
- /** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
20
14
  export async function indexedLineage(storage, sessionID) {
21
15
  return childStore(storage).indexedLineage(sessionID);
22
16
  }
23
- /** The sessions a session started, oldest first, expired entries included. */
24
17
  export async function childrenOf(storage, parentID) {
25
18
  return childStore(storage).children(parentID);
26
19
  }
@@ -38,10 +31,6 @@ export async function reportState(storage, sessionID) {
38
31
  stopped,
39
32
  };
40
33
  }
41
- /**
42
- * The top of a spawned session's lineage, where its permission requests and questions go, since the
43
- * person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
44
- */
45
34
  export async function answeringTop(storage, sessionID, callerID, what) {
46
35
  const chain = await lineage(storage, sessionID);
47
36
  if (!chain.length)
@@ -51,31 +40,21 @@ export async function answeringTop(storage, sessionID, callerID, what) {
51
40
  throw new Error(`${sessionID}'s ${what} go to ${top}, the session at the top of the sessions started from it with courier_spawn; ${callerID} cannot answer them.`);
52
41
  return top;
53
42
  }
54
- /** `domain.ts`'s `isExpired`, asking the file system whether an isolated child's worktree is still there unless a test passes a fake. */
55
43
  export const isExpired = (entry, now, exists = existsSync) => expiredBy(entry, now, exists);
56
- /** Forgets the given entries that are expired at `now`, and returns the rest. */
57
44
  async function dropExpired(storage, entries, now, exists) {
58
45
  const expired = new Set(entries.filter((entry) => isExpired(entry, now, exists)));
59
46
  await Promise.all([...expired].map((entry) => forgetChild(storage, entry.parentID, entry.sessionID, entry.group)));
60
47
  return entries.filter((entry) => !expired.has(entry));
61
48
  }
62
- /** A parent's children, oldest first, after dropping the expired ones. */
63
49
  export async function current(storage, parentID, now, exists = existsSync) {
64
50
  return dropExpired(storage, await childStore(storage).children(parentID), now, exists);
65
51
  }
66
- /**
67
- * On loading: drops expired entries of every parent, then brings the reverse index in line with
68
- * what is left (`backfill`) and drops report state with no entry (`dropOrphanedReports`).
69
- */
70
52
  export async function pruneExpired(storage, now, exists = existsSync) {
71
53
  const children = childStore(storage);
72
- // Report keys are read before the roster: courier_spawn writes a child's entry before its first
73
- // report key, so a session seen here that spawns during the sweep has its entry in the read below.
74
54
  const reported = await children.reported().catch(() => new Map());
75
55
  const entries = await dropExpired(storage, await children.all(), now, exists);
76
56
  await Promise.all([children.backfill(entries), dropOrphanedReports(storage, reported, entries)]);
77
57
  }
78
- /** Removes the report keys read of sessions that have no roster entry. */
79
58
  async function dropOrphanedReports(storage, reported, entries) {
80
59
  const sessions = new Set(entries.map((entry) => entry.sessionID));
81
60
  await forgetReports(storage, reported, [...reported.keys()].filter((sessionID) => !sessions.has(sessionID)));