opencode-courier 0.13.7 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/dist/index.d.ts +0 -8
- package/dist/index.js +6 -53
- package/dist/kernel/protocol.d.ts +0 -14
- package/dist/kernel/protocol.js +1 -15
- package/dist/kernel/tool.d.ts +0 -2
- package/dist/kernel/tool.js +0 -1
- package/dist/memory/domain.d.ts +6 -85
- package/dist/memory/domain.js +7 -63
- package/dist/memory/events.d.ts +0 -11
- package/dist/memory/events.js +1 -12
- package/dist/memory/forget.d.ts +0 -5
- package/dist/memory/forget.js +0 -4
- package/dist/memory/index.js +0 -2
- package/dist/memory/lessons.d.ts +0 -8
- package/dist/memory/lessons.js +0 -9
- package/dist/memory/note.d.ts +0 -9
- package/dist/memory/note.js +0 -5
- package/dist/memory/pin.d.ts +0 -5
- package/dist/memory/pin.js +0 -4
- package/dist/memory/ports.d.ts +2 -22
- package/dist/memory/recall.d.ts +0 -5
- package/dist/memory/recall.js +0 -12
- package/dist/memory/record.d.ts +0 -4
- package/dist/memory/record.js +8 -31
- package/dist/memory/rpc-handlers.d.ts +3 -6
- package/dist/memory/rpc-handlers.js +0 -6
- package/dist/memory/rpc.d.ts +6 -19
- package/dist/memory/rpc.js +1 -16
- package/dist/memory/store.d.ts +0 -5
- package/dist/memory/store.js +1 -16
- package/dist/memory/sweep.d.ts +0 -4
- package/dist/memory/sweep.js +0 -4
- package/dist/memory/text.d.ts +0 -25
- package/dist/memory/text.js +6 -36
- package/dist/memory/tools.d.ts +0 -2
- package/dist/memory/tools.js +0 -2
- package/dist/memory/tui.d.ts +1 -35
- package/dist/person/events.d.ts +0 -16
- package/dist/person/events.js +3 -30
- package/dist/person/form.d.ts +0 -9
- package/dist/person/form.js +0 -10
- package/dist/person/index.d.ts +0 -2
- package/dist/person/permission.d.ts +0 -34
- package/dist/person/permission.js +2 -28
- package/dist/person/ports.d.ts +0 -16
- package/dist/person/ports.js +0 -1
- package/dist/person/question/answer.d.ts +0 -28
- package/dist/person/question/answer.js +0 -49
- package/dist/person/question/index.js +0 -2
- package/dist/person/question/lifecycle.d.ts +0 -26
- package/dist/person/question/lifecycle.js +1 -32
- package/dist/person/question/pure.d.ts +0 -11
- package/dist/person/question/pure.js +0 -11
- package/dist/person/question/relay.d.ts +0 -1
- package/dist/person/question/relay.js +5 -67
- package/dist/person/question/runtime.d.ts +0 -14
- package/dist/person/question/runtime.js +1 -26
- package/dist/person/question/shared.d.ts +0 -17
- package/dist/person/question/shared.js +0 -20
- package/dist/person/question/types.d.ts +0 -44
- package/dist/person/rpc-handlers.d.ts +0 -7
- package/dist/person/rpc-handlers.js +0 -6
- package/dist/person/rpc.d.ts +0 -20
- package/dist/person/rpc.js +0 -16
- package/dist/person/text.d.ts +0 -45
- package/dist/person/text.js +2 -54
- package/dist/person/tools.d.ts +0 -3
- package/dist/person/tools.js +1 -3
- package/dist/person/tui.d.ts +0 -21
- package/dist/platform/bounded.d.ts +0 -8
- package/dist/platform/bounded.js +0 -8
- package/dist/platform/events.d.ts +0 -18
- package/dist/platform/events.js +3 -22
- package/dist/platform/hub.d.ts +0 -113
- package/dist/platform/hub.js +1 -76
- package/dist/platform/json.d.ts +0 -3
- package/dist/platform/json.js +1 -3
- package/dist/platform/storage.d.ts +0 -7
- package/dist/platform/storage.js +0 -6
- package/dist/platform/version.d.ts +0 -9
- package/dist/platform/version.js +0 -8
- package/dist/rpc.js +0 -1
- package/dist/scheduling/index.d.ts +0 -1
- package/dist/scheduling/index.js +0 -1
- package/dist/scheduling/later.d.ts +0 -11
- package/dist/scheduling/later.js +0 -14
- package/dist/scheduling/text.d.ts +0 -1
- package/dist/scheduling/text.js +0 -1
- package/dist/scheduling/tools.d.ts +0 -1
- package/dist/scheduling/tools.js +1 -5
- package/dist/skill.d.ts +1 -9
- package/dist/skill.js +97 -118
- package/dist/tree/cleanup.d.ts +0 -20
- package/dist/tree/cleanup.js +0 -26
- package/dist/tree/context.d.ts +0 -5
- package/dist/tree/context.js +0 -8
- package/dist/tree/domain.d.ts +0 -64
- package/dist/tree/domain.js +7 -45
- package/dist/tree/events.d.ts +0 -36
- package/dist/tree/events.js +4 -49
- package/dist/tree/forget.d.ts +0 -15
- package/dist/tree/forget.js +0 -18
- package/dist/tree/group.d.ts +0 -22
- package/dist/tree/group.js +1 -37
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.js +1 -3
- package/dist/tree/limits.d.ts +0 -4
- package/dist/tree/limits.js +1 -12
- package/dist/tree/mascot.d.ts +0 -25
- package/dist/tree/mascot.js +1 -32
- package/dist/tree/model.d.ts +0 -5
- package/dist/tree/model.js +2 -15
- package/dist/tree/nudge.d.ts +0 -11
- package/dist/tree/nudge.js +3 -16
- package/dist/tree/panel.d.ts +0 -8
- package/dist/tree/panel.js +3 -16
- package/dist/tree/ports.d.ts +2 -138
- package/dist/tree/report.d.ts +0 -10
- package/dist/tree/report.js +0 -11
- package/dist/tree/roster.d.ts +0 -21
- package/dist/tree/roster.js +0 -21
- package/dist/tree/rpc-handlers.d.ts +6 -1
- package/dist/tree/rpc-handlers.js +0 -5
- package/dist/tree/rpc.d.ts +9 -109
- package/dist/tree/rpc.js +2 -38
- package/dist/tree/running.d.ts +0 -6
- package/dist/tree/running.js +2 -10
- package/dist/tree/send.d.ts +0 -4
- package/dist/tree/send.js +4 -31
- package/dist/tree/spawn.d.ts +0 -7
- package/dist/tree/spawn.js +6 -47
- package/dist/tree/standing.d.ts +0 -8
- package/dist/tree/standing.js +1 -11
- package/dist/tree/status.d.ts +0 -8
- package/dist/tree/status.js +1 -9
- package/dist/tree/stop.d.ts +2 -9
- package/dist/tree/stop.js +58 -25
- package/dist/tree/store.d.ts +0 -8
- package/dist/tree/store.js +4 -38
- package/dist/tree/subtree.d.ts +8 -26
- package/dist/tree/subtree.js +37 -35
- package/dist/tree/summary.d.ts +0 -4
- package/dist/tree/summary.js +0 -8
- package/dist/tree/text.d.ts +1 -44
- package/dist/tree/text.js +6 -66
- package/dist/tree/tools.d.ts +0 -6
- package/dist/tree/tools.js +4 -8
- package/dist/tree/tui.d.ts +0 -81
- package/dist/tree/wake.d.ts +0 -30
- package/dist/tree/wake.js +5 -39
- package/dist/tui.js +22 -8
- package/package.json +8 -9
- package/dist/webhook/index.d.ts +0 -5
- package/dist/webhook/index.js +0 -3
- package/dist/webhook/receiver.d.ts +0 -80
- package/dist/webhook/receiver.js +0 -339
- package/dist/webhook/subscriptions.d.ts +0 -29
- package/dist/webhook/subscriptions.js +0 -52
- package/dist/webhook/text.d.ts +0 -23
- package/dist/webhook/text.js +0 -82
- package/dist/webhook/tools.d.ts +0 -19
- package/dist/webhook/tools.js +0 -46
|
@@ -10,5 +10,4 @@ export declare const LaterInput: Schema.Struct<{
|
|
|
10
10
|
export declare const CancelInput: Schema.Struct<{
|
|
11
11
|
readonly id: Schema.String;
|
|
12
12
|
}>;
|
|
13
|
-
/** Adds `courier_later` and `courier_cancel`. */
|
|
14
13
|
export declare function addSchedulingTools(tools: ToolEditor, later: LaterPorts): void;
|
package/dist/scheduling/tools.js
CHANGED
|
@@ -4,10 +4,7 @@ import { cancel, schedule } from "./later.js";
|
|
|
4
4
|
import { cancelText, laterText } from "./text.js";
|
|
5
5
|
export const LaterInput = Schema.Struct({
|
|
6
6
|
message: Schema.String.annotate({ description: "The message to deliver." }),
|
|
7
|
-
delayMinutes: Schema.optional(
|
|
8
|
-
// Some models send the number as a string ("3", "3.0") whatever the schema says, and resend it
|
|
9
|
-
// unchanged after the error, so a string is accepted and schedule reads it as a number.
|
|
10
|
-
Schema.Union([Schema.Number, Schema.String]).annotate({
|
|
7
|
+
delayMinutes: Schema.optional(Schema.Union([Schema.Number, Schema.String]).annotate({
|
|
11
8
|
description: "Deliver this many minutes from now. Give this or at.",
|
|
12
9
|
})),
|
|
13
10
|
at: Schema.optional(Schema.String.annotate({ description: "Deliver at this ISO 8601 time. Give this or delayMinutes." })),
|
|
@@ -16,7 +13,6 @@ export const LaterInput = Schema.Struct({
|
|
|
16
13
|
export const CancelInput = Schema.Struct({
|
|
17
14
|
id: Schema.String.annotate({ description: "The id courier_later returned." }),
|
|
18
15
|
});
|
|
19
|
-
/** Adds `courier_later` and `courier_cancel`. */
|
|
20
16
|
export function addSchedulingTools(tools, later) {
|
|
21
17
|
tools.add({
|
|
22
18
|
name: "courier_later",
|
package/dist/skill.d.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin";
|
|
2
|
-
/** Why a model loads the skill, listed with every session's available skills. */
|
|
3
2
|
export declare const SKILL_DESCRIPTION: string;
|
|
4
|
-
/**
|
|
5
|
-
* The skill's text; it names no limits, which the role part and the brief give for the session's own tree.
|
|
6
|
-
* Steps 1, 3, 4 and 6 repeat the brief's split rule on purpose: the root has no brief, so only the skill gives it.
|
|
7
|
-
*/
|
|
8
3
|
export declare const SKILL_CONTENT: string;
|
|
9
|
-
/** The editor OpenCode hands a skill transform. */
|
|
10
4
|
export type SkillEditor = Parameters<Parameters<Plugin.Context["skill"]["transform"]>[0]>[0];
|
|
11
|
-
/** The courier-orchestrate skill as OpenCode lists it: its id, name and description, and the text the model loads. */
|
|
12
5
|
export declare const orchestrateSkill: () => {
|
|
13
6
|
readonly id: "courier-orchestrate";
|
|
14
7
|
readonly name: "courier-orchestrate";
|
|
15
8
|
readonly description: string;
|
|
16
|
-
readonly path: "
|
|
9
|
+
readonly path: "skill://courier-orchestrate/SKILL.md";
|
|
17
10
|
readonly content: string;
|
|
18
11
|
};
|
|
19
|
-
/** Adds the courier-orchestrate skill; every instance adds the same one, under the same id. */
|
|
20
12
|
export declare function addSkill(skills: SkillEditor): void;
|
package/dist/skill.js
CHANGED
|
@@ -1,135 +1,114 @@
|
|
|
1
1
|
import { ONE_PART_LINE, SKILL_ID, SPLIT_LINE } from "./kernel/protocol.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"courier_spawn is among your tools: how to decide whether to split it across OpenCode sessions and state the decision, " +
|
|
6
|
-
"plan and start the parts, end your turn, verify and integrate the reports, report upward, and handle a part gone wrong.";
|
|
7
|
-
/**
|
|
8
|
-
* The skill's text; it names no limits, which the role part and the brief give for the session's own tree.
|
|
9
|
-
* Steps 1, 3, 4 and 6 repeat the brief's split rule on purpose: the root has no brief, so only the skill gives it.
|
|
10
|
-
*/
|
|
2
|
+
export const SKILL_DESCRIPTION = "How opencode-courier's session tools work. Load before you split a task with courier_spawn: when it has 2 or more " +
|
|
3
|
+
"independent, substantial parts touching separate files or areas. Starting the parts, waiting without polling, " +
|
|
4
|
+
"relaying questions and permissions, reading reports, merging and reporting upward.";
|
|
11
5
|
export const SKILL_CONTENT = [
|
|
12
|
-
"How
|
|
13
|
-
"opencode-courier role line in your system prompt, names your depth in the session tree and its limits. If " +
|
|
14
|
-
"courier_spawn is not among your tools, you cannot split: do the task yourself, and report upward as your brief says " +
|
|
15
|
-
"if a session started you.",
|
|
6
|
+
"# How opencode-courier's session tools work",
|
|
16
7
|
"",
|
|
17
|
-
"
|
|
18
|
-
|
|
8
|
+
"Your brief, or the opencode-courier role line in your system prompt, names your depth in the session tree and its " +
|
|
9
|
+
"limits. If courier_spawn is not among your tools, you cannot split: do the task yourself, and report upward as your " +
|
|
10
|
+
"brief says if a session started you.",
|
|
11
|
+
"",
|
|
12
|
+
"This skill says how the tools behave. How to plan the parts, stage a tightly coupled core or review the merged result " +
|
|
13
|
+
"is a playbook's: if a skill for that is listed to you, load it too.",
|
|
14
|
+
"",
|
|
15
|
+
"## 1. Decide: split or do",
|
|
16
|
+
"",
|
|
17
|
+
"- At the root, first call courier_recall once, before you decide and never in a loop: it lists how earlier trees in " +
|
|
19
18
|
"this project were split and how each part went. Below the root, read none: the records describe whole trees, which " +
|
|
20
19
|
"the root weighed before it split.",
|
|
21
|
-
"
|
|
20
|
+
"- Split when the task has 2 or more independent, substantial parts touching separate files or areas; do it yourself " +
|
|
22
21
|
"when it is small, sequential or tightly coupled. Several whole jobs in one prompt, such as 4 tickets, are that many " +
|
|
23
|
-
"parts: each session runs "
|
|
24
|
-
|
|
25
|
-
" - What you read and write yourself stays in your context for every later turn: keep the deciding, checking and " +
|
|
26
|
-
"integrating, and hand off the long reading and writing.",
|
|
27
|
-
" - Only the root, the session nobody started, may start exactly one session for the whole task, and only when the " +
|
|
22
|
+
"parts: each session runs this skill for its job, so the tree below you repeats its shape once per job.",
|
|
23
|
+
"- Only the root, the session nobody started, may start exactly one session for the whole task, and only when the " +
|
|
28
24
|
"task is heavy (long reading, many edits, long runs) and its own context is worth keeping; it still verifies and " +
|
|
29
|
-
"integrates that session's report itself
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"Leave model and variant unset, so a part runs on your own model, unless the part plainly calls for another: a " +
|
|
72
|
-
"cheaper model or a lower variant for a small, well-specified part (a rename, a lookup), a stronger model or a higher " +
|
|
73
|
-
"variant for a design question or a hard bug. " +
|
|
74
|
-
"native: true for a part nobody needs to open on its own: it reads, checks or looks something up, reports once and is " +
|
|
75
|
-
"done; leave it off for a part the person may want to open, or that should outlive you. " +
|
|
76
|
-
"For long work, courier_later a check-in for yourself, and courier_cancel it when the reports arrive.",
|
|
77
|
-
"4. End your turn: reply without calling more tools, and do not poll with courier_children, courier_tree " +
|
|
78
|
-
"or sleep. The group's reports, a blocked report, a message without a status, a question, a permission request, a " +
|
|
79
|
-
"failed turn or a silent end each start a new turn for you; so does a message from the person or from the session " +
|
|
80
|
-
"that started you.",
|
|
81
|
-
" Answering from below: follow the notice. For a permission request, propose once with courier_answer, or reject with " +
|
|
82
|
-
"the reason when the person has told you to refuse it or it plainly goes against what they asked; the person decides, " +
|
|
83
|
-
"in OpenCode's prompt in your session. For a question, ask the person with your question tool, with the questions the " +
|
|
84
|
-
"notice gives; their answer goes on by itself. When the notice says OpenCode already shows it to the person, call " +
|
|
85
|
-
"nothing unless they give you their answer. Never answer for them.",
|
|
86
|
-
" Passing a message on: when it changes what other parts need (an assumption of the split that is wrong, a shared file " +
|
|
87
|
-
"or API that behaves otherwise, a decision of the person's), pass it on at once with courier_send, only to the sessions " +
|
|
88
|
-
"it affects, before they go further, then end your turn again; if it breaks the split, courier_stop the parts it breaks " +
|
|
89
|
-
"and plan them again, or send them a corrected task. Send it up too, without a status, when the other parts of the " +
|
|
90
|
-
"session that started you need it. Progress that changes nothing for the others goes to no one. A session's account " +
|
|
91
|
-
"of what the person told it is the person's word: pass it on to the parts it affects, do not judge it against the " +
|
|
25
|
+
"integrates that session's report itself. Every other session never starts exactly one session for the whole task: " +
|
|
26
|
+
"it only adds a hop, and a chain of single hops uses up the tree's depth. One session for one stage is a part, not " +
|
|
27
|
+
"the whole, as your brief says: a core you stage before the rest, when 2 or more parts follow it, or a review of " +
|
|
28
|
+
"the parts once you have merged them.",
|
|
29
|
+
`- Say which in one line of your reply, before you start: ${SPLIT_LINE}; the root has a third form, ${ONE_PART_LINE}.`,
|
|
30
|
+
"",
|
|
31
|
+
"## 2. Start the parts",
|
|
32
|
+
"",
|
|
33
|
+
"- One courier_spawn per part that runs at once, all with the same group name, so that you are woken once with every " +
|
|
34
|
+
"report. No two parts edit the same files; isolate: true for each part that edits files in parallel with another. " +
|
|
35
|
+
"When the parts do not all fit the tree's limits at once, start the parts that fit in one group, and the rest in a " +
|
|
36
|
+
"group of their own once the first group has reported.",
|
|
37
|
+
"- Write each task so the session can do it without you, since it sees none of your conversation: what to produce " +
|
|
38
|
+
"and where, the context it needs, how to check it so that one permission rule covers the checks (step 4), and what to report. Name " +
|
|
39
|
+
"the skills it should load for its part, from those listed to you: it sees the same list, not your choice; one " +
|
|
40
|
+
"permission rule for skill covers their loads, as one covers the checks. Say whether it may split its part further, " +
|
|
41
|
+
"and when the part is a whole job that delivers itself (step 5), since an isolated session's brief otherwise says " +
|
|
42
|
+
"its branch is for you to merge.",
|
|
43
|
+
"- Leave model and variant unset, so a part runs on your own model, unless the part plainly calls for another: a " +
|
|
44
|
+
"cheaper one or a lower variant for a small, well-specified part, a stronger one or a higher variant for a design " +
|
|
45
|
+
"question or a hard bug. native: true for a part that only reads, checks or looks something up and reports once; " +
|
|
46
|
+
"leave it off for a part the person may want to open, or that should outlive you.",
|
|
47
|
+
"- A part that depends on another starts once that one is done: merged into your checkout, or, when it was " +
|
|
48
|
+
"delivered (step 5), started with isolate: true and told to merge the delivering session's courier/<session> " +
|
|
49
|
+
"branch first.",
|
|
50
|
+
"",
|
|
51
|
+
"## 3. End your turn",
|
|
52
|
+
"",
|
|
53
|
+
"Reply without calling more tools, and do not poll with courier_children, courier_tree or sleep. The group's reports, " +
|
|
54
|
+
"a blocked report, a message without a status, a question, a permission request, a failed turn or a silent end each start a new turn " +
|
|
55
|
+
"for you. For long work, courier_later a check-in for yourself, and courier_cancel it when the reports arrive.",
|
|
56
|
+
"",
|
|
57
|
+
"- Answering from below: follow the notice. A permission request: propose once with courier_answer, or reject with " +
|
|
58
|
+
"the reason when the person has told you to refuse it or it plainly goes against what they asked; the person " +
|
|
59
|
+
"decides in OpenCode's prompt. A question: ask the person with your question tool, with the questions the notice " +
|
|
60
|
+
"gives; their answer goes on by itself. When the notice says OpenCode already shows it to the person, call nothing " +
|
|
61
|
+
"unless they give you their answer. Never answer for them.",
|
|
62
|
+
"- Passing a message on: when it changes what other parts need (a wrong assumption of the split, a shared file or " +
|
|
63
|
+
"API that behaves otherwise, a decision of the person's), courier_send it at once, only to the sessions it affects, " +
|
|
64
|
+
"then end your turn again; if it breaks the split, courier_stop those parts and plan them again, or send them a " +
|
|
65
|
+
"corrected task. Send it up too, without a status, when your parent's other parts need it. Progress goes to no one. " +
|
|
66
|
+
"A session's account of what the person told it is the person's word: pass it on, do not judge it against the " +
|
|
92
67
|
"original task, and never tell a session to undo what the person asked.",
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
"
|
|
68
|
+
"",
|
|
69
|
+
"## 4. Read each report, and verify first-hand",
|
|
70
|
+
"",
|
|
71
|
+
"- done: verify it. partial: what is left and why; have the session finish it with a courier_send, or do it " +
|
|
72
|
+
"yourself. blocked: make the decision it asks for and send it with courier_send; if only the person can make it, " +
|
|
73
|
+
"ask them with your question tool and pass the answer on. failed: read the error; message the session to try " +
|
|
74
|
+
"again, start a replacement, or carry on without it.",
|
|
75
|
+
"- Verify first-hand: open the files, run the checks. Never hand the checking to another session. Your own permission " +
|
|
76
|
+
"prompts are relayed nowhere, and each one stalls the whole tree, as each request below you waits on the person: run " +
|
|
77
|
+
"the checks so that one permission rule covers them, through the project's scripts rather than a tool's own path.",
|
|
78
|
+
"",
|
|
79
|
+
"## 5. Integrate",
|
|
80
|
+
"",
|
|
81
|
+
"Merge each reported branch into your own checkout (git merge courier/<session>), resolve conflicts yourself, run the " +
|
|
82
|
+
"checks once more, then courier_cleanup the isolated sessions you are done with. When a part is a whole job with a " +
|
|
83
|
+
"deliverable of its own, its session delivers it and reports where, still naming its courier/<session> branch when " +
|
|
84
|
+
"it is isolated; you verify each delivery and merge nothing of it into your checkout.",
|
|
85
|
+
"",
|
|
86
|
+
"## 6. Report upward",
|
|
87
|
+
"",
|
|
88
|
+
"- If a session started you: one courier_send to it, with one status for the whole (done only when every part is " +
|
|
89
|
+
"verified and integrated, or verified and delivered; partial, blocked or failed otherwise) and the artifacts.",
|
|
90
|
+
"- At the top, before you reply to the person: if the next tree in this project should know something about running " +
|
|
91
|
+
"one like this (how it was split, what a part got wrong and what caught it, what a brief should have said, what the " +
|
|
92
|
+
"person decided), one courier_note with that one line; none when there is nothing. Name the job it comes from and " +
|
|
93
|
+
"the reason, so a lesson for one kind of job is not taken for every job; the tool refuses a line over its limit " +
|
|
94
|
+
"rather than clipping it, so write it shorter. A rule that holds every time goes to AGENTS.md or a skill, through " +
|
|
95
|
+
"the person, not into a note.",
|
|
96
|
+
"",
|
|
97
|
+
"## When a part goes wrong",
|
|
98
|
+
"",
|
|
99
|
+
'- A failed turn (failed="...") or a silent end (ended="without-report", quoting the session\'s last reply): message ' +
|
|
100
|
+
"the session with courier_send to carry on, to report, or to try again; or start a replacement. A silent end counts " +
|
|
101
|
+
"toward the limits until it reports; a failed turn does not, until you message it again.",
|
|
121
102
|
"- A refusal from courier_spawn naming maxDepth, maxChildren or maxTotal: do that part yourself, or end your turn and " +
|
|
122
103
|
"start it once a report has arrived. Do not retry it in a loop.",
|
|
123
104
|
"- Lost track, after a compaction or a restart: one courier_tree shows every session under you with its depth, its " +
|
|
124
105
|
"parent and whether it still owes a report; one look, not a loop.",
|
|
125
|
-
"- A subtree that went wrong or is no longer needed: courier_stop its top session; everything under it is stopped
|
|
126
|
-
"and nothing in it reports back. cleanup: true removes their worktrees.",
|
|
106
|
+
"- A subtree that went wrong or is no longer needed: courier_stop its top session; everything under it is stopped " +
|
|
107
|
+
"too, and nothing in it reports back. cleanup: true removes their worktrees.",
|
|
127
108
|
].join("\n");
|
|
128
|
-
|
|
129
|
-
const SKILL_PATH =
|
|
130
|
-
/** The courier-orchestrate skill as OpenCode lists it: its id, name and description, and the text the model loads. */
|
|
109
|
+
// A skill:// path: the read tool serves it from the registry (api-notes)
|
|
110
|
+
const SKILL_PATH = `skill://${SKILL_ID}/SKILL.md`;
|
|
131
111
|
export const orchestrateSkill = () => ({ id: SKILL_ID, name: SKILL_ID, description: SKILL_DESCRIPTION, path: SKILL_PATH, content: SKILL_CONTENT });
|
|
132
|
-
/** Adds the courier-orchestrate skill; every instance adds the same one, under the same id. */
|
|
133
112
|
export function addSkill(skills) {
|
|
134
113
|
skills.add(orchestrateSkill());
|
|
135
114
|
}
|
package/dist/tree/cleanup.d.ts
CHANGED
|
@@ -3,31 +3,11 @@ export interface CleanupInput {
|
|
|
3
3
|
readonly sessionID: string;
|
|
4
4
|
readonly force?: boolean;
|
|
5
5
|
}
|
|
6
|
-
/** At most this many changes and commits are returned; the reason still counts them all. */
|
|
7
6
|
export declare const MAX_LISTED = 50;
|
|
8
|
-
/**
|
|
9
|
-
* Forgets a child the parent started, removing its worktree if it is isolated. A worktree with uncommitted changes
|
|
10
|
-
* or commits found nowhere else is kept unless `force` is set; the result says so.
|
|
11
|
-
*/
|
|
12
7
|
export declare function cleanup(ports: CleanupPorts, parentID: string, input: CleanupInput): Promise<CleanupResult>;
|
|
13
|
-
/** Names the git to run, by absolute path, overriding the places `GIT_LOCATIONS` lists. */
|
|
14
8
|
export declare const GIT_ENV = "OPENCODE_COURIER_GIT";
|
|
15
|
-
/**
|
|
16
|
-
* Where git is looked for, in order: its usual install locations, never `PATH`, so a writable
|
|
17
|
-
* directory early in `PATH` cannot put another program in its place.
|
|
18
|
-
*/
|
|
19
9
|
export declare const GIT_LOCATIONS: Readonly<Record<"posix" | "win32", readonly string[]>>;
|
|
20
|
-
/**
|
|
21
|
-
* The git to run: `OPENCODE_COURIER_GIT` when it is an absolute path, otherwise the first of
|
|
22
|
-
* `GIT_LOCATIONS` that exists. Throws when there is none.
|
|
23
|
-
*/
|
|
24
10
|
export declare function findGit(env?: Readonly<Record<string, string | undefined>>, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string;
|
|
25
|
-
/** The commit a worktree is on, or undefined when git cannot tell. */
|
|
26
11
|
export declare function headOf(directory: string): Promise<string | undefined>;
|
|
27
|
-
/** Whether a worktree has uncommitted changes, untracked files included; undefined when git cannot tell. */
|
|
28
12
|
export declare function dirtyOf(directory: string): Promise<boolean | undefined>;
|
|
29
|
-
/**
|
|
30
|
-
* Reads a worktree's state with git; undefined when the directory is gone. Commits reachable from
|
|
31
|
-
* `base`, the commit the worktree was made from, are not its own work and are not listed.
|
|
32
|
-
*/
|
|
33
13
|
export declare function inspectWorktree(directory: string, base?: string): Promise<WorktreeState | undefined>;
|
package/dist/tree/cleanup.js
CHANGED
|
@@ -5,18 +5,12 @@ import { forgetDropped } from "./forget.js";
|
|
|
5
5
|
import { syncStanding } from "./standing.js";
|
|
6
6
|
import { childStore } from "./store.js";
|
|
7
7
|
import { keepReason } from "./text.js";
|
|
8
|
-
/** At most this many changes and commits are returned; the reason still counts them all. */
|
|
9
8
|
export const MAX_LISTED = 50;
|
|
10
|
-
/**
|
|
11
|
-
* Forgets a child the parent started, removing its worktree if it is isolated. A worktree with uncommitted changes
|
|
12
|
-
* or commits found nowhere else is kept unless `force` is set; the result says so.
|
|
13
|
-
*/
|
|
14
9
|
export async function cleanup(ports, parentID, input) {
|
|
15
10
|
const entry = await childStore(ports.storage).entry(parentID, input.sessionID);
|
|
16
11
|
if (!entry)
|
|
17
12
|
throw new Error(`${input.sessionID} is not on the courier_children list of ${parentID}.`);
|
|
18
13
|
const { directory } = entry;
|
|
19
|
-
// A member still out is dropped from its group, so the group is released without it; the result names that group.
|
|
20
14
|
const forget = async () => {
|
|
21
15
|
const group = await forgetDropped(ports, entry);
|
|
22
16
|
await syncStanding(ports, parentID);
|
|
@@ -24,8 +18,6 @@ export async function cleanup(ports, parentID, input) {
|
|
|
24
18
|
};
|
|
25
19
|
if (!entry.isolated)
|
|
26
20
|
return { sessionID: input.sessionID, directory, outcome: "forgotten", ...(await forget()) };
|
|
27
|
-
// With force the state only decides whether there is anything left to remove, so a worktree git
|
|
28
|
-
// can no longer read is still removed.
|
|
29
21
|
const state = await ports
|
|
30
22
|
.inspect(directory, entry.base)
|
|
31
23
|
.catch((error) => (input.force ? { changes: [], commits: [] } : Promise.reject(error)));
|
|
@@ -48,26 +40,16 @@ export async function cleanup(ports, parentID, input) {
|
|
|
48
40
|
});
|
|
49
41
|
return { sessionID: input.sessionID, directory, outcome: "removed", ...(await forget()) };
|
|
50
42
|
}
|
|
51
|
-
/** Names the git to run, by absolute path, overriding the places `GIT_LOCATIONS` lists. */
|
|
52
43
|
export const GIT_ENV = "OPENCODE_COURIER_GIT";
|
|
53
|
-
/**
|
|
54
|
-
* Where git is looked for, in order: its usual install locations, never `PATH`, so a writable
|
|
55
|
-
* directory early in `PATH` cannot put another program in its place.
|
|
56
|
-
*/
|
|
57
44
|
export const GIT_LOCATIONS = {
|
|
58
45
|
posix: [
|
|
59
46
|
"/usr/bin/git",
|
|
60
47
|
"/usr/local/bin/git",
|
|
61
48
|
"/opt/homebrew/bin/git",
|
|
62
|
-
// NixOS, and nix-darwin.
|
|
63
49
|
"/run/current-system/sw/bin/git",
|
|
64
50
|
],
|
|
65
51
|
win32: [String.raw `C:\Program Files\Git\cmd\git.exe`, String.raw `C:\Program Files (x86)\Git\cmd\git.exe`],
|
|
66
52
|
};
|
|
67
|
-
/**
|
|
68
|
-
* The git to run: `OPENCODE_COURIER_GIT` when it is an absolute path, otherwise the first of
|
|
69
|
-
* `GIT_LOCATIONS` that exists. Throws when there is none.
|
|
70
|
-
*/
|
|
71
53
|
export function findGit(env = process.env, platform = process.platform, exists = existsSync) {
|
|
72
54
|
const named = env[GIT_ENV]?.trim();
|
|
73
55
|
if (named) {
|
|
@@ -84,29 +66,21 @@ export function findGit(env = process.env, platform = process.platform, exists =
|
|
|
84
66
|
function git(directory, args) {
|
|
85
67
|
return new Promise((resolve, reject) => execFile(findGit(), ["-C", directory, ...args], { maxBuffer: 16 * 1024 * 1024 }, (error, stdout, stderr) => error ? reject(new Error(`git ${args[0]} in ${directory}: ${stderr.trim() || error.message}`)) : resolve(stdout)));
|
|
86
68
|
}
|
|
87
|
-
/** The commit a worktree is on, or undefined when git cannot tell. */
|
|
88
69
|
export function headOf(directory) {
|
|
89
70
|
return git(directory, ["rev-parse", "HEAD"]).then((out) => out.trim() || undefined, () => undefined);
|
|
90
71
|
}
|
|
91
|
-
/** Whether a worktree has uncommitted changes, untracked files included; undefined when git cannot tell. */
|
|
92
72
|
export function dirtyOf(directory) {
|
|
93
73
|
return git(directory, ["status", "--porcelain"]).then((out) => out.trim().length > 0, () => undefined);
|
|
94
74
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Reads a worktree's state with git; undefined when the directory is gone. Commits reachable from
|
|
97
|
-
* `base`, the commit the worktree was made from, are not its own work and are not listed.
|
|
98
|
-
*/
|
|
99
75
|
export async function inspectWorktree(directory, base) {
|
|
100
76
|
if (!existsSync(directory))
|
|
101
77
|
return undefined;
|
|
102
|
-
// An untracked directory is one entry, not every file in it.
|
|
103
78
|
const status = await git(directory, ["status", "--porcelain=v1", "-z", "--untracked-files=normal"]);
|
|
104
79
|
const changes = [];
|
|
105
80
|
const records = status.split("\0").filter(Boolean);
|
|
106
81
|
for (let i = 0; i < records.length; i++) {
|
|
107
82
|
const record = records[i];
|
|
108
83
|
changes.push(record.slice(3));
|
|
109
|
-
// A rename or copy is followed by its source path in a record of its own.
|
|
110
84
|
if ("RC".includes(record[0]) || "RC".includes(record[1]))
|
|
111
85
|
i++;
|
|
112
86
|
}
|
package/dist/tree/context.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ContextPorts } from "./ports.js";
|
|
2
|
-
/** The part of a session context this hook changes. */
|
|
3
2
|
export interface Shaped {
|
|
4
3
|
readonly sessionID: string;
|
|
5
4
|
tools: Record<string, unknown>;
|
|
@@ -8,8 +7,4 @@ export interface Shaped {
|
|
|
8
7
|
text: string;
|
|
9
8
|
}>;
|
|
10
9
|
}
|
|
11
|
-
/**
|
|
12
|
-
* The `context` hook: hides courier_spawn and the tools named `hiddenFromLeaf` from a session at `maxDepth`, and names a
|
|
13
|
-
* session's role in a system part unless its `role` entry does; a session nobody spawned that has started none is pointed to the skill.
|
|
14
|
-
*/
|
|
15
10
|
export declare function shapeContext(ports: ContextPorts, event: Shaped): Promise<void>;
|
package/dist/tree/context.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { setBounded } from "../platform/bounded.js";
|
|
2
2
|
import { childStore } from "./store.js";
|
|
3
3
|
import { ROLE_KEY, ROLE_PREFIX, rolePart } from "./text.js";
|
|
4
|
-
/** How many sessions' roles an instance remembers. */
|
|
5
4
|
const ROLES_MAX = 1000;
|
|
6
|
-
/** A role part, or the role entry's block, already in the system prompt, wherever its part begins. */
|
|
7
5
|
const ROLE_LINE = new RegExp(`^${ROLE_PREFIX}`, "m");
|
|
8
|
-
/**
|
|
9
|
-
* The `context` hook: hides courier_spawn and the tools named `hiddenFromLeaf` from a session at `maxDepth`, and names a
|
|
10
|
-
* session's role in a system part unless its `role` entry does; a session nobody spawned that has started none is pointed to the skill.
|
|
11
|
-
*/
|
|
12
6
|
export async function shapeContext(ports, event) {
|
|
13
7
|
try {
|
|
14
8
|
const { depth, entry } = await roleOf(ports, event.sessionID);
|
|
@@ -29,7 +23,6 @@ async function roleOf(ports, sessionID) {
|
|
|
29
23
|
const known = ports.roles.get(sessionID);
|
|
30
24
|
if (known !== undefined)
|
|
31
25
|
return known;
|
|
32
|
-
// The reverse index alone: a scan here would run for every session's first request, courier or not.
|
|
33
26
|
const children = childStore(ports.storage);
|
|
34
27
|
const chain = await children.indexedLineage(sessionID);
|
|
35
28
|
const depth = chain.length || ((await children.children(sessionID)).length ? 0 : null);
|
|
@@ -37,7 +30,6 @@ async function roleOf(ports, sessionID) {
|
|
|
37
30
|
setBounded(ports.roles, sessionID, role, ROLES_MAX);
|
|
38
31
|
return role;
|
|
39
32
|
}
|
|
40
|
-
/** Whether the session carries the role entry, listed when its role is looked up; one that cannot be read gets the part. */
|
|
41
33
|
async function hasEntry(ports, sessionID) {
|
|
42
34
|
if (!ports.instructions)
|
|
43
35
|
return false;
|