@resolveworks/fork 0.1.1 → 0.2.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 +23 -4
- package/index.ts +136 -101
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,11 +33,13 @@ Then restart pi, or `/reload`.
|
|
|
33
33
|
|
|
34
34
|
The parent session gains three tools:
|
|
35
35
|
|
|
36
|
-
- `spawn_agent({ task, branch? })` — writes the task to a file and opens a new tmux window running `pi @<taskfile>`. Returns immediately. The child sees the task plus normal project context, not the parent's conversation, so the task must be self-contained.
|
|
36
|
+
- `spawn_agent({ task, branch?, model? })` — writes the task to a file and opens a new tmux window running `pi @<taskfile>`. Returns immediately. The child sees the task plus normal project context, not the parent's conversation, so the task must be self-contained.
|
|
37
37
|
- `message_agent({ id, text })` — steers a revision request into the child's current run, or starts a new turn when the child is idle.
|
|
38
38
|
- `close_agent({ id })` — mechanical shutdown; no model turn spent.
|
|
39
39
|
|
|
40
|
-
The child gains `
|
|
40
|
+
The child gains `message_parent({ message })`. The message travels over a Unix socket and arrives in the parent as a steering notification: immediately if the parent is idle, or injected into the parent's current run after its pending tool calls. The child stays alive after replying, awaiting the verdict: revisions via `message_agent`, or closure. Messages are outcomes, not progress updates. If delivery fails, the child is told unambiguously and retries with the same payload — a retry cannot double-deliver. Interrupting the parent would drop its queued notifications, so fork re-delivers them once the interrupted run settles — an escape can no longer lose a message.
|
|
41
|
+
|
|
42
|
+
Input is tagged by sender: the task and every `message_agent` message reach the child wrapped in `<message-from-parent>`, while anything typed into the child's tmux window arrives unwrapped. The child's prompt uses that distinction to know who it is answering.
|
|
41
43
|
|
|
42
44
|
Don't poll. Navigate windows with your usual tmux keys.
|
|
43
45
|
|
|
@@ -51,12 +53,29 @@ Fork deliberately does no environment setup and runs no checks. `git worktree ad
|
|
|
51
53
|
|
|
52
54
|
## Config
|
|
53
55
|
|
|
54
|
-
`~/.pi/agent/fork.json`, overridden per-project by `.pi/fork.json` (read only in trusted projects).
|
|
56
|
+
`~/.pi/agent/fork.json`, overridden per-project by `.pi/fork.json` (read only in trusted projects). A `default` profile plus named `options`, passed through to the child as CLI flags:
|
|
55
57
|
|
|
56
58
|
```json
|
|
57
|
-
{
|
|
59
|
+
{
|
|
60
|
+
"default": { "model": "glm-5.2", "thinking": "high" },
|
|
61
|
+
"options": [
|
|
62
|
+
{
|
|
63
|
+
"tag": "small",
|
|
64
|
+
"description": "Fast and inexpensive; use for routine tasks.",
|
|
65
|
+
"model": "glm-4.7-air"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"tag": "big",
|
|
69
|
+
"description": "Stronger reasoning; use for complex design and difficult debugging.",
|
|
70
|
+
"model": "claude-opus-4-5",
|
|
71
|
+
"provider": "anthropic"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
58
75
|
```
|
|
59
76
|
|
|
77
|
+
Each option requires a `description`, shown to the parent model alongside its tag so it can select an appropriate profile. `spawn_agent` takes an optional `model` argument naming an option's tag; without it, the `default` profile applies, or pi's normal startup rules if there is none. The project file replaces global fields wholesale.
|
|
78
|
+
|
|
60
79
|
## Runtime state
|
|
61
80
|
|
|
62
81
|
Under `~/.pi/agent/extensions/fork/` (sockets and task files are mode `0o600`):
|
package/index.ts
CHANGED
|
@@ -3,22 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The parent registers `spawn_agent`, `message_agent`, and `close_agent`
|
|
5
5
|
* tools. Calling spawn_agent opens a new tmux window running pi with the
|
|
6
|
-
* given task and returns immediately. The child
|
|
7
|
-
* each
|
|
8
|
-
* the parent promptly: if the parent is idle the
|
|
9
|
-
* once; if it is mid-run the
|
|
10
|
-
* after its current tool calls, before the next LLM call.
|
|
11
|
-
* clears the steering queue without delivering queued custom
|
|
12
|
-
*
|
|
13
|
-
* interrupted run settles. The child stays alive after
|
|
14
|
-
*
|
|
6
|
+
* given task and returns immediately. The child replies to the parent through
|
|
7
|
+
* `message_parent`; each message is delivered over a Unix socket as a
|
|
8
|
+
* notification that reaches the parent promptly: if the parent is idle the
|
|
9
|
+
* message starts a turn at once; if it is mid-run the message is steered into
|
|
10
|
+
* that run — delivered after its current tool calls, before the next LLM call.
|
|
11
|
+
* An interrupt clears the steering queue without delivering queued custom
|
|
12
|
+
* messages, so messages are tracked until delivery is confirmed and
|
|
13
|
+
* re-delivered when an interrupted run settles. The child stays alive after
|
|
14
|
+
* replying: the parent reviews the message, sends revision requests with
|
|
15
15
|
* message_agent, and closes the child with close_agent.
|
|
16
16
|
*
|
|
17
|
-
* Configure
|
|
18
|
-
* .pi/fork.json (project, overrides global)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* Configure subagent models via ~/.pi/agent/fork.json (global) or
|
|
18
|
+
* .pi/fork.json (project, overrides global): a default profile plus named
|
|
19
|
+
* options selected by tag:
|
|
20
|
+
* { "default": { "model": "glm-5.2" },
|
|
21
|
+
* "options": [{ "tag": "small", "description": "Fast routine tasks",
|
|
22
|
+
* "model": "glm-4.7-air" }] }
|
|
23
|
+
* Fork passes the selected profile to the child pi process as CLI flags, so
|
|
24
|
+
* pi resolves them using its normal startup rules.
|
|
22
25
|
*
|
|
23
26
|
* Requires: run pi inside tmux.
|
|
24
27
|
*/
|
|
@@ -32,6 +35,7 @@ import * as path from "node:path";
|
|
|
32
35
|
import {
|
|
33
36
|
CONFIG_DIR_NAME,
|
|
34
37
|
type ExtensionAPI,
|
|
38
|
+
type ExtensionContext,
|
|
35
39
|
getAgentDir,
|
|
36
40
|
getMarkdownTheme,
|
|
37
41
|
keyHint,
|
|
@@ -155,7 +159,8 @@ type AgentMessage = { type: "message"; text: string };
|
|
|
155
159
|
|
|
156
160
|
// ── config (~/.pi/agent/fork.json, .pi/fork.json) ───────────────────
|
|
157
161
|
|
|
158
|
-
|
|
162
|
+
/** Model settings passed to the child as CLI flags. */
|
|
163
|
+
interface ModelProfile {
|
|
159
164
|
/** Model id or pattern (supports `provider/id` and `:thinking`). */
|
|
160
165
|
model?: string;
|
|
161
166
|
provider?: string;
|
|
@@ -163,14 +168,22 @@ interface ForkConfig {
|
|
|
163
168
|
thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
/** A named profile, selectable via spawn_agent's model argument. */
|
|
172
|
+
interface ModelOption extends ModelProfile {
|
|
173
|
+
tag: string;
|
|
174
|
+
/** Shown to the parent model to help it select a profile. */
|
|
175
|
+
description: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface ForkConfig {
|
|
179
|
+
/** Profile for spawns without a model argument. */
|
|
180
|
+
default?: ModelProfile;
|
|
181
|
+
options?: ModelOption[];
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
function readConfig(file: string): ForkConfig {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return JSON.parse(fs.readFileSync(file, "utf-8")) as ForkConfig;
|
|
170
|
-
} catch (err) {
|
|
171
|
-
console.error(`fork: failed to load ${file}: ${err}`);
|
|
172
|
-
return {};
|
|
173
|
-
}
|
|
185
|
+
if (!fs.existsSync(file)) return {};
|
|
186
|
+
return JSON.parse(fs.readFileSync(file, "utf-8")) as ForkConfig;
|
|
174
187
|
}
|
|
175
188
|
|
|
176
189
|
/** Merge global + project config. Project wins; only read if trusted. */
|
|
@@ -180,6 +193,14 @@ function loadConfig(cwd: string, projectTrusted: boolean): ForkConfig {
|
|
|
180
193
|
return { ...globalCfg, ...projectCfg };
|
|
181
194
|
}
|
|
182
195
|
|
|
196
|
+
/** The profile for a named tag, or `default` when no tag is given. */
|
|
197
|
+
function resolveProfile(config: ForkConfig, tag: string | undefined): ModelProfile {
|
|
198
|
+
if (tag === undefined) return config.default ?? {};
|
|
199
|
+
const option = config.options?.find((option) => option.tag === tag);
|
|
200
|
+
if (option === undefined) throw new Error(`fork: unknown model tag "${tag}"`);
|
|
201
|
+
return option;
|
|
202
|
+
}
|
|
203
|
+
|
|
183
204
|
// ── git worktrees ───────────────────────────────────────────────────
|
|
184
205
|
|
|
185
206
|
interface WorktreeInfo {
|
|
@@ -266,15 +287,15 @@ function spawn(
|
|
|
266
287
|
id: string,
|
|
267
288
|
taskPath: string,
|
|
268
289
|
cwd: string,
|
|
269
|
-
|
|
290
|
+
profile: ModelProfile,
|
|
270
291
|
branch?: string,
|
|
271
292
|
): void {
|
|
272
293
|
// argv passed directly; tmux runs a multi-arg command without `sh -c`, so
|
|
273
294
|
// no shell quoting is needed. Let pi resolve model settings itself.
|
|
274
295
|
const cmdArgs = ["pi", "--subagent-socket", socketPathFor(sessionId), "--subagent-id", id];
|
|
275
|
-
if (
|
|
276
|
-
if (
|
|
277
|
-
if (
|
|
296
|
+
if (profile.provider) cmdArgs.push("--provider", profile.provider);
|
|
297
|
+
if (profile.model) cmdArgs.push("--model", profile.model);
|
|
298
|
+
if (profile.thinking) cmdArgs.push("--thinking", profile.thinking);
|
|
278
299
|
if (branch) cmdArgs.push("--subagent-branch", branch);
|
|
279
300
|
cmdArgs.push(`@${taskPath}`);
|
|
280
301
|
|
|
@@ -452,10 +473,10 @@ function assertOpenAgent(id: string): void {
|
|
|
452
473
|
}
|
|
453
474
|
}
|
|
454
475
|
|
|
455
|
-
function setupParent(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
476
|
+
function setupParent(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
477
|
+
const config = loadConfig(ctx.cwd, ctx.isProjectTrusted());
|
|
478
|
+
const modelOptions = config.options ?? [];
|
|
479
|
+
|
|
459
480
|
fs.mkdirSync(SOCKETS_DIR, { recursive: true });
|
|
460
481
|
fs.mkdirSync(TASKS_DIR, { recursive: true });
|
|
461
482
|
|
|
@@ -501,18 +522,17 @@ function setupParent(
|
|
|
501
522
|
name: "spawn_agent",
|
|
502
523
|
label: "Spawn Agent",
|
|
503
524
|
description:
|
|
504
|
-
"Delegate a self-contained task to a subagent
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
"task
|
|
510
|
-
"conversation, so include
|
|
511
|
-
"
|
|
512
|
-
"
|
|
513
|
-
"
|
|
514
|
-
"
|
|
515
|
-
"request revisions with message_agent or close it with close_agent.",
|
|
525
|
+
"Delegate a self-contained task to a subagent in a fresh pi session and " +
|
|
526
|
+
"tmux window. Use this for independent work that benefits from isolated " +
|
|
527
|
+
"context; handle simple, sequential, or context-dependent work yourself. " +
|
|
528
|
+
"By default, the subagent shares your working tree; do not run concurrent " +
|
|
529
|
+
"editing agents in shared mode. Set branch to use an isolated Git worktree. " +
|
|
530
|
+
"The subagent sees the task and normal project context, but not this " +
|
|
531
|
+
"conversation, so include all necessary context in the task. This tool " +
|
|
532
|
+
"returns immediately. The subagent's report steers your current run, or " +
|
|
533
|
+
"starts a new turn if you are idle. Do not poll or sleep; continue other " +
|
|
534
|
+
"work or end your turn. After the report arrives, review its work, then " +
|
|
535
|
+
"use message_agent for revisions or close_agent when finished.",
|
|
516
536
|
parameters: Type.Object({
|
|
517
537
|
task: Type.String({
|
|
518
538
|
description:
|
|
@@ -532,16 +552,42 @@ function setupParent(
|
|
|
532
552
|
"already exist. Omit to share the current working tree.",
|
|
533
553
|
}),
|
|
534
554
|
),
|
|
555
|
+
...(modelOptions.length > 0
|
|
556
|
+
? {
|
|
557
|
+
model: Type.Optional(
|
|
558
|
+
Type.Enum(
|
|
559
|
+
modelOptions.map((option) => option.tag),
|
|
560
|
+
{
|
|
561
|
+
description:
|
|
562
|
+
"Model profile for the subagent:\n" +
|
|
563
|
+
modelOptions
|
|
564
|
+
.map((option) => `- ${option.tag}: ${option.description}`)
|
|
565
|
+
.join("\n") +
|
|
566
|
+
"\nOmit to use the default profile, or pi's normal startup rules " +
|
|
567
|
+
"if no default is configured.",
|
|
568
|
+
},
|
|
569
|
+
),
|
|
570
|
+
),
|
|
571
|
+
}
|
|
572
|
+
: {}),
|
|
535
573
|
}),
|
|
536
574
|
execute: async (_id, params, signal, _onUpdate, toolCtx) => {
|
|
537
|
-
const { task, branch } = params as {
|
|
575
|
+
const { task, branch, model } = params as {
|
|
576
|
+
task: string;
|
|
577
|
+
branch?: string;
|
|
578
|
+
model?: string;
|
|
579
|
+
};
|
|
538
580
|
const id = randomUUID();
|
|
539
581
|
const taskPath = taskPathFor(id);
|
|
540
|
-
|
|
582
|
+
// Tag the task like every other parent message, so the child learns one
|
|
583
|
+
// rule: tagged input is the parent, untagged input is a human at its window.
|
|
584
|
+
fs.writeFileSync(taskPath, `<message-from-parent>\n${task}\n</message-from-parent>`, {
|
|
585
|
+
mode: 0o600,
|
|
586
|
+
});
|
|
541
587
|
|
|
542
588
|
let worktree: WorktreeInfo | undefined;
|
|
543
589
|
try {
|
|
544
|
-
const
|
|
590
|
+
const profile = resolveProfile(config, model);
|
|
545
591
|
if (branch !== undefined) {
|
|
546
592
|
worktree = await createWorktree(pi, toolCtx.cwd, id, branch, signal);
|
|
547
593
|
}
|
|
@@ -551,7 +597,7 @@ function setupParent(
|
|
|
551
597
|
id,
|
|
552
598
|
taskPath,
|
|
553
599
|
worktree?.path ?? toolCtx.cwd,
|
|
554
|
-
|
|
600
|
+
profile,
|
|
555
601
|
worktree?.branch,
|
|
556
602
|
);
|
|
557
603
|
} catch (err) {
|
|
@@ -583,10 +629,10 @@ function setupParent(
|
|
|
583
629
|
label: "Message Agent",
|
|
584
630
|
description:
|
|
585
631
|
"Send a revision request or follow-up instruction to a live subagent. " +
|
|
586
|
-
"
|
|
587
|
-
"Use after reviewing its report when changes are needed
|
|
588
|
-
"
|
|
589
|
-
"
|
|
632
|
+
"The message steers its current run, or starts a new turn if it is idle. " +
|
|
633
|
+
"Use after reviewing its report when changes are needed. Its next report " +
|
|
634
|
+
"steers your current run, or starts a new turn if you are idle. Do not poll " +
|
|
635
|
+
"or sleep; continue other work or end your turn.",
|
|
590
636
|
parameters: Type.Object({
|
|
591
637
|
id: Type.String({
|
|
592
638
|
description: "The subagent's UUID, from its spawn result or report.",
|
|
@@ -628,11 +674,10 @@ function setupParent(
|
|
|
628
674
|
name: "close_agent",
|
|
629
675
|
label: "Close Agent",
|
|
630
676
|
description:
|
|
631
|
-
"Shut down a live subagent
|
|
632
|
-
"
|
|
633
|
-
"isolated
|
|
634
|
-
"
|
|
635
|
-
"until closed; close every subagent when you are done with it.",
|
|
677
|
+
"Shut down a live subagent without spending a model turn. Use after " +
|
|
678
|
+
"accepting its report or when the subagent is no longer needed. For an " +
|
|
679
|
+
"isolated subagent, this removes its worktree and discards uncommitted " +
|
|
680
|
+
"changes, but retains its agent/<name> branch. Close every subagent when finished.",
|
|
636
681
|
parameters: Type.Object({
|
|
637
682
|
id: Type.String({
|
|
638
683
|
description: "The subagent's UUID, from its spawn result or report.",
|
|
@@ -697,19 +742,14 @@ function subagentSystemPrompt(branch?: string): string {
|
|
|
697
742
|
"conversation. Focus only on the delegated task. " +
|
|
698
743
|
workspace +
|
|
699
744
|
"\n\n" +
|
|
700
|
-
"#
|
|
701
|
-
"
|
|
702
|
-
"
|
|
703
|
-
"
|
|
704
|
-
"
|
|
705
|
-
"
|
|
706
|
-
"
|
|
707
|
-
"the parent's
|
|
708
|
-
"- Every message from the parent calls for a report_result response, " +
|
|
709
|
-
"even if the report is only a brief status update.\n" +
|
|
710
|
-
"- After reporting, end your turn and wait. The session stays alive: " +
|
|
711
|
-
"the parent reviews each report and either sends you a revision request " +
|
|
712
|
-
"as an ordinary message or closes the session."
|
|
745
|
+
"# Communication\n\n" +
|
|
746
|
+
"- Parent messages, including your task, arrive in <message-from-parent> " +
|
|
747
|
+
"tags. Send answers, outcomes, and blockers back with message_parent; " +
|
|
748
|
+
"the parent cannot see your text replies.\n" +
|
|
749
|
+
"- Unwrapped messages come from the human using this session. Reply in " +
|
|
750
|
+
"text unless they ask you to relay something to the parent.\n\n" +
|
|
751
|
+
"After calling message_parent, end your turn and wait. The session stays " +
|
|
752
|
+
"alive for the parent's reply."
|
|
713
753
|
);
|
|
714
754
|
}
|
|
715
755
|
|
|
@@ -737,17 +777,14 @@ function processAgentLine(pi: ExtensionAPI, line: string): void {
|
|
|
737
777
|
// steer: a revision request is news the child can fold into its current
|
|
738
778
|
// run; it is delivered after the child's pending tool calls, before its
|
|
739
779
|
// next LLM call.
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
// which never reaches the parent.
|
|
780
|
+
// Match the spawn task's wrapper. The reminder makes the child reply over
|
|
781
|
+
// the socket rather than ending with a text reply the parent cannot see.
|
|
743
782
|
pi.sendMessage(
|
|
744
783
|
{
|
|
745
784
|
customType: MESSAGE_TYPE,
|
|
746
785
|
content:
|
|
747
786
|
`<message-from-parent>\n${candidate.text}\n</message-from-parent>\n\n` +
|
|
748
|
-
"Act on this message, then
|
|
749
|
-
"The parent receives only report_result output — it cannot see " +
|
|
750
|
-
"your text replies.",
|
|
787
|
+
"Act on this message, then reply with message_parent.",
|
|
751
788
|
display: true,
|
|
752
789
|
},
|
|
753
790
|
{ triggerTurn: true, deliverAs: "steer" },
|
|
@@ -770,37 +807,35 @@ function setupChild(
|
|
|
770
807
|
};
|
|
771
808
|
});
|
|
772
809
|
|
|
773
|
-
// ──
|
|
810
|
+
// ── message channel (child → parent) ───────────────────────────────
|
|
774
811
|
//
|
|
775
812
|
// Delivery failures are reported to the model, which retries with the same
|
|
776
|
-
//
|
|
777
|
-
// gone or the write failed, so a retry cannot deliver a
|
|
778
|
-
let
|
|
813
|
+
// message. Failures are unambiguous in practice: they mean the parent is
|
|
814
|
+
// gone or the write failed, so a retry cannot deliver a message twice.
|
|
815
|
+
let sendState: "open" | "sending" = "open";
|
|
779
816
|
|
|
780
817
|
pi.registerTool({
|
|
781
|
-
name: "
|
|
782
|
-
label: "
|
|
818
|
+
name: "message_parent",
|
|
819
|
+
label: "Message Parent",
|
|
783
820
|
description:
|
|
784
|
-
"Send a
|
|
785
|
-
"it
|
|
786
|
-
"
|
|
787
|
-
"blocked and need the parent's input. Report outcomes, not " +
|
|
788
|
-
"progress updates.",
|
|
821
|
+
"Send a message to the parent session; it cannot see your text replies. " +
|
|
822
|
+
"Use it to answer the parent, report completed work, or ask for input " +
|
|
823
|
+
"when blocked. Send outcomes, not progress updates.",
|
|
789
824
|
parameters: Type.Object({
|
|
790
|
-
|
|
825
|
+
message: Type.String({
|
|
791
826
|
description:
|
|
792
|
-
"The
|
|
793
|
-
"
|
|
827
|
+
"The answer, outcome, or blocker for the parent. Include edits and " +
|
|
828
|
+
"verification when relevant.",
|
|
794
829
|
}),
|
|
795
830
|
}),
|
|
796
831
|
execute: async (_id, params, _signal, _onUpdate, toolCtx) => {
|
|
797
|
-
if (
|
|
798
|
-
throw new Error("
|
|
832
|
+
if (sendState !== "open") {
|
|
833
|
+
throw new Error("message_parent is already sending a message");
|
|
799
834
|
}
|
|
800
|
-
|
|
835
|
+
sendState = "sending";
|
|
801
836
|
|
|
802
|
-
const {
|
|
803
|
-
const payload = `${JSON.stringify({ id: subagentId, summary:
|
|
837
|
+
const { message } = params as { message: string };
|
|
838
|
+
const payload = `${JSON.stringify({ id: subagentId, summary: message })}\n`;
|
|
804
839
|
|
|
805
840
|
try {
|
|
806
841
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -823,25 +858,25 @@ function setupChild(
|
|
|
823
858
|
socket.end(payload, () => finish());
|
|
824
859
|
});
|
|
825
860
|
} catch (err) {
|
|
826
|
-
|
|
827
|
-
const
|
|
828
|
-
console.error(
|
|
861
|
+
sendState = "open";
|
|
862
|
+
const failure = `fork: could not deliver message to parent: ${err}`;
|
|
863
|
+
console.error(failure);
|
|
829
864
|
toolCtx.ui.notify(
|
|
830
|
-
`${
|
|
865
|
+
`${failure}. The message was not sent; retry message_parent with the same message.`,
|
|
831
866
|
"error",
|
|
832
867
|
);
|
|
833
868
|
throw new Error(
|
|
834
|
-
"
|
|
835
|
-
"
|
|
869
|
+
"Message delivery failed; the message was not sent. Retry " +
|
|
870
|
+
"message_parent with the same message.",
|
|
836
871
|
);
|
|
837
872
|
}
|
|
838
873
|
|
|
839
|
-
|
|
874
|
+
sendState = "open";
|
|
840
875
|
return {
|
|
841
876
|
content: [
|
|
842
877
|
{
|
|
843
878
|
type: "text" as const,
|
|
844
|
-
text: "
|
|
879
|
+
text: "Delivered to the parent. End your turn and wait for its reply.",
|
|
845
880
|
},
|
|
846
881
|
],
|
|
847
882
|
details: {},
|