@dalmasonto/taskflow-mcp 1.0.35 → 2.1.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 +138 -193
- package/dist/attachment-download.d.ts +74 -0
- package/dist/attachment-download.js +193 -0
- package/dist/attachment-download.js.map +1 -0
- package/dist/attachments.d.ts +23 -0
- package/dist/attachments.js +66 -0
- package/dist/attachments.js.map +1 -0
- package/dist/client.d.ts +206 -0
- package/dist/client.js +301 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +137 -18
- package/dist/config.js +187 -106
- package/dist/config.js.map +1 -0
- package/dist/connect.d.ts +89 -0
- package/dist/connect.js +269 -0
- package/dist/connect.js.map +1 -0
- package/dist/doctor.d.ts +24 -0
- package/dist/doctor.js +120 -0
- package/dist/doctor.js.map +1 -0
- package/dist/events.d.ts +208 -0
- package/dist/events.js +454 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +121 -218
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.js +114 -0
- package/dist/instructions.js.map +1 -0
- package/dist/mint.d.ts +62 -0
- package/dist/mint.js +135 -0
- package/dist/mint.js.map +1 -0
- package/dist/mirror.d.ts +68 -0
- package/dist/mirror.js +103 -0
- package/dist/mirror.js.map +1 -0
- package/dist/pane-queue.d.ts +29 -0
- package/dist/pane-queue.js +35 -0
- package/dist/pane-queue.js.map +1 -0
- package/dist/prompts.d.ts +79 -0
- package/dist/prompts.js +211 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resolve.d.ts +72 -0
- package/dist/resolve.js +89 -0
- package/dist/resolve.js.map +1 -0
- package/dist/runtime.d.ts +54 -0
- package/dist/runtime.js +339 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +56 -0
- package/dist/server.js +793 -0
- package/dist/server.js.map +1 -0
- package/dist/session-identifier.d.ts +48 -0
- package/dist/session-identifier.js +44 -0
- package/dist/session-identifier.js.map +1 -0
- package/dist/sessions-store.d.ts +38 -0
- package/dist/sessions-store.js +88 -0
- package/dist/sessions-store.js.map +1 -0
- package/dist/tmux.d.ts +200 -0
- package/dist/tmux.js +580 -0
- package/dist/tmux.js.map +1 -0
- package/hooks/metadata.mjs +99 -0
- package/hooks/permission-prompt.mjs +100 -0
- package/hooks/taskflow-hook.mjs +499 -0
- package/hooks/tool-logging.mjs +63 -0
- package/package.json +38 -29
- package/dist/agent-registry.d.ts +0 -28
- package/dist/agent-registry.js +0 -158
- package/dist/db.d.ts +0 -5
- package/dist/db.js +0 -220
- package/dist/helpers.d.ts +0 -21
- package/dist/helpers.js +0 -27
- package/dist/resources.d.ts +0 -2
- package/dist/resources.js +0 -89
- package/dist/retry.d.ts +0 -34
- package/dist/retry.js +0 -94
- package/dist/sse.d.ts +0 -10
- package/dist/sse.js +0 -824
- package/dist/tmux-bridge.d.ts +0 -13
- package/dist/tmux-bridge.js +0 -217
- package/dist/tools/activity.d.ts +0 -39
- package/dist/tools/activity.js +0 -152
- package/dist/tools/agent-inbox.d.ts +0 -12
- package/dist/tools/agent-inbox.js +0 -272
- package/dist/tools/agent.d.ts +0 -14
- package/dist/tools/agent.js +0 -168
- package/dist/tools/analytics.d.ts +0 -21
- package/dist/tools/analytics.js +0 -191
- package/dist/tools/checkpoint.d.ts +0 -27
- package/dist/tools/checkpoint.js +0 -105
- package/dist/tools/notifications.d.ts +0 -31
- package/dist/tools/notifications.js +0 -59
- package/dist/tools/projects.d.ts +0 -55
- package/dist/tools/projects.js +0 -112
- package/dist/tools/settings.d.ts +0 -19
- package/dist/tools/settings.js +0 -73
- package/dist/tools/tasks.d.ts +0 -105
- package/dist/tools/tasks.js +0 -403
- package/dist/tools/terminal.d.ts +0 -4
- package/dist/tools/terminal.js +0 -98
- package/dist/tools/timer.d.ts +0 -37
- package/dist/tools/timer.js +0 -154
- package/dist/types.d.ts +0 -83
- package/dist/types.js +0 -30
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a dashboard-answered prompt back into keystrokes.
|
|
3
|
+
*
|
|
4
|
+
* `AskUserQuestion` accepts SEVERAL questions in one call. The agent's terminal
|
|
5
|
+
* presents them one at a time — question 2's screen only exists once question 1
|
|
6
|
+
* is answered — so a whole set is stored as ONE prompt row and replayed in order
|
|
7
|
+
* only when every question has an answer. Replaying half a set would send the
|
|
8
|
+
* remaining digits at whatever screen the agent had moved on to.
|
|
9
|
+
*
|
|
10
|
+
* Two on-the-wire shapes are supported, because rows written before
|
|
11
|
+
* multi-question support are still in the table:
|
|
12
|
+
*
|
|
13
|
+
* legacy options_json = [{number, label}, ...] one question
|
|
14
|
+
* answer_json = [1, 3] one flat set
|
|
15
|
+
*
|
|
16
|
+
* set options_json = [{question, kind, options}] N questions
|
|
17
|
+
* answer_json = [[1], [2, 3]] one set per question
|
|
18
|
+
*
|
|
19
|
+
* A prompt that cannot be read yields NO keystrokes. An agent left waiting is
|
|
20
|
+
* recoverable; digits fired into the wrong screen are not.
|
|
21
|
+
*/
|
|
22
|
+
import { answerKeystrokes } from "./events.js";
|
|
23
|
+
function asOptions(value) {
|
|
24
|
+
if (!Array.isArray(value))
|
|
25
|
+
return [];
|
|
26
|
+
return value.filter((o) => typeof o === "object" && o !== null && typeof o.number === "number");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The questions a prompt is asking.
|
|
30
|
+
*
|
|
31
|
+
* `fallbackKind` and `fallbackQuestion` come from the row's own columns and are
|
|
32
|
+
* used only for the legacy shape, which stored the question text and kind there
|
|
33
|
+
* rather than inside the JSON.
|
|
34
|
+
*/
|
|
35
|
+
export function parseQuestions(optionsJson, fallbackKind, fallbackQuestion) {
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(optionsJson);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
// The discriminator is a nested `options` array: the set shape wraps each
|
|
46
|
+
// question, the legacy shape IS the option list.
|
|
47
|
+
const isSet = parsed.every((entry) => typeof entry === "object" && entry !== null && "options" in entry);
|
|
48
|
+
if (!isSet) {
|
|
49
|
+
const options = asOptions(parsed);
|
|
50
|
+
return options.length ? [{ question: fallbackQuestion, kind: fallbackKind, options }] : [];
|
|
51
|
+
}
|
|
52
|
+
return parsed
|
|
53
|
+
.map((entry) => {
|
|
54
|
+
const e = entry;
|
|
55
|
+
return {
|
|
56
|
+
question: typeof e.question === "string" ? e.question : fallbackQuestion,
|
|
57
|
+
kind: typeof e.kind === "string" ? e.kind : "single",
|
|
58
|
+
options: asOptions(e.options),
|
|
59
|
+
};
|
|
60
|
+
})
|
|
61
|
+
.filter((q) => q.options.length > 0);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The chosen numbers, one set per question.
|
|
65
|
+
*
|
|
66
|
+
* `answer` is the row's single-answer column, kept as a fallback for rows
|
|
67
|
+
* answered before `answer_json` existed.
|
|
68
|
+
*/
|
|
69
|
+
export function parseAnswerSets(answerJson, answer) {
|
|
70
|
+
const numeric = (value) => Array.isArray(value) ? value.filter((n) => typeof n === "number") : [];
|
|
71
|
+
if (answerJson) {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(answerJson);
|
|
74
|
+
if (Array.isArray(parsed)) {
|
|
75
|
+
// `[[1],[2]]` is one set per question; `[1,3]` is a single flat set.
|
|
76
|
+
return parsed.some(Array.isArray)
|
|
77
|
+
? parsed.map(numeric).filter((set) => set.length > 0)
|
|
78
|
+
: [numeric(parsed)].filter((set) => set.length > 0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// fall through to the single-answer column
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return typeof answer === "number" ? [[answer]] : [];
|
|
86
|
+
}
|
|
87
|
+
/** Whether every question in the set has at least one choice. */
|
|
88
|
+
export function isFullyAnswered(sets, questionCount) {
|
|
89
|
+
if (questionCount === 0)
|
|
90
|
+
return false;
|
|
91
|
+
if (sets.length !== questionCount)
|
|
92
|
+
return false;
|
|
93
|
+
return sets.every((set) => set.length > 0);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The review screen a MULTI-QUESTION set lands on once every question has been
|
|
97
|
+
* answered:
|
|
98
|
+
*
|
|
99
|
+
* Ready to submit your answers?
|
|
100
|
+
* > 1. Submit answers
|
|
101
|
+
* 2. Cancel
|
|
102
|
+
*
|
|
103
|
+
* Verified against a live session on 2026-07-21. Without this stage the agent
|
|
104
|
+
* answered all three questions and then sat on the review screen forever — the
|
|
105
|
+
* questions were right, and nothing pressed the final key.
|
|
106
|
+
*
|
|
107
|
+
* A numbered choice, so it takes a number AND Enter, matching the single-select
|
|
108
|
+
* behaviour confirmed the same day.
|
|
109
|
+
*/
|
|
110
|
+
const REVIEW_SUBMIT = ["1", "Enter"];
|
|
111
|
+
const REVIEW_CANCEL = ["2", "Enter"];
|
|
112
|
+
/**
|
|
113
|
+
* The full keystroke sequence for an answered prompt, or `[]` when it must not
|
|
114
|
+
* be replayed.
|
|
115
|
+
*
|
|
116
|
+
* Each question contributes its own sequence and they run back to back, in the
|
|
117
|
+
* order the questions were asked — the terminal advances to the next question as
|
|
118
|
+
* each is submitted. A set then confirms at the review screen.
|
|
119
|
+
*
|
|
120
|
+
* A single SINGLE-select question has no review screen (it submits as soon as it
|
|
121
|
+
* is answered), so nothing is appended and there is no safe "cancel" key. A
|
|
122
|
+
* single MULTI-select question DOES reach the review screen, so it gets the
|
|
123
|
+
* submit/cancel like a set does.
|
|
124
|
+
*/
|
|
125
|
+
export function keystrokesForPrompt(optionsJson, kind, question, answerJson, answer, intent = "submit") {
|
|
126
|
+
const questions = parseQuestions(optionsJson, kind, question);
|
|
127
|
+
const sets = parseAnswerSets(answerJson, answer);
|
|
128
|
+
if (!isFullyAnswered(sets, questions.length))
|
|
129
|
+
return [];
|
|
130
|
+
// The review screen appears for a question SET *and* for a single
|
|
131
|
+
// multi-select question — both end on "Ready to submit your answers?".
|
|
132
|
+
// Gating only on question count (as it did) left a lone multi-select stuck on
|
|
133
|
+
// that screen with nothing to press it. Verified live 2026-07-21.
|
|
134
|
+
const hasReviewScreen = questions.length > 1 || questions.some((q) => q.kind === "multi");
|
|
135
|
+
if (intent === "cancel" && !hasReviewScreen)
|
|
136
|
+
return [];
|
|
137
|
+
const answers = questions.flatMap((q, i) => answerKeystrokes(sets[i] ?? [], q.kind));
|
|
138
|
+
if (!hasReviewScreen)
|
|
139
|
+
return answers;
|
|
140
|
+
// Cancel is reached the same way as submit: every answer is replayed first,
|
|
141
|
+
// because the review screen only exists once the last question is answered.
|
|
142
|
+
return [...answers, ...(intent === "cancel" ? REVIEW_CANCEL : REVIEW_SUBMIT)];
|
|
143
|
+
}
|
|
144
|
+
/** The Other free-text value per question, parallel to the answer sets. */
|
|
145
|
+
export function parseAnswerTexts(answerTextJson, count) {
|
|
146
|
+
if (!answerTextJson)
|
|
147
|
+
return Array(count).fill(null);
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(answerTextJson);
|
|
150
|
+
if (Array.isArray(parsed)) {
|
|
151
|
+
return Array.from({ length: count }, (_, i) => typeof parsed[i] === "string" ? parsed[i] : null);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch { /* fall through */ }
|
|
155
|
+
return Array(count).fill(null);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The full ordered steps for an answered prompt. Without any Other text this is
|
|
159
|
+
* `keystrokesForPrompt` wrapped as `{key}` steps. With Other text on a single
|
|
160
|
+
* multi-select question, it walks the arrow-based flow the new TUI uses (see the
|
|
161
|
+
* hypothesis in the test — verified live before this ships).
|
|
162
|
+
*/
|
|
163
|
+
export function stepsForPrompt(optionsJson, kind, question, answerJson, answer, answerTextJson, intent = "submit") {
|
|
164
|
+
const questions = parseQuestions(optionsJson, kind, question);
|
|
165
|
+
const sets = parseAnswerSets(answerJson, answer);
|
|
166
|
+
const texts = parseAnswerTexts(answerTextJson, questions.length);
|
|
167
|
+
// Only the scoped case gets the Other flow: one multi-select question whose
|
|
168
|
+
// Other option was picked with text. Everything else uses the proven path.
|
|
169
|
+
const single = questions.length === 1 ? questions[0] : undefined;
|
|
170
|
+
const other = single?.options.find((o) => o.isOther);
|
|
171
|
+
const otherPicked = other && sets[0]?.includes(other.number) && (texts[0] ?? "").length > 0;
|
|
172
|
+
if (!otherPicked || intent === "cancel") {
|
|
173
|
+
return keystrokesForPrompt(optionsJson, kind, question, answerJson, answer, intent)
|
|
174
|
+
.map((key) => ({ key }));
|
|
175
|
+
}
|
|
176
|
+
const otherIndex = single.options.findIndex((o) => o.isOther);
|
|
177
|
+
// SINGLE-select Other (#30): one answer, no toggles, no review screen. Arrow
|
|
178
|
+
// down from the caret on option 1 to the "Type something" row, type the text
|
|
179
|
+
// (which fills the field), and Enter submits it — single-select commits on
|
|
180
|
+
// Enter, unlike multi-select where Enter only toggles. HYPOTHESIS, verified
|
|
181
|
+
// live before shipping (the choreography is the one thing tests can't prove).
|
|
182
|
+
if (single.kind !== "multi") {
|
|
183
|
+
const singleSteps = [];
|
|
184
|
+
for (let i = 0; i < otherIndex; i++)
|
|
185
|
+
singleSteps.push({ key: "Down" });
|
|
186
|
+
singleSteps.push({ text: texts[0] });
|
|
187
|
+
singleSteps.push({ key: "Enter" });
|
|
188
|
+
return singleSteps;
|
|
189
|
+
}
|
|
190
|
+
const steps = [];
|
|
191
|
+
// Walk down the list from the caret's start on option 1, toggling picks.
|
|
192
|
+
for (let i = 0; i < single.options.length; i++) {
|
|
193
|
+
const opt = single.options[i];
|
|
194
|
+
if (opt.isOther) {
|
|
195
|
+
// Typing (which send-keys -l mimics) AUTO-CHECKS the Other option, and
|
|
196
|
+
// Enter merely TOGGLES it — so NO Enter here, or it would deselect the box
|
|
197
|
+
// the paste just checked. Verified live 2026-07-21: with an Enter, Other
|
|
198
|
+
// came back unchecked and dropped from the answer.
|
|
199
|
+
steps.push({ text: texts[0] });
|
|
200
|
+
}
|
|
201
|
+
else if (sets[0].includes(opt.number)) {
|
|
202
|
+
steps.push({ key: "Enter" }); // toggle this pick
|
|
203
|
+
}
|
|
204
|
+
if (i < single.options.length - 1)
|
|
205
|
+
steps.push({ key: "Down" });
|
|
206
|
+
}
|
|
207
|
+
steps.push({ key: "Down" }, { key: "Enter" }); // to Submit, activate -> review
|
|
208
|
+
steps.push({ key: "1" }, { key: "Enter" }); // Submit answers
|
|
209
|
+
return steps;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn an id-only realtime message event into the message itself.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* Chat rows are CHANNEL-scoped but the realtime group is per-PROJECT, so
|
|
7
|
+
* anything projected onto the wire reaches every member of the project. That is
|
|
8
|
+
* why chat events carry the row id and nothing else — projecting the fields
|
|
9
|
+
* broadcast every DM's body, roster and attachment key to the whole project
|
|
10
|
+
* (`backend/tests/realtime_dm_privacy.rs`).
|
|
11
|
+
*
|
|
12
|
+
* The id alone is safe to broadcast and useless without authorization: the
|
|
13
|
+
* message is fetched back over the agent read API, which re-checks the channel
|
|
14
|
+
* roster. An agent that is not on the channel resolves nothing.
|
|
15
|
+
*
|
|
16
|
+
* The event carries no channel either, so the lookup fans out across the
|
|
17
|
+
* channels this agent is on — the same shape `check_messages` already uses. That
|
|
18
|
+
* roster is small (a project room plus a handful of DMs), and the per-channel
|
|
19
|
+
* fetch is a one-row window rather than a page.
|
|
20
|
+
*/
|
|
21
|
+
import type { MessageTarget } from "./events.js";
|
|
22
|
+
/** A message as the agent read API returns it. */
|
|
23
|
+
export interface ResolvedMessage {
|
|
24
|
+
id: number;
|
|
25
|
+
channel: number;
|
|
26
|
+
sender_kind: string;
|
|
27
|
+
sender_label: string;
|
|
28
|
+
body_markdown: string;
|
|
29
|
+
sender_agent: number | null;
|
|
30
|
+
/** Present on the read row (the backend serializes them); the agent notice
|
|
31
|
+
* surfaces them so a delivered message carries its own context. */
|
|
32
|
+
project?: number | null;
|
|
33
|
+
task?: number | null;
|
|
34
|
+
sender_user?: number | null;
|
|
35
|
+
/** #29: the one agent a directed group message is for; null = broadcast. */
|
|
36
|
+
target_agent?: number | null;
|
|
37
|
+
/** #29: the full directed-target set (agents + users). The backend stores it
|
|
38
|
+
* as a JSON STRING column, so the read row carries a string here; this module
|
|
39
|
+
* parses it into an array before delivery. Supersedes `target_agent`. */
|
|
40
|
+
targets?: MessageTarget[];
|
|
41
|
+
attachments?: Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
size_bytes: number;
|
|
44
|
+
url: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
/** Parse the message's `targets` column — a JSON string like
|
|
48
|
+
* `[{"kind":"agent","id":1}]` on the read row — into a validated array.
|
|
49
|
+
* Anything malformed (bad JSON, wrong shape) degrades to undefined, i.e. a
|
|
50
|
+
* broadcast, rather than throwing on the delivery path. */
|
|
51
|
+
export declare function parseTargets(raw: unknown): MessageTarget[] | undefined;
|
|
52
|
+
/** The slice of the client this module needs; narrowed so tests need no HTTP. */
|
|
53
|
+
export interface MessageSource {
|
|
54
|
+
listChannels(): Promise<Array<{
|
|
55
|
+
id: number;
|
|
56
|
+
}>>;
|
|
57
|
+
listMessages(params: {
|
|
58
|
+
channel: number;
|
|
59
|
+
since?: number;
|
|
60
|
+
limit?: number;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
messages: ResolvedMessage[];
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Fetch message `id`, or `null` when this agent cannot see it.
|
|
67
|
+
*
|
|
68
|
+
* Never throws. A failure to resolve must degrade to "nothing delivered" — the
|
|
69
|
+
* message is still stored and `check_messages` will surface it — rather than
|
|
70
|
+
* taking down the event stream that carries every other message.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveMessage(source: MessageSource, id: number): Promise<ResolvedMessage | null>;
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn an id-only realtime message event into the message itself.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* Chat rows are CHANNEL-scoped but the realtime group is per-PROJECT, so
|
|
7
|
+
* anything projected onto the wire reaches every member of the project. That is
|
|
8
|
+
* why chat events carry the row id and nothing else — projecting the fields
|
|
9
|
+
* broadcast every DM's body, roster and attachment key to the whole project
|
|
10
|
+
* (`backend/tests/realtime_dm_privacy.rs`).
|
|
11
|
+
*
|
|
12
|
+
* The id alone is safe to broadcast and useless without authorization: the
|
|
13
|
+
* message is fetched back over the agent read API, which re-checks the channel
|
|
14
|
+
* roster. An agent that is not on the channel resolves nothing.
|
|
15
|
+
*
|
|
16
|
+
* The event carries no channel either, so the lookup fans out across the
|
|
17
|
+
* channels this agent is on — the same shape `check_messages` already uses. That
|
|
18
|
+
* roster is small (a project room plus a handful of DMs), and the per-channel
|
|
19
|
+
* fetch is a one-row window rather than a page.
|
|
20
|
+
*/
|
|
21
|
+
/** Parse the message's `targets` column — a JSON string like
|
|
22
|
+
* `[{"kind":"agent","id":1}]` on the read row — into a validated array.
|
|
23
|
+
* Anything malformed (bad JSON, wrong shape) degrades to undefined, i.e. a
|
|
24
|
+
* broadcast, rather than throwing on the delivery path. */
|
|
25
|
+
export function parseTargets(raw) {
|
|
26
|
+
let value = raw;
|
|
27
|
+
if (typeof raw === "string") {
|
|
28
|
+
if (raw.trim() === "")
|
|
29
|
+
return undefined;
|
|
30
|
+
try {
|
|
31
|
+
value = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!Array.isArray(value))
|
|
38
|
+
return undefined;
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const entry of value) {
|
|
41
|
+
if (entry && typeof entry === "object") {
|
|
42
|
+
const kind = entry.kind;
|
|
43
|
+
const id = entry.id;
|
|
44
|
+
if (typeof kind === "string" && typeof id === "number")
|
|
45
|
+
out.push({ kind, id });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out.length ? out : undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch message `id`, or `null` when this agent cannot see it.
|
|
52
|
+
*
|
|
53
|
+
* Never throws. A failure to resolve must degrade to "nothing delivered" — the
|
|
54
|
+
* message is still stored and `check_messages` will surface it — rather than
|
|
55
|
+
* taking down the event stream that carries every other message.
|
|
56
|
+
*/
|
|
57
|
+
export async function resolveMessage(source, id) {
|
|
58
|
+
let channels;
|
|
59
|
+
try {
|
|
60
|
+
channels = await source.listChannels();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const found = await Promise.all(channels.map(async (channel) => {
|
|
66
|
+
try {
|
|
67
|
+
// `since` is exclusive, so `id - 1` asks for exactly this row onward and
|
|
68
|
+
// `limit: 1` keeps it to one. A per-event page fetch would be wasteful on
|
|
69
|
+
// a busy project.
|
|
70
|
+
const page = await source.listMessages({ channel: channel.id, since: id - 1, limit: 1 });
|
|
71
|
+
// The window can return a NEIGHBOURING row when this id is not in that
|
|
72
|
+
// channel, so match the id explicitly — delivering the wrong message
|
|
73
|
+
// would be worse than delivering none.
|
|
74
|
+
return page.messages.find((m) => m.id === id) ?? null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// One unreachable channel must not hide a message sitting in another.
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}));
|
|
81
|
+
const message = found.find((m) => m !== null) ?? null;
|
|
82
|
+
// The read row carries `targets` as a JSON string; parse it to the array
|
|
83
|
+
// `shouldDeliver` gates on. Done here so both the live push and the reconnect
|
|
84
|
+
// catch-up (which resolve through this function) see the same parsed shape.
|
|
85
|
+
if (message)
|
|
86
|
+
message.targets = parseTargets(message.targets);
|
|
87
|
+
return message;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA0BH;;;4DAG4D;AAC5D,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,IAAI,KAAK,GAAY,GAAG,CAAC;IACzB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;QACxC,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,GAAI,KAAiC,CAAC,IAAI,CAAC;YACrD,MAAM,EAAE,GAAI,KAAiC,CAAC,EAAE,CAAC;YACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC;AAYD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAqB,EACrB,EAAU;IAEV,IAAI,QAA+B,CAAC;IACpC,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAC7B,IAAI,CAAC;YACH,yEAAyE;YACzE,0EAA0E;YAC1E,kBAAkB;YAClB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACzF,uEAAuE;YACvE,qEAAqE;YACrE,uCAAuC;YACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAwB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC;IAC5E,yEAAyE;IACzE,8EAA8E;IAC9E,4EAA4E;IAC5E,IAAI,OAAO;QAAE,OAAO,CAAC,OAAO,GAAG,YAAY,CAAE,OAAiC,CAAC,OAAO,CAAC,CAAC;IACxF,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything that runs ONCE THERE IS A LIVE SESSION: the agent event stream
|
|
3
|
+
* (message delivery, prompt replay, terminal keys) and the tmux pane mirror.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `index.ts` because two callers need it — the bin entry at
|
|
6
|
+
* startup, and `select_profile` once a human has chosen an identity — and
|
|
7
|
+
* because a 170-line closure inside a bin entry is not a unit anyone can
|
|
8
|
+
* reason about.
|
|
9
|
+
*
|
|
10
|
+
* The pane is optional. Without one there is nothing to mirror and nowhere to
|
|
11
|
+
* type a message, so only the parts that need a pane are skipped; the session
|
|
12
|
+
* itself is already live and `check_messages` still works.
|
|
13
|
+
*
|
|
14
|
+
* It also holds the STARTUP entry (`startAgent`) and the two ways in — the bin
|
|
15
|
+
* entry at boot, and `select_profile` once a human has chosen an identity — so
|
|
16
|
+
* both take exactly the same path to a live agent.
|
|
17
|
+
*/
|
|
18
|
+
import type { ConnectedContext } from "./connect.js";
|
|
19
|
+
import { type ResolvedProfile } from "./config.js";
|
|
20
|
+
/**
|
|
21
|
+
* Attach the event stream (and, with a pane, the terminal mirror) to a live
|
|
22
|
+
* session.
|
|
23
|
+
*
|
|
24
|
+
* Returns a teardown that detaches BOTH. A profile switch must be able to call
|
|
25
|
+
* it: the mirror keeps calling `appendFrame(oldSession, …)` every couple of
|
|
26
|
+
* seconds and the backend reads a frame as proof of life, so an abandoned
|
|
27
|
+
* runtime keeps the OLD identity online in the dashboard however thoroughly its
|
|
28
|
+
* heartbeat was stopped — and its SSE stream keeps delivering that identity's
|
|
29
|
+
* DMs into this pane and marking them read as it. Idempotent.
|
|
30
|
+
*/
|
|
31
|
+
export declare function startAgentRuntime(ctx: ConnectedContext, log: (line: string) => void): () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Bring this agent online: resolve which identity this terminal is, then
|
|
34
|
+
* connect and start the runtime. When the repo defines several identities and
|
|
35
|
+
* nothing says which one this is, connect NOTHING and record `needs_profile` —
|
|
36
|
+
* guessing would silently collapse two terminals into one agent.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately NOT conditional on tmux: registering a session is what makes the
|
|
39
|
+
* agent visible and reachable, and that must happen whether or not there is a
|
|
40
|
+
* pane to mirror.
|
|
41
|
+
*/
|
|
42
|
+
export declare function startAgent(options?: {
|
|
43
|
+
configPath?: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Connect as a known profile and start the runtime. Used by `select_profile`.
|
|
47
|
+
*
|
|
48
|
+
* Nothing is awaited: `startConnection`'s `settled` stays pending for as long as
|
|
49
|
+
* the backend is down, so awaiting it here would keep the MCP server from
|
|
50
|
+
* serving a single tool call until the backend came up.
|
|
51
|
+
*/
|
|
52
|
+
export declare function connectAs(profile: ResolvedProfile, pane: string | null): void;
|
|
53
|
+
/** Persist a human's pick for this terminal, then connect as it. */
|
|
54
|
+
export declare function selectProfile(profile: ResolvedProfile): Promise<void>;
|