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
@@ -1,35 +1,24 @@
1
1
  import { finite, isNotFound, millis } from "../platform/json.js";
2
2
  import { contextOf, reportState, stalledAmong, subtree, turnIn, turnOf, waitsInInbox } from "../tree/index.js";
3
- import { isSettled, isStated, mergeParts, NOT_STATED, partStatus, replacingMention, revise, splitLinesOf, splitOf, unreadPart } from "./domain.js";
3
+ import { isSettled, isStated, mergeParts, NOT_STATED, partStatus, subagentStatus, replacingMention, revise, splitLinesOf, splitOf, unreadPart } from "./domain.js";
4
4
  import { memoryStore } from "./store.js";
5
- /** A session's replies, in order, each with when it was created, for its split lines; only the `finished` ones when asked. */
6
5
  const repliesOf = (messages, finished) => messages.flatMap((message) => message.type === "assistant" && (!finished || message.time?.completed !== undefined)
7
6
  ? [{ text: message.content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n"), at: finite(millis(message.time?.created)) }]
8
7
  : []);
9
- /** The lines in the replies that begin with `split:`, each with its reply's time. */
10
8
  const statedIn = (replies) => replies.flatMap(({ text, at }) => splitLinesOf(text).map((line) => (at === undefined ? { text: line } : { text: line, at })));
11
- /** How many courier_spawn calls of a session failed, refused by a limit mostly. */
12
9
  const refusedIn = (messages) => messages.reduce((n, message) => (message.type === "assistant" ? n + message.content.filter((part) => part.type === "tool" && part.name === "courier_spawn" && part.state.status === "error").length : n), 0);
13
- /** A part's context use and refused spawns, as its messages show them. */
14
10
  function useOf(messages, windows) {
15
11
  const context = contextOf(messages, windows);
16
12
  const refused = refusedIn(messages);
17
13
  return { ...(context !== undefined ? { context: Math.round(context * 100) / 100 } : {}), ...(refused ? { refused } : {}) };
18
14
  }
19
- /** A part's use as last recorded. */
20
15
  const keptUse = (last) => ({
21
16
  ...(last?.context !== undefined ? { context: last.context } : {}),
22
17
  ...(last?.refused ? { refused: last.refused } : {}),
23
18
  });
24
- /** A session's info, or `gone` when OpenCode no longer knows it; neither when it could not be read for another reason. */
25
19
  const infoOf = (ports, sessionID) => ports.session.get({ sessionID }).then((info) => ({ info, gone: false }), (error) => ({ gone: isNotFound(error) }));
26
- /**
27
- * One session of the tree as its record keeps it, without its use when its messages were not read for it (only when its turn
28
- * `ended` or it is `known` to no record yet), without a duration when its info was not, and whether it runs or is owed a turn.
29
- */
30
- async function part(ports, windows, { entry, depth }, stalled, at, known, ended) {
20
+ async function part(ports, windows, { entry, depth, subagent }, stalled, at, known, ended) {
31
21
  const { sessionID } = entry;
32
- // Read once: for its use, and for whether it runs when its times do not show rest.
33
22
  const readAt = Date.now();
34
23
  const messages = ended || !known ? ports.session.context({ sessionID }) : undefined;
35
24
  const [{ info, gone, running, endedAt }, use, report, waiting] = await Promise.all([
@@ -40,7 +29,7 @@ async function part(ports, windows, { entry, depth }, stalled, at, known, ended)
40
29
  return { ...one, ...(await turn) };
41
30
  }),
42
31
  messages?.then((read) => useOf(read, windows), () => undefined),
43
- reportState(ports.storage, sessionID).catch(() => undefined),
32
+ subagent ? undefined : reportState(ports.storage, sessionID).catch(() => undefined),
44
33
  waitsInInbox(ports, sessionID).catch(() => true),
45
34
  ]);
46
35
  const created = finite(millis(info?.time.created)) ?? entry.createdAt;
@@ -51,31 +40,25 @@ async function part(ports, windows, { entry, depth }, stalled, at, known, ended)
51
40
  title: info?.title ?? entry.title,
52
41
  depth,
53
42
  isolated: entry.isolated,
43
+ ...(subagent ? { subagent } : {}),
54
44
  ...(entry.group ? { group: entry.group } : {}),
55
- status: partStatus({ stopped: report?.stopped ?? false, stalled, report }),
45
+ status: subagent ? subagentStatus({ running, stalled, outcome: info?.outcome }) : partStatus({ stopped: report?.stopped ?? false, stalled, report }),
56
46
  ...(info ? { duration: Math.max(0, end - created) } : {}),
57
47
  ...(cost !== undefined ? { cost } : {}),
58
48
  };
59
- // A courier message waiting to be taken keeps it unfinished, unless OpenCode no longer knows it, which then takes none.
60
49
  return { record, use, running: running || (waiting && !gone), unread: info === undefined, gone };
61
50
  }
62
- /** Of the parts last recorded, those the roster has forgotten before they settled whose session OpenCode no longer knows. */
63
51
  async function goneSince(ports, parts, onRoster) {
64
52
  const unsettled = parts.filter((one) => !onRoster.has(one.sessionID) && !isSettled(one.status));
65
53
  const looked = await Promise.all(unsettled.map(async ({ sessionID }) => ({ sessionID, ...(await infoOf(ports, sessionID)) })));
66
54
  return new Set(looked.flatMap((one) => (one.gone ? [one.sessionID] : [])));
67
55
  }
68
- /** The top's turn: from the transcript read at `readAt`, unless a stamp since then moved its times, which `turnOf` reads again. */
69
56
  async function topTurnAfter(ports, top, info, messages, readAt) {
70
57
  const fresh = await ports.session.get({ sessionID: top }).catch(() => undefined);
71
58
  if (fresh && millis(fresh.time.updated) > readAt)
72
59
  return turnOf(ports.session, top, fresh.time);
73
60
  return messages ? turnIn(ports.session, top, info.time, messages, readAt) : turnOf(ports.session, top, info.time);
74
61
  }
75
- /**
76
- * Writes the record of the tree under `top` as it stands at `at`, after the turn of `ended`; a part the roster has forgotten
77
- * keeps its last recorded state, unless found gone before it settled. Nothing for a top unknown to OpenCode or with memory off.
78
- */
79
62
  export async function refresh(ports, state, top, at, ended) {
80
63
  let info;
81
64
  try {
@@ -89,9 +72,7 @@ export async function refresh(ports, state, top, at, ended) {
89
72
  if (state.off.has(info.location.directory))
90
73
  return undefined;
91
74
  const store = memoryStore(ports.storage);
92
- const [previous, { nodes }] = await Promise.all([store.get(info.projectID, top), subtree(ports.storage, top)]);
93
- // The split lines are in the top's own replies, so only its own turn end can revise one stated already; another's reads
94
- // them while none is stated, and only the replies finished, never a line the top is still writing.
75
+ const [previous, { nodes }] = await Promise.all([store.get(info.projectID, top), subtree(ports.storage, top, undefined, ports.session)]);
95
76
  const own = ended === top;
96
77
  const topReadAt = Date.now();
97
78
  const topMessages = own || !isStated(previous?.split ?? NOT_STATED) ? ports.session.context({ sessionID: top }) : undefined;
@@ -103,11 +84,8 @@ export async function refresh(ports, state, top, at, ended) {
103
84
  Promise.all(nodes.map((node) => part(ports, windows, node, stalled.has(node.entry.sessionID), at, known.has(node.entry.sessionID), node.entry.sessionID === ended))),
104
85
  goneSince(ports, previous?.parts ?? [], new Set(nodes.map((node) => node.entry.sessionID))),
105
86
  ]);
106
- // A courier message waiting in the top's inbox is a turn its transcript does not show yet, as for a part; a store that
107
- // cannot be read counts as one waiting, as `runs` counts a transcript it cannot read as running.
108
87
  const topTurn = topTurnAfter(ports, top, info, topMessages, topReadAt);
109
88
  const [said, topRunning, topOwed] = await Promise.all([replies, topTurn.then((turn) => turn.running), waitsInInbox(ports, top).catch(() => true)]);
110
- // Read again just before the write: a courier_recall or courier_note call, or another turn end's refresh, wrote meanwhile.
111
89
  const latest = (await store.get(info.projectID, top).catch(() => previous)) ?? previous;
112
90
  const last = new Map(latest?.parts.map((one) => [one.sessionID, one]));
113
91
  const current = read.map(({ record, use, unread, gone }) => {
@@ -118,15 +96,14 @@ export async function refresh(ports, state, top, at, ended) {
118
96
  const parts = mergeParts(latest?.parts ?? [], [...current, ...forgotten]);
119
97
  const recalled = latest?.recalled ?? previous?.recalled;
120
98
  const lesson = latest?.lesson ?? previous?.lesson;
121
- // The first stated line stays even when compaction has dropped its reply; a later one is a revision.
99
+ // the transcript starts at the last compaction (api-notes)
122
100
  const first = latest?.split ?? NOT_STATED;
123
101
  const found = splitOf(first, said.map((reply) => reply.text).join("\n"));
124
102
  const stated = statedIn(said);
125
103
  const replaced = found !== first;
126
104
  const { split, revisions: recorded } = replaced ? replacingMention(found, latest?.revisions) : { split: first, revisions: latest?.revisions };
127
- // The line that replaced a mention is the first stated, so beside revisions already recorded it is not one of them too.
128
105
  const since = replaced && recorded ? stated.slice(1) : stated;
129
- const revisions = split === NOT_STATED ? undefined : revise({ split, revisions: recorded }, since, at, parts.length);
106
+ const revisions = split === NOT_STATED ? undefined : revise({ split, revisions: recorded }, since, at, parts.filter((one) => !one.subagent).length);
130
107
  const record = {
131
108
  startedAt: finite(millis(info.time.created)) ?? previous?.startedAt ?? at,
132
109
  movedAt: at,
@@ -1,14 +1,8 @@
1
1
  import type { Plugin } from "@opencode/plugin";
2
2
  import type { MemoryPorts } from "./ports.js";
3
3
  import { type Records } from "./rpc.js";
4
- /** What the memory's RPC runs on: one instance's ports. */
5
4
  export type MemoryRpcPorts = Pick<MemoryPorts, "storage" | "projectID" | "memory" | "now">;
6
- /**
7
- * The records of the ports' project, pinned first and then newest first, as the memory view lists them; nothing, and
8
- * `memory` false, with the option off. Read from the store alone: unlike courier_recall, a look from the terminal is noted on no record.
9
- */
10
5
  export declare function records(ports: Pick<MemoryRpcPorts, "storage" | "projectID" | "memory">): Promise<Records>;
11
- /** Serves the memory's RPC at the plugin's location, after the tree's and the person's; disposed with the plugin. */
12
6
  export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "register">, ports: MemoryRpcPorts): Promise<import("@opencode/plugin/promise/rpc").RpcRegistration<{
13
7
  readonly id: "courier-memory";
14
8
  readonly methods: {
@@ -32,6 +26,7 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
32
26
  readonly depth: number;
33
27
  readonly context?: number | undefined;
34
28
  readonly group?: string | undefined;
29
+ readonly subagent?: boolean | undefined;
35
30
  readonly cost?: number | undefined;
36
31
  readonly refused?: number | undefined;
37
32
  readonly duration?: number | undefined;
@@ -72,6 +67,7 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
72
67
  readonly depth: number;
73
68
  readonly context?: number | undefined;
74
69
  readonly group?: string | undefined;
70
+ readonly subagent?: boolean | undefined;
75
71
  readonly cost?: number | undefined;
76
72
  readonly refused?: number | undefined;
77
73
  readonly duration?: number | undefined;
@@ -126,6 +122,7 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
126
122
  readonly title: import("effect/Schema").String;
127
123
  readonly depth: import("effect/Schema").Int;
128
124
  readonly isolated: import("effect/Schema").Boolean;
125
+ readonly subagent: import("effect/Schema").optionalKey<import("effect/Schema").Boolean>;
129
126
  readonly group: import("effect/Schema").optionalKey<import("effect/Schema").String>;
130
127
  readonly status: import("effect/Schema").Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
131
128
  readonly duration: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
@@ -3,19 +3,13 @@ import { forget } from "./forget.js";
3
3
  import { pin } from "./pin.js";
4
4
  import { memoryRpc } from "./rpc.js";
5
5
  import { memoryStore } from "./store.js";
6
- /** A stored record as the RPC gives it: without its project, which the listing names once, nor the split line's revisions, which its schema lacks. */
7
6
  const toRecord = ({ projectID: _project, revisions: _revisions, ...record }) => record;
8
- /**
9
- * The records of the ports' project, pinned first and then newest first, as the memory view lists them; nothing, and
10
- * `memory` false, with the option off. Read from the store alone: unlike courier_recall, a look from the terminal is noted on no record.
11
- */
12
7
  export async function records(ports) {
13
8
  const { projectID } = ports;
14
9
  if (!ports.memory)
15
10
  return { memory: false, projectID, records: [] };
16
11
  return { memory: true, projectID, records: pinnedFirst(await memoryStore(ports.storage).ofProject(projectID)).map(toRecord) };
17
12
  }
18
- /** Serves the memory's RPC at the plugin's location, after the tree's and the person's; disposed with the plugin. */
19
13
  export function registerMemoryRpc(rpc, ports) {
20
14
  return rpc.register(memoryRpc, {
21
15
  records: () => records(ports),
@@ -1,13 +1,11 @@
1
1
  import { Schema } from "effect";
2
- /** A part's statuses, as `memory/domain.ts`'s `PART_STATUSES` has them; a unit test keeps the two equal. */
3
2
  export declare const PART_STATUSES: readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"];
4
- /** One session started in a tree, as its record keeps it: durations in milliseconds, context use a share of the model's window. */
5
3
  declare const Part: Schema.Struct<{
6
4
  readonly sessionID: Schema.String;
7
5
  readonly title: Schema.String;
8
- /** 1 for a session the top started. */
9
6
  readonly depth: Schema.Int;
10
7
  readonly isolated: Schema.Boolean;
8
+ readonly subagent: Schema.optionalKey<Schema.Boolean>;
11
9
  readonly group: Schema.optionalKey<Schema.String>;
12
10
  readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
13
11
  readonly duration: Schema.optionalKey<Schema.Number>;
@@ -15,13 +13,11 @@ declare const Part: Schema.Struct<{
15
13
  readonly context: Schema.optionalKey<Schema.Number>;
16
14
  readonly refused: Schema.optionalKey<Schema.Int>;
17
15
  }>;
18
- /** The one line the tree's orchestrator wrote with courier_note: when, and from which depth when not the top's own. */
19
16
  declare const Lesson: Schema.Struct<{
20
17
  readonly text: Schema.String;
21
18
  readonly at: Schema.Number;
22
19
  readonly depth: Schema.optionalKey<Schema.Int>;
23
20
  }>;
24
- /** A tree's record as `courier_recall` reads it, with the top session it is kept under; times in epoch milliseconds. */
25
21
  declare const Record: Schema.Struct<{
26
22
  readonly sessionID: Schema.String;
27
23
  readonly startedAt: Schema.Number;
@@ -38,22 +34,20 @@ declare const Record: Schema.Struct<{
38
34
  readonly at: Schema.Number;
39
35
  readonly depth: Schema.optionalKey<Schema.Int>;
40
36
  }>>;
41
- /** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
42
37
  readonly reads: Schema.optionalKey<Schema.Struct<{
43
38
  readonly shown: Schema.Int;
44
39
  readonly read: Schema.Int;
45
40
  readonly at: Schema.Number;
46
41
  }>>;
47
- /** The person's pin, when; a pinned record is listed first and never swept. */
48
42
  readonly pinned: Schema.optionalKey<Schema.Struct<{
49
43
  readonly at: Schema.Number;
50
44
  }>>;
51
45
  readonly parts: Schema.$Array<Schema.Struct<{
52
46
  readonly sessionID: Schema.String;
53
47
  readonly title: Schema.String;
54
- /** 1 for a session the top started. */
55
48
  readonly depth: Schema.Int;
56
49
  readonly isolated: Schema.Boolean;
50
+ readonly subagent: Schema.optionalKey<Schema.Boolean>;
57
51
  readonly group: Schema.optionalKey<Schema.String>;
58
52
  readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
59
53
  readonly duration: Schema.optionalKey<Schema.Number>;
@@ -62,7 +56,6 @@ declare const Record: Schema.Struct<{
62
56
  readonly refused: Schema.optionalKey<Schema.Int>;
63
57
  }>>;
64
58
  }>;
65
- /** The records of the project of the location asked, pinned first and then newest first; none, and `memory` false, when the option is off there. */
66
59
  declare const Records: Schema.Struct<{
67
60
  readonly memory: Schema.Boolean;
68
61
  readonly projectID: Schema.String;
@@ -82,22 +75,20 @@ declare const Records: Schema.Struct<{
82
75
  readonly at: Schema.Number;
83
76
  readonly depth: Schema.optionalKey<Schema.Int>;
84
77
  }>>;
85
- /** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
86
78
  readonly reads: Schema.optionalKey<Schema.Struct<{
87
79
  readonly shown: Schema.Int;
88
80
  readonly read: Schema.Int;
89
81
  readonly at: Schema.Number;
90
82
  }>>;
91
- /** The person's pin, when; a pinned record is listed first and never swept. */
92
83
  readonly pinned: Schema.optionalKey<Schema.Struct<{
93
84
  readonly at: Schema.Number;
94
85
  }>>;
95
86
  readonly parts: Schema.$Array<Schema.Struct<{
96
87
  readonly sessionID: Schema.String;
97
88
  readonly title: Schema.String;
98
- /** 1 for a session the top started. */
99
89
  readonly depth: Schema.Int;
100
90
  readonly isolated: Schema.Boolean;
91
+ readonly subagent: Schema.optionalKey<Schema.Boolean>;
101
92
  readonly group: Schema.optionalKey<Schema.String>;
102
93
  readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
103
94
  readonly duration: Schema.optionalKey<Schema.Number>;
@@ -107,11 +98,9 @@ declare const Records: Schema.Struct<{
107
98
  }>>;
108
99
  }>>;
109
100
  }>;
110
- /** What a forget did: removed the record, dropped only its lesson, or found nothing to forget. */
111
101
  declare const Forgotten: Schema.Struct<{
112
102
  readonly outcome: Schema.Literals<readonly ["record", "lesson", "none"]>;
113
103
  }>;
114
- /** What a pin did: pinned the record, unpinned it, or found no record to pin. */
115
104
  declare const Pinned: Schema.Struct<{
116
105
  readonly outcome: Schema.Literals<readonly ["pinned", "unpinned", "none"]>;
117
106
  }>;
@@ -139,6 +128,7 @@ export declare const memoryRpc: {
139
128
  readonly title: string;
140
129
  readonly depth: number;
141
130
  readonly isolated: boolean;
131
+ readonly subagent?: boolean | undefined;
142
132
  readonly group?: string | undefined;
143
133
  readonly duration?: number | undefined;
144
134
  readonly cost?: number | undefined;
@@ -179,6 +169,7 @@ export declare const memoryRpc: {
179
169
  readonly title: string;
180
170
  readonly depth: number;
181
171
  readonly isolated: boolean;
172
+ readonly subagent?: boolean | undefined;
182
173
  readonly group?: string | undefined;
183
174
  readonly duration?: number | undefined;
184
175
  readonly cost?: number | undefined;
@@ -226,22 +217,20 @@ export declare const memoryRpc: {
226
217
  readonly at: Schema.Number;
227
218
  readonly depth: Schema.optionalKey<Schema.Int>;
228
219
  }>>;
229
- /** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
230
220
  readonly reads: Schema.optionalKey<Schema.Struct<{
231
221
  readonly shown: Schema.Int;
232
222
  readonly read: Schema.Int;
233
223
  readonly at: Schema.Number;
234
224
  }>>;
235
- /** The person's pin, when; a pinned record is listed first and never swept. */
236
225
  readonly pinned: Schema.optionalKey<Schema.Struct<{
237
226
  readonly at: Schema.Number;
238
227
  }>>;
239
228
  readonly parts: Schema.$Array<Schema.Struct<{
240
229
  readonly sessionID: Schema.String;
241
230
  readonly title: Schema.String;
242
- /** 1 for a session the top started. */
243
231
  readonly depth: Schema.Int;
244
232
  readonly isolated: Schema.Boolean;
233
+ readonly subagent: Schema.optionalKey<Schema.Boolean>;
245
234
  readonly group: Schema.optionalKey<Schema.String>;
246
235
  readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
247
236
  readonly duration: Schema.optionalKey<Schema.Number>;
@@ -252,7 +241,6 @@ export declare const memoryRpc: {
252
241
  }>>;
253
242
  }>;
254
243
  };
255
- /** Forgets the record of the tree under `sessionID` in the project asked, or with `lesson` only its lesson, keeping the facts. */
256
244
  readonly forget: {
257
245
  readonly input: import("effect/StandardSchema").StandardSchemaV1<{
258
246
  readonly sessionID: string;
@@ -272,7 +260,6 @@ export declare const memoryRpc: {
272
260
  readonly outcome: Schema.Literals<readonly ["record", "lesson", "none"]>;
273
261
  }>;
274
262
  };
275
- /** Pins the record of the tree under `sessionID` in the project asked, or with `pinned` false unpins it. */
276
263
  readonly pin: {
277
264
  readonly input: import("effect/StandardSchema").StandardSchemaV1<{
278
265
  readonly sessionID: string;
@@ -1,19 +1,13 @@
1
- /**
2
- * The memory's RPC, the contract its TUI part, the memory view, and its server half share. Standard Schema values, as
3
- * the tree's and the person's; imports only `@opencode/plugin/rpc` and `effect`.
4
- */
5
1
  import { Rpc } from "@opencode/plugin/rpc";
6
2
  import { Schema } from "effect";
7
3
  const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
8
- /** A part's statuses, as `memory/domain.ts`'s `PART_STATUSES` has them; a unit test keeps the two equal. */
9
4
  export const PART_STATUSES = ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"];
10
- /** One session started in a tree, as its record keeps it: durations in milliseconds, context use a share of the model's window. */
11
5
  const Part = Schema.Struct({
12
6
  sessionID: Schema.String,
13
7
  title: Schema.String,
14
- /** 1 for a session the top started. */
15
8
  depth: Count,
16
9
  isolated: Schema.Boolean,
10
+ subagent: Schema.optionalKey(Schema.Boolean),
17
11
  group: Schema.optionalKey(Schema.String),
18
12
  status: Schema.Literals(PART_STATUSES),
19
13
  duration: Schema.optionalKey(Schema.Number),
@@ -21,9 +15,7 @@ const Part = Schema.Struct({
21
15
  context: Schema.optionalKey(Schema.Number),
22
16
  refused: Schema.optionalKey(Count),
23
17
  });
24
- /** The one line the tree's orchestrator wrote with courier_note: when, and from which depth when not the top's own. */
25
18
  const Lesson = Schema.Struct({ text: Schema.String, at: Schema.Number, depth: Schema.optionalKey(Count) });
26
- /** A tree's record as `courier_recall` reads it, with the top session it is kept under; times in epoch milliseconds. */
27
19
  const Record = Schema.Struct({
28
20
  sessionID: Schema.String,
29
21
  startedAt: Schema.Number,
@@ -33,21 +25,16 @@ const Record = Schema.Struct({
33
25
  finished: Schema.Boolean,
34
26
  recalled: Schema.optionalKey(Schema.Struct({ at: Schema.optionalKey(Schema.Number), calls: Count })),
35
27
  lesson: Schema.optionalKey(Lesson),
36
- /** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
37
28
  reads: Schema.optionalKey(Schema.Struct({ shown: Count, read: Count, at: Schema.Number })),
38
- /** The person's pin, when; a pinned record is listed first and never swept. */
39
29
  pinned: Schema.optionalKey(Schema.Struct({ at: Schema.Number })),
40
30
  parts: Schema.Array(Part),
41
31
  });
42
- /** The records of the project of the location asked, pinned first and then newest first; none, and `memory` false, when the option is off there. */
43
32
  const Records = Schema.Struct({
44
33
  memory: Schema.Boolean,
45
34
  projectID: Schema.String,
46
35
  records: Schema.Array(Record),
47
36
  });
48
- /** What a forget did: removed the record, dropped only its lesson, or found nothing to forget. */
49
37
  const Forgotten = Schema.Struct({ outcome: Schema.Literals(["record", "lesson", "none"]) });
50
- /** What a pin did: pinned the record, unpinned it, or found no record to pin. */
51
38
  const Pinned = Schema.Struct({ outcome: Schema.Literals(["pinned", "unpinned", "none"]) });
52
39
  export const memoryRpc = Rpc.define({
53
40
  id: "courier-memory",
@@ -56,12 +43,10 @@ export const memoryRpc = Rpc.define({
56
43
  input: Schema.toStandardSchemaV1(Schema.Struct({})),
57
44
  output: Schema.toStandardSchemaV1(Records),
58
45
  },
59
- /** Forgets the record of the tree under `sessionID` in the project asked, or with `lesson` only its lesson, keeping the facts. */
60
46
  forget: {
61
47
  input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, lesson: Schema.optionalKey(Schema.Boolean) })),
62
48
  output: Schema.toStandardSchemaV1(Forgotten),
63
49
  },
64
- /** Pins the record of the tree under `sessionID` in the project asked, or with `pinned` false unpins it. */
65
50
  pin: {
66
51
  input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, pinned: Schema.Boolean })),
67
52
  output: Schema.toStandardSchemaV1(Pinned),
@@ -2,13 +2,8 @@ import { type Storage } from "../platform/storage.js";
2
2
  import { type TreeRecord } from "./domain.js";
3
3
  import type { MemoryStore } from "./ports.js";
4
4
  export declare const recordKey: (projectID: string, sessionID: string) => string;
5
- /** The key prefix of a project's records. */
6
5
  export declare const projectPrefix: (projectID: string) => string;
7
- /** The person's pin on a record, `{ at }`; the key's presence pins. */
8
6
  export declare const pinKey: (projectID: string, sessionID: string) => string;
9
- /** The reads of a record by the tree under `by`, `{ shown, read, at }`, written only by that tree's sessions. */
10
7
  export declare const readsKey: (projectID: string, sessionID: string, by: string) => string;
11
- /** A record as stored, or undefined for a value that is not one; its reads and pin lie beside it (`beside`). */
12
8
  export declare function readRecord(value: unknown): TreeRecord | undefined;
13
- /** The `MemoryStore` over the plugin's storage. */
14
9
  export declare function memoryStore(storage: Storage): MemoryStore;
@@ -1,17 +1,11 @@
1
1
  import { finite, num, obj, str } from "../platform/json.js";
2
2
  import { scanEntries } from "../platform/storage.js";
3
3
  import { isPartStatus, lessonText, MAX_REVISIONS, NOT_STATED, noteRead, sumReads, } from "./domain.js";
4
- // The memory's storage: the only file that knows its keys and layout. A record is read leniently, as one written by hand
5
- // with `e2e/kv.ts set` is; beside it, under its key and each with its own writer, lie the person's pin and each reader tree's reads.
6
4
  const PREFIX = "memory/";
7
5
  export const recordKey = (projectID, sessionID) => `${PREFIX}${projectID}/${sessionID}`;
8
- /** The key prefix of a project's records. */
9
6
  export const projectPrefix = (projectID) => `${PREFIX}${projectID}/`;
10
- /** The key prefix of what lies beside a record: its pin and its reads. */
11
7
  const besidePrefix = (projectID, sessionID) => `${recordKey(projectID, sessionID)}/`;
12
- /** The person's pin on a record, `{ at }`; the key's presence pins. */
13
8
  export const pinKey = (projectID, sessionID) => `${besidePrefix(projectID, sessionID)}pin`;
14
- /** The reads of a record by the tree under `by`, `{ shown, read, at }`, written only by that tree's sessions. */
15
9
  export const readsKey = (projectID, sessionID, by) => `${besidePrefix(projectID, sessionID)}reads/${by}`;
16
10
  function readPart(value) {
17
11
  const given = obj(value);
@@ -28,6 +22,7 @@ function readPart(value) {
28
22
  title: str(given.title) ?? "",
29
23
  depth: num(given.depth) ?? 1,
30
24
  isolated: given.isolated === true,
25
+ ...(given.subagent === true ? { subagent: true } : {}),
31
26
  ...(group ? { group } : {}),
32
27
  status: isPartStatus(given.status) ? given.status : "out",
33
28
  ...optional("duration"),
@@ -44,7 +39,6 @@ function readRecalled(value) {
44
39
  const at = num(given.at);
45
40
  return at === undefined ? { calls } : { at, calls };
46
41
  }
47
- /** A lesson as `{ text, at, depth }`, or as a bare string written by hand, dated `movedAt`; none for an empty text. */
48
42
  function readLesson(value, movedAt) {
49
43
  const given = typeof value === "string" ? { text: value } : obj(value);
50
44
  const { text } = lessonText(str(given.text) ?? "");
@@ -53,7 +47,6 @@ function readLesson(value, movedAt) {
53
47
  const depth = num(given.depth);
54
48
  return { text, at: num(given.at) ?? movedAt, ...(depth !== undefined && Number.isInteger(depth) && depth > 0 ? { depth } : {}) };
55
49
  }
56
- /** The revisions as `{ text, at, parts }`, the newest `MAX_REVISIONS`, leaving out an entry without a text, a time or a count of parts. */
57
50
  function readRevisions(value) {
58
51
  if (!Array.isArray(value))
59
52
  return [];
@@ -66,7 +59,6 @@ function readRevisions(value) {
66
59
  });
67
60
  return revisions.slice(-MAX_REVISIONS);
68
61
  }
69
- /** A record as stored, or undefined for a value that is not one; its reads and pin lie beside it (`beside`). */
70
62
  export function readRecord(value) {
71
63
  const given = obj(value);
72
64
  if (!Array.isArray(given.parts))
@@ -87,7 +79,6 @@ export function readRecord(value) {
87
79
  parts: given.parts.map(readPart).filter((part) => part !== undefined),
88
80
  };
89
81
  }
90
- /** One tree's reads as `{ shown, read, at }`; a count missing is 0, an entry without a time is none. */
91
82
  function readReads(value) {
92
83
  const given = obj(value);
93
84
  const at = num(given.at);
@@ -96,17 +87,13 @@ function readReads(value) {
96
87
  const count = (field) => Math.max(0, Math.floor(num(given[field]) ?? 0));
97
88
  return { shown: count("shown"), read: count("read"), at };
98
89
  }
99
- /** The pin as `{ at }`: any value under the key pins, dated 0 when it says no time. */
100
90
  const readPinned = (value) => ({ at: num(obj(value).at) ?? 0 });
101
- /** A record with what lies beside it: its readers' reads as one, and its pin. */
102
91
  function beside(record, { pinned, reads }) {
103
92
  const { parts, ...facts } = record;
104
93
  const summed = sumReads(reads);
105
94
  return { ...facts, ...(summed ? { reads: summed } : {}), ...(pinned ? { pinned } : {}), parts };
106
95
  }
107
- /** The key's path under `memory/`, split: `[projectID, sessionID]` a record, then `pin`, or `reads` and the reader. */
108
96
  const pathOf = (prefix, key) => `${prefix.slice(PREFIX.length)}${key}`.split("/");
109
- /** Sorts a scan's entries by record: the record's value, when the scan held it, and what lies beside it. */
110
97
  function group(prefix, entries) {
111
98
  const groups = new Map();
112
99
  const of = (projectID, sessionID) => {
@@ -134,7 +121,6 @@ function group(prefix, entries) {
134
121
  return { projectID, sessionID, ...found };
135
122
  });
136
123
  }
137
- /** The `MemoryStore` over the plugin's storage. */
138
124
  export function memoryStore(storage) {
139
125
  const scanned = async (prefix) => group(prefix, await scanEntries(storage, prefix));
140
126
  const under = async (prefix) => (await scanned(prefix)).flatMap(({ projectID, sessionID, record: value, beside: others }) => {
@@ -147,7 +133,6 @@ export function memoryStore(storage) {
147
133
  const record = readRecord(value);
148
134
  return record && beside(record, others[0]?.beside ?? { reads: [] });
149
135
  },
150
- // What lies beside the record is never written with it, so no refresh or note can write over a pin or a count.
151
136
  async put(projectID, sessionID, { reads: _reads, pinned: _pinned, ...record }) {
152
137
  await storage.set(recordKey(projectID, sessionID), { ...record, parts: record.parts.map((part) => ({ ...part })) });
153
138
  },
@@ -1,7 +1,3 @@
1
1
  import type { Storage } from "../platform/storage.js";
2
2
  import { type StoredRecord } from "./domain.js";
3
- /**
4
- * On loading: drops, per project, the records `keep` does not at `now`, the pinned never among them, and the reads or
5
- * pin left beside a record removed meanwhile. Returns the records dropped.
6
- */
7
3
  export declare function sweepRecords(storage: Storage, now: number): Promise<StoredRecord[]>;
@@ -1,9 +1,5 @@
1
1
  import { keep } from "./domain.js";
2
2
  import { memoryStore } from "./store.js";
3
- /**
4
- * On loading: drops, per project, the records `keep` does not at `now`, the pinned never among them, and the reads or
5
- * pin left beside a record removed meanwhile. Returns the records dropped.
6
- */
7
3
  export async function sweepRecords(storage, now) {
8
4
  const store = memoryStore(storage);
9
5
  const byProject = new Map();
@@ -1,46 +1,21 @@
1
1
  import { type Lesson, type StoredRecord } from "./domain.js";
2
- /** The tool's name, which the skill and the role part name too. */
3
2
  export declare const RECALL_TOOL = "courier_recall";
4
- /** The lesson tool's name, which the skill's last step names. */
5
3
  export declare const NOTE_TOOL = "courier_note";
6
- /** A duration in milliseconds as a model reads it: seconds under half a minute, else whole minutes, else hours and minutes. */
7
4
  export declare function duration(ms: number): string;
8
- /** How long ago a record last moved: a duration under a day, else whole days, which a record kept for months is read in. */
9
5
  export declare function age(ms: number): string;
10
- /** A lesson as the listing and the note's result show it: dated, and from which depth when a session below the top wrote it. */
11
6
  export declare function lessonLine(lesson: Lesson): string;
12
- /**
13
- * One record as the listing shows it: its top, when it started, its title, its split line and its latest revision as prose,
14
- * its parts, how long it ran, how long ago it last moved, what it cost, and whether the person pinned it.
15
- */
16
7
  export declare function recordLine(record: StoredRecord, top: string, now: number): string;
17
- /** What courier_recall says when the memory option is off. */
18
8
  export declare const RECALL_OFF = "Memory is off: the plugin's memory option is false, so no record of a session tree is kept or listed in this project. Decide from the task alone.";
19
- /**
20
- * The listing: every record of the project, the pinned first and then newest first, the caller's own tree marked, each
21
- * aged against `now`.
22
- */
23
9
  export declare function recallListText(records: ReadonlyArray<StoredRecord>, top: string, now: number): string;
24
- /** One record in full, as JSON: durations in milliseconds, times in epoch milliseconds, context use as a share of the model's window. */
25
10
  export declare function recallRecordText(record: StoredRecord): string;
26
11
  export declare const recallMissingText: (sessionID: string) => string;
27
- /** What courier_note says once the lesson is written: where, and what it replaced. */
28
12
  export declare function noteText(top: string, lesson: Lesson, replaced: Lesson | undefined): string;
29
- /** What courier_note says when the memory option is off. */
30
13
  export declare const NOTE_OFF = "Not noted: the plugin's memory option is false, so no record of a session tree is kept in this project to write a lesson on.";
31
- export declare const noteLeafRefusal: (depth: number, maxDepth: number) => string;
14
+ export declare const noteBelowRefusal: (depth: number, top: string) => string;
32
15
  export declare const NOTE_EMPTY = "Not noted: text is empty. Write one line, what the next orchestrator in this project should know about running a tree like this one, with the job it comes from and the reason, or nothing.";
33
- /** The refusal of a lesson longer than `LESSON_MAX` as one line: nothing is written, and the earlier lesson stays. */
34
16
  export declare const noteLongRefusal: (length: number) => string;
35
17
  export declare const noteUnrecordedRefusal: (top: string) => string;
36
- /** The key of the session instruction entry carrying the project's lessons, `<context key="lessons">`. */
37
18
  export declare const LESSONS_KEY = "lessons";
38
- /** How every lessons entry begins. */
39
19
  export declare const LESSONS_PREFIX = "opencode-courier lessons:";
40
- /** How many lessons the entry carries: the records' order, pinned first, then newest first. */
41
20
  export declare const LESSONS_MAX = 5;
42
- /**
43
- * The lessons entry: the lessons of the project's records as courier_recall lists them, pinned first and then newest first,
44
- * at most `LESSONS_MAX`, each with its tree's title; undefined while no record holds one.
45
- */
46
21
  export declare function lessonsEntry(records: ReadonlyArray<StoredRecord>): string | undefined;