@gavin-hu/my-pi-agent 0.17.0 → 0.19.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 +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The WeChat implementation of {@link InteractionChannel}: it forwards a dialog
|
|
3
|
-
* prompt to
|
|
4
|
-
* replies.
|
|
3
|
+
* prompt to one peer and resolves when that peer replies.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* One channel serves one peer. That is what lets two peers' turns run at once
|
|
6
|
+
* without a dialog becoming ambiguous: the channel knows who it is asking, so a
|
|
7
|
+
* reply can only answer its own peer's prompt. It depends only on injected
|
|
8
|
+
* `send`/`sendFile`/`active` accessors and a timer seam, never on the bridge or
|
|
9
|
+
* HTTP client, so it is unit-tested without I/O. The bridge owns the poll loop,
|
|
10
|
+
* keeps one channel per peer (`channels.ts`), and hands each inbound message to
|
|
9
11
|
* {@link WechatInteractionChannel.handleInbound}.
|
|
10
12
|
*
|
|
11
13
|
* A prompt is bounded: a peer who never answers cannot hold the session open, so
|
|
@@ -18,23 +20,25 @@ import {
|
|
|
18
20
|
type InteractionAnswer,
|
|
19
21
|
type InteractionChannel,
|
|
20
22
|
type InteractionRequest,
|
|
21
|
-
} from "
|
|
22
|
-
import { type PeerLanguage } from "
|
|
23
|
-
import { notice } from "
|
|
23
|
+
} from "../../../lib/interaction.ts";
|
|
24
|
+
import { type PeerLanguage } from "../text/format.ts";
|
|
25
|
+
import { notice } from "../text/notices.ts";
|
|
24
26
|
import { formatPrompt, parsePromptAnswer } from "./prompt.ts";
|
|
25
|
-
import { defaultTimer, type ArmTimer } from "
|
|
27
|
+
import { defaultTimer, type ArmTimer } from "../timer.ts";
|
|
26
28
|
|
|
27
29
|
/** How long a forwarded prompt waits before it is cancelled. */
|
|
28
30
|
export const PROMPT_TIMEOUT_MS = 180_000;
|
|
29
31
|
|
|
30
32
|
export interface WechatInteractionDeps {
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
/** Send
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
/**
|
|
33
|
+
/** The one peer this channel asks. */
|
|
34
|
+
peer: string;
|
|
35
|
+
/** Send one text message to the peer; `force` bypasses the prompt-active gate. */
|
|
36
|
+
send: (text: string, signal?: AbortSignal, options?: { force?: boolean }) => Promise<boolean>;
|
|
37
|
+
/** Send a local file to the peer; `force` bypasses the prompt-active gate. */
|
|
38
|
+
sendFile: (path: string, name: string, signal?: AbortSignal, options?: { force?: boolean }) => Promise<boolean>;
|
|
39
|
+
/** Whether the peer owns a turn right now, so a prompt has somewhere to land. */
|
|
40
|
+
active: () => boolean;
|
|
41
|
+
/** The language the peer writes in, for the timeout notices. */
|
|
38
42
|
language: () => PeerLanguage;
|
|
39
43
|
/** Arm a cancellable one-shot timer. Defaults to a real, unref'd `setTimeout`. */
|
|
40
44
|
armTimer?: ArmTimer;
|
|
@@ -48,14 +52,16 @@ export interface WechatInteractionChannel extends InteractionChannel {
|
|
|
48
52
|
* previous one when it ends: the optional channel member, made concrete here.
|
|
49
53
|
*/
|
|
50
54
|
setPromptTimeout(ms: number): () => void;
|
|
51
|
-
/** Consume an inbound message as a reply
|
|
52
|
-
handleInbound(
|
|
55
|
+
/** Consume an inbound message as a reply to this peer's prompt. */
|
|
56
|
+
handleInbound(text: string): boolean;
|
|
53
57
|
/** Whether a prompt is currently waiting for a reply. */
|
|
54
58
|
hasPending(): boolean;
|
|
55
59
|
/** The waiting prompt, for the control screen; undefined when none waits. */
|
|
56
60
|
pendingInfo(): PendingPromptInfo | undefined;
|
|
57
61
|
/** Withdraw the waiting prompt: tell the peer and resolve it cancelled. */
|
|
58
|
-
cancel(): void
|
|
62
|
+
cancel(): Promise<void>;
|
|
63
|
+
/** The language prompts are rendered in. */
|
|
64
|
+
language(): PeerLanguage;
|
|
59
65
|
/** Drop any pending prompt (turn settled or the bridge stopped). */
|
|
60
66
|
reset(): void;
|
|
61
67
|
}
|
|
@@ -71,7 +77,6 @@ export interface PendingPromptInfo {
|
|
|
71
77
|
|
|
72
78
|
interface Pending {
|
|
73
79
|
request: InteractionRequest;
|
|
74
|
-
peer: string;
|
|
75
80
|
startedAt: number;
|
|
76
81
|
resolve: (answer: InteractionAnswer) => void;
|
|
77
82
|
signal?: AbortSignal;
|
|
@@ -90,11 +95,11 @@ export function createWechatInteractionChannel(deps: WechatInteractionDeps): Wec
|
|
|
90
95
|
*/
|
|
91
96
|
let scopedTimeoutMs: number | undefined;
|
|
92
97
|
/**
|
|
93
|
-
* Free text captured from a `select` "Other" reply
|
|
94
|
-
*
|
|
95
|
-
*
|
|
98
|
+
* Free text captured from a `select` "Other" reply: the immediately following
|
|
99
|
+
* `input` for this peer returns it. One value, because one channel asks one peer
|
|
100
|
+
* — another peer's free text cannot reach this channel at all.
|
|
96
101
|
*/
|
|
97
|
-
|
|
102
|
+
let other: string | undefined;
|
|
98
103
|
|
|
99
104
|
function settle(answer: InteractionAnswer): void {
|
|
100
105
|
const current = pending;
|
|
@@ -108,7 +113,11 @@ export function createWechatInteractionChannel(deps: WechatInteractionDeps): Wec
|
|
|
108
113
|
|
|
109
114
|
return {
|
|
110
115
|
isActive(): boolean {
|
|
111
|
-
return deps.
|
|
116
|
+
return deps.active();
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
language(): PeerLanguage {
|
|
120
|
+
return deps.language();
|
|
112
121
|
},
|
|
113
122
|
|
|
114
123
|
hasPending(): boolean {
|
|
@@ -120,28 +129,33 @@ export function createWechatInteractionChannel(deps: WechatInteractionDeps): Wec
|
|
|
120
129
|
return current === undefined
|
|
121
130
|
? undefined
|
|
122
131
|
: {
|
|
123
|
-
peer:
|
|
132
|
+
peer: deps.peer,
|
|
124
133
|
kind: current.request.kind,
|
|
125
134
|
title: current.request.title,
|
|
126
135
|
startedAt: current.startedAt,
|
|
127
136
|
};
|
|
128
137
|
},
|
|
129
138
|
|
|
130
|
-
cancel(): void {
|
|
131
|
-
|
|
139
|
+
async cancel(): Promise<void> {
|
|
140
|
+
const current = pending;
|
|
141
|
+
if (!current) return;
|
|
132
142
|
// The peer saw the prompt; one line says it was withdrawn, which is not the
|
|
133
143
|
// same as expiry — they were not slow, the question went away. `reset` is the
|
|
134
|
-
// silent cleanup path and deliberately does not send this.
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
// silent cleanup path and deliberately does not send this. Settle after the
|
|
145
|
+
// notice is on the wire, so a caller awaiting the answer knows it was sent.
|
|
146
|
+
try {
|
|
147
|
+
await deps.send(notice("withdrawn", deps.language()), undefined, { force: true });
|
|
148
|
+
} finally {
|
|
149
|
+
if (pending === current) settle({ kind: "cancelled" });
|
|
150
|
+
}
|
|
137
151
|
},
|
|
138
152
|
|
|
139
153
|
async post(text: string, signal?: AbortSignal): Promise<void> {
|
|
140
|
-
await deps.send(text, signal);
|
|
154
|
+
await deps.send(text, signal, { force: true });
|
|
141
155
|
},
|
|
142
156
|
|
|
143
157
|
async postFile(path: string, name: string, signal?: AbortSignal): Promise<boolean> {
|
|
144
|
-
return deps.sendFile(path, name, signal);
|
|
158
|
+
return deps.sendFile(path, name, signal, { force: true });
|
|
145
159
|
},
|
|
146
160
|
|
|
147
161
|
setPromptTimeout(ms: number): () => void {
|
|
@@ -153,54 +167,55 @@ export function createWechatInteractionChannel(deps: WechatInteractionDeps): Wec
|
|
|
153
167
|
},
|
|
154
168
|
|
|
155
169
|
async request(request: InteractionRequest, signal?: AbortSignal): Promise<InteractionAnswer> {
|
|
156
|
-
// A `select` that resolved to "Other" stashes its text
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (request.kind === "input" && asking !== undefined && stashed !== undefined) {
|
|
162
|
-
other.delete(asking);
|
|
170
|
+
// A `select` that resolved to "Other" stashes its text; the immediately
|
|
171
|
+
// following `input` returns it without another round-trip.
|
|
172
|
+
const stashed = other;
|
|
173
|
+
if (request.kind === "input" && stashed !== undefined) {
|
|
174
|
+
other = undefined;
|
|
163
175
|
return { kind: "value", value: stashed };
|
|
164
176
|
}
|
|
165
|
-
if (request.kind !== "input"
|
|
177
|
+
if (request.kind !== "input") other = undefined;
|
|
166
178
|
|
|
167
179
|
if (signal?.aborted || !this.isActive()) return { kind: "cancelled" };
|
|
168
180
|
if (pending) return { kind: "cancelled" };
|
|
169
181
|
|
|
170
182
|
// Register the pending prompt before the send await so a fast reply is
|
|
171
183
|
// never lost to the round-trip, then send and fail closed.
|
|
172
|
-
const peer = deps.activePeer() as string;
|
|
173
184
|
const cancelTimers: Array<() => void> = [];
|
|
174
185
|
const answer = new Promise<InteractionAnswer>((resolve) => {
|
|
175
186
|
const onAbort = () => settle({ kind: "cancelled" });
|
|
176
|
-
pending = { request,
|
|
187
|
+
pending = { request, startedAt: now(), resolve, signal, onAbort, cancelTimers };
|
|
177
188
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
178
189
|
// An ignored question must not freeze the session: nudge once, then
|
|
179
190
|
// cancel so the model gets a clean answer instead of a hang.
|
|
180
191
|
const bound = scopedTimeoutMs ?? PROMPT_TIMEOUT_MS;
|
|
181
|
-
cancelTimers.push(
|
|
192
|
+
cancelTimers.push(
|
|
193
|
+
arm(bound / 2, () => void deps.send(notice("promptWaiting", deps.language()), undefined, { force: true })),
|
|
194
|
+
);
|
|
182
195
|
cancelTimers.push(
|
|
183
196
|
arm(bound, () => {
|
|
184
|
-
void deps.send(notice("timeout", deps.language()));
|
|
197
|
+
void deps.send(notice("timeout", deps.language()), undefined, { force: true });
|
|
185
198
|
settle({ kind: "cancelled" });
|
|
186
199
|
}),
|
|
187
200
|
);
|
|
188
201
|
});
|
|
189
|
-
const sent = await deps.send(formatPrompt(request), signal);
|
|
202
|
+
const sent = await deps.send(formatPrompt(request, { language: deps.language() }), signal);
|
|
190
203
|
if (!sent) settle({ kind: "cancelled" });
|
|
191
204
|
return await answer;
|
|
192
205
|
},
|
|
193
206
|
|
|
194
|
-
handleInbound(
|
|
207
|
+
handleInbound(text: string): boolean {
|
|
195
208
|
const current = pending;
|
|
196
|
-
if (!current
|
|
209
|
+
if (!current) return false;
|
|
197
210
|
const parsed = parsePromptAnswer(current.request, text);
|
|
198
211
|
switch (parsed.kind) {
|
|
199
212
|
case "retry":
|
|
200
|
-
void deps.send(formatPrompt(current.request, { retry: true })
|
|
213
|
+
void deps.send(formatPrompt(current.request, { retry: true, language: deps.language() }), undefined, {
|
|
214
|
+
force: true,
|
|
215
|
+
});
|
|
201
216
|
return true;
|
|
202
217
|
case "other":
|
|
203
|
-
other
|
|
218
|
+
other = parsed.text;
|
|
204
219
|
settle({ kind: "value", value: INTERACTION_OTHER_LABEL });
|
|
205
220
|
return true;
|
|
206
221
|
case "value":
|
|
@@ -216,7 +231,7 @@ export function createWechatInteractionChannel(deps: WechatInteractionDeps): Wec
|
|
|
216
231
|
},
|
|
217
232
|
|
|
218
233
|
reset(): void {
|
|
219
|
-
other
|
|
234
|
+
other = undefined;
|
|
220
235
|
scopedTimeoutMs = undefined;
|
|
221
236
|
settle({ kind: "cancelled" });
|
|
222
237
|
},
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Pure rendering and parsing of remote dialog prompts (no I/O).
|
|
3
3
|
*
|
|
4
4
|
* A prompt is rendered to a WeChat message by {@link formatPrompt}; the reply is
|
|
5
|
-
* interpreted by {@link parsePromptAnswer}. Hints are
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* interpreted by {@link parsePromptAnswer}. Hints are rendered in the peer's
|
|
6
|
+
* language, and {@link formatPrompt} can prepend a short notice when a reply was
|
|
7
|
+
* not understood. The parser is deliberately total:
|
|
8
8
|
* anything it cannot resolve reports `retry`, and free text on a `select` becomes
|
|
9
9
|
* the {@link INTERACTION_OTHER_LABEL} option so the caller can collect it once.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { INTERACTION_OTHER_LABEL, type InteractionRequest } from "
|
|
13
|
-
import { sanitizeInbound } from "
|
|
12
|
+
import { INTERACTION_OTHER_LABEL, type InteractionRequest } from "../../../lib/interaction.ts";
|
|
13
|
+
import { sanitizeInbound, type PeerLanguage } from "../text/format.ts";
|
|
14
14
|
|
|
15
15
|
/** A parsed reply: a resolved answer, the "other" choice, or a retry. */
|
|
16
16
|
export type PromptParse =
|
|
@@ -20,20 +20,30 @@ export type PromptParse =
|
|
|
20
20
|
| { kind: "cancelled" }
|
|
21
21
|
| { kind: "retry" };
|
|
22
22
|
|
|
23
|
-
const SELECT_OTHER_HINT =
|
|
24
|
-
|
|
25
|
-
"Reply with a number or the option text; anything else
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
23
|
+
const SELECT_OTHER_HINT: Record<PeerLanguage, string> = {
|
|
24
|
+
zh: "回复编号或选项文字;其它内容作为自定义回答;回复「取消」跳过。",
|
|
25
|
+
en: "Reply with a number or the option text; anything else is your own answer; reply cancel to dismiss.",
|
|
26
|
+
};
|
|
27
|
+
const SELECT_CHOICE_HINT: Record<PeerLanguage, string> = {
|
|
28
|
+
zh: "回复编号或选项文字;回复「取消」跳过。",
|
|
29
|
+
en: "Reply with a number or the option text; reply cancel to dismiss.",
|
|
30
|
+
};
|
|
31
|
+
const CONFIRM_HINT: Record<PeerLanguage, string> = {
|
|
32
|
+
zh: "回复「是」或「否」。",
|
|
33
|
+
en: "Reply yes or no.",
|
|
34
|
+
};
|
|
35
|
+
const INPUT_HINT: Record<PeerLanguage, string> = {
|
|
36
|
+
zh: "直接回复内容;回复「取消」跳过。",
|
|
37
|
+
en: "Reply with the text; reply cancel to skip.",
|
|
38
|
+
};
|
|
39
|
+
const EDITOR_HINT: Record<PeerLanguage, string> = {
|
|
40
|
+
zh: "把内容作为一条消息发回;回复「取消」取消。",
|
|
41
|
+
en: "Send the text as one message; reply cancel to cancel.",
|
|
42
|
+
};
|
|
33
43
|
|
|
34
44
|
/** Sent ahead of a re-prompt when a reply could not be interpreted. */
|
|
35
|
-
export const RETRY_NOTICE =
|
|
36
|
-
|
|
45
|
+
export const RETRY_NOTICE = "Sorry, I didn't recognize that reply — please answer again.";
|
|
46
|
+
const RETRY_ZH = "没有识别到有效回复,请重新回答。";
|
|
37
47
|
|
|
38
48
|
const CANCEL_WORDS = new Set(["取消", "退出", "cancel", "quit", "exit"]);
|
|
39
49
|
const YES_WORDS = new Set(["是", "y", "yes", "1", "确认", "确定", "行", "ok", "好", "对"]);
|
|
@@ -44,24 +54,29 @@ function lines(parts: Array<string | undefined>): string {
|
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
/** Render a dialog prompt as a WeChat message body, optionally after a retry. */
|
|
47
|
-
export function formatPrompt(
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
export function formatPrompt(
|
|
58
|
+
request: InteractionRequest,
|
|
59
|
+
options: { retry?: boolean; language?: PeerLanguage } = {},
|
|
60
|
+
): string {
|
|
61
|
+
const language = options.language ?? "en";
|
|
62
|
+
const body = promptBody(request, language);
|
|
63
|
+
const retry = language === "zh" ? RETRY_ZH : RETRY_NOTICE;
|
|
64
|
+
return options.retry ? `${retry}\n\n${body}` : body;
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
function promptBody(request: InteractionRequest): string {
|
|
67
|
+
function promptBody(request: InteractionRequest, language: PeerLanguage): string {
|
|
53
68
|
switch (request.kind) {
|
|
54
69
|
case "select": {
|
|
55
70
|
const options = (request.options ?? []).map((label, index) => `${index + 1}. ${label}`);
|
|
56
71
|
const hasOther = (request.options ?? []).includes(INTERACTION_OTHER_LABEL);
|
|
57
|
-
return lines([request.title, ...options, hasOther ? SELECT_OTHER_HINT : SELECT_CHOICE_HINT]);
|
|
72
|
+
return lines([request.title, ...options, hasOther ? SELECT_OTHER_HINT[language] : SELECT_CHOICE_HINT[language]]);
|
|
58
73
|
}
|
|
59
74
|
case "confirm":
|
|
60
|
-
return lines([request.title, request.message, CONFIRM_HINT]);
|
|
75
|
+
return lines([request.title, request.message, CONFIRM_HINT[language]]);
|
|
61
76
|
case "input":
|
|
62
|
-
return lines([request.title, request.placeholder, INPUT_HINT]);
|
|
77
|
+
return lines([request.title, request.placeholder, INPUT_HINT[language]]);
|
|
63
78
|
case "editor":
|
|
64
|
-
return lines([request.title, request.prefill, EDITOR_HINT]);
|
|
79
|
+
return lines([request.title, request.prefill, EDITOR_HINT[language]]);
|
|
65
80
|
}
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type InteractionAnswer,
|
|
25
25
|
type InteractionChannel,
|
|
26
26
|
type InteractionRequest,
|
|
27
|
-
} from "
|
|
27
|
+
} from "../../../lib/interaction.ts";
|
|
28
28
|
|
|
29
29
|
function valueOf(answer: InteractionAnswer): string | undefined {
|
|
30
30
|
return answer.kind === "value" ? answer.value : undefined;
|
|
@@ -19,18 +19,18 @@ import { isPidAlive } from "../../lib/spawn.ts";
|
|
|
19
19
|
import { GLYPHS, STATUS_KEYS } from "../../lib/tui/glyphs.ts";
|
|
20
20
|
import { paint } from "../../lib/tui/tokens.ts";
|
|
21
21
|
import { createBridge, type Bridge, type BridgeState } from "./bridge.ts";
|
|
22
|
-
import { createFetchRunner, WechatClient, type WechatClientOptions } from "./client.ts";
|
|
23
|
-
import { registerCommands, type CommandDeps } from "./commands.ts";
|
|
22
|
+
import { createFetchRunner, WechatClient, type WechatClientOptions } from "./transport/client.ts";
|
|
23
|
+
import { registerCommands, type CommandDeps } from "./commands/commands.ts";
|
|
24
24
|
import { messageOf } from "./errors.ts";
|
|
25
25
|
import { loadWechatConfig } from "./config.ts";
|
|
26
|
-
import { clearCredentials, primaryAccount, saveAccount, wechatDir } from "./credentials.ts";
|
|
27
|
-
import { acquireLock, lockPath, readLock, refreshLock, releaseLock } from "./lock.ts";
|
|
28
|
-
import { openPeerSession, type PeerSession, type PeerSessionOptions } from "./peer-session.ts";
|
|
29
|
-
import { createPeerUIContext } from "./remote-ui.ts";
|
|
30
|
-
import { peerKey } from "./format.ts";
|
|
31
|
-
import { readPeerHistory } from "./history.ts";
|
|
32
|
-
import { SLASH_COMMANDS } from "./slash.ts";
|
|
33
|
-
import { readState, statePath, writeState } from "./state.ts";
|
|
26
|
+
import { clearCredentials, primaryAccount, saveAccount, wechatDir } from "./state/credentials.ts";
|
|
27
|
+
import { acquireLock, lockPath, readLock, refreshLock, releaseLock } from "./state/lock.ts";
|
|
28
|
+
import { openPeerSession, type PeerSession, type PeerSessionOptions } from "./peer/peer-session.ts";
|
|
29
|
+
import { createPeerUIContext } from "./dialog/remote-ui.ts";
|
|
30
|
+
import { peerKey } from "./text/format.ts";
|
|
31
|
+
import { readPeerHistory } from "./commands/history.ts";
|
|
32
|
+
import { SLASH_COMMANDS } from "./commands/slash.ts";
|
|
33
|
+
import { readState, statePath, writeState } from "./state/state.ts";
|
|
34
34
|
import { registerTools } from "./tools.ts";
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* budget is in memory; a reload starts a fresh hour.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { InboundItem } from "
|
|
12
|
-
import { peerKey } from "
|
|
11
|
+
import type { InboundItem } from "../types.ts";
|
|
12
|
+
import { peerKey } from "../text/format.ts";
|
|
13
13
|
|
|
14
14
|
/** A rolling-hour turn budget per peer. */
|
|
15
15
|
export interface TurnBudget {
|
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
SessionManager,
|
|
33
33
|
} from "@earendil-works/pi-coding-agent";
|
|
34
34
|
import { join } from "node:path";
|
|
35
|
-
import { WECHAT_CHANNEL_PROMPT } from "
|
|
36
|
-
import { peerKey } from "
|
|
35
|
+
import { WECHAT_CHANNEL_PROMPT } from "../text/channel.ts";
|
|
36
|
+
import { peerKey } from "../text/format.ts";
|
|
37
37
|
|
|
38
38
|
/** The SDK pieces a peer session needs; injected so tests never load resources. */
|
|
39
39
|
export interface PeerSessionSeams {
|