@agentshouse/kit 0.2.1-cb6679d048e60 → 0.2.1-cb66cb2fd0696
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 +47 -46
- package/dist/acp.js +102 -108
- package/dist/agents.js +214 -62
- package/dist/api.js +36 -3
- package/dist/app-git.js +48 -0
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +40 -59
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +161 -95
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +105 -0
- package/dist/conversations.js +732 -330
- package/dist/copies.js +205 -150
- package/dist/end.js +33 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +546 -0
- package/dist/files.js +140 -17
- package/dist/git.js +13 -11
- package/dist/home.js +89 -7
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -159
- package/dist/instructions.js +8 -10
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +119 -104
- package/dist/logout.js +14 -13
- package/dist/mcp.js +8 -3
- package/dist/parts.js +82 -0
- package/dist/placement.js +31 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +41 -0
- package/dist/resident.js +114 -51
- package/dist/scope.js +214 -0
- package/dist/scripts.js +255 -0
- package/dist/secret-input.js +4 -1
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +40 -32
- package/dist/silence.js +28 -0
- package/dist/skills.js +49 -35
- package/dist/transcription.js +129 -41
- package/dist/translate.js +2 -8
- package/dist/update.js +91 -104
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/package.json +20 -20
- package/dist/blocks.js +0 -51
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/stream.js +0 -46
- package/how-we-work.md +0 -23
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/conversations.js
CHANGED
|
@@ -1,63 +1,156 @@
|
|
|
1
1
|
import { client, } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { signedOut, startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal, HouseRefusal } from './api.js';
|
|
8
8
|
import { openBridge } from './bridge.js';
|
|
9
9
|
import { CLIS } from './clis.js';
|
|
10
|
-
import {
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
14
|
+
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
17
|
+
import { killMarked, Scope } from './scope.js';
|
|
13
18
|
import { holdSecretInput } from './secret-input.js';
|
|
14
|
-
import { createHowWeWork } from './skills.js';
|
|
15
19
|
import { transcribe } from './transcription.js';
|
|
20
|
+
import { workFor } from './work.js';
|
|
16
21
|
const KILLED = 'the conversation was killed';
|
|
22
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
23
|
+
const REFUSED = 'the CLI refused the prompt';
|
|
24
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
26
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
27
|
+
const ENDED = {
|
|
28
|
+
conversation_not_found: GONE,
|
|
29
|
+
conversation_historical: RETIRED,
|
|
30
|
+
};
|
|
31
|
+
const NO_START = "the Agent's CLI did not start";
|
|
32
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
34
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
35
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
const UNSTEERED = "the Agent's CLI did not take the message into its running turn";
|
|
37
|
+
class NotStarted extends Error {
|
|
38
|
+
}
|
|
39
|
+
class ResumeRefused extends NotStarted {
|
|
40
|
+
}
|
|
41
|
+
async function step(reason, work) {
|
|
42
|
+
try {
|
|
43
|
+
return await work;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function unopened(reason) {
|
|
50
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
|
+
}
|
|
52
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
53
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
54
|
+
function unbridged(error) {
|
|
55
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
56
|
+
}
|
|
57
|
+
const EXIT_PLAN_MODE = {
|
|
58
|
+
parse(value) {
|
|
59
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
60
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'done', 'option', 'answer']);
|
|
17
66
|
function causeOf(error) {
|
|
18
67
|
return error instanceof Error ? error.message : String(error);
|
|
19
68
|
}
|
|
69
|
+
function answeredWith(error) {
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
}
|
|
72
|
+
function exitedWith(running, cause) {
|
|
73
|
+
if (running.killed)
|
|
74
|
+
return { failure: 'killed', cause: KILLED };
|
|
75
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
76
|
+
}
|
|
20
77
|
function answerMessage(request, response) {
|
|
21
78
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
22
79
|
}
|
|
80
|
+
async function sentPart(url, bytes, headers) {
|
|
81
|
+
const answer = await fetch(relayed(url), { method: 'PUT', headers, body: bytes });
|
|
82
|
+
await answer.arrayBuffer();
|
|
83
|
+
if (!answer.ok)
|
|
84
|
+
throw storeRefusal('upload', answer.status);
|
|
85
|
+
}
|
|
23
86
|
function logged(error) {
|
|
24
87
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
25
88
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
89
|
+
function refusal(conversation, error) {
|
|
90
|
+
if (conversation.kills > 0)
|
|
91
|
+
return KILLED;
|
|
92
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
93
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
94
|
+
return causeOf(error);
|
|
95
|
+
}
|
|
96
|
+
function audioOf(result) {
|
|
97
|
+
if (result.isError === true)
|
|
98
|
+
return null;
|
|
99
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
100
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
101
|
+
}
|
|
102
|
+
function marker(bridge) {
|
|
103
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
104
|
+
}
|
|
105
|
+
function steers(adapter) {
|
|
106
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
28
107
|
}
|
|
29
108
|
class Turn {
|
|
30
109
|
id = randomUUID();
|
|
31
|
-
|
|
32
|
-
segment = null;
|
|
33
|
-
admitted = false;
|
|
34
|
-
notes = [];
|
|
35
|
-
sent = [];
|
|
36
|
-
unsentFrom = null;
|
|
37
|
-
draftSequence = 0;
|
|
38
|
-
planSequence = 0;
|
|
39
|
-
entries = null;
|
|
110
|
+
parts;
|
|
40
111
|
questions = 0;
|
|
41
112
|
secrets = 0;
|
|
42
|
-
|
|
113
|
+
prompted = false;
|
|
114
|
+
steered = false;
|
|
115
|
+
taken = null;
|
|
43
116
|
ended = false;
|
|
117
|
+
images = 0;
|
|
118
|
+
uploads = [];
|
|
119
|
+
constructor(emit) {
|
|
120
|
+
this.parts = new Parts(emit);
|
|
121
|
+
}
|
|
44
122
|
}
|
|
45
123
|
class Conversation {
|
|
46
124
|
id;
|
|
47
125
|
running = null;
|
|
126
|
+
session = null;
|
|
48
127
|
turn = null;
|
|
49
128
|
commands = null;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
129
|
+
options = null;
|
|
130
|
+
exitUntold = false;
|
|
131
|
+
context = null;
|
|
132
|
+
leftovers = new Set();
|
|
133
|
+
waiting = [];
|
|
134
|
+
done = false;
|
|
135
|
+
sending = null;
|
|
136
|
+
subagents = new Map();
|
|
137
|
+
held = [];
|
|
53
138
|
opening = null;
|
|
139
|
+
bridge = null;
|
|
140
|
+
launch = null;
|
|
141
|
+
prompted = null;
|
|
54
142
|
kills = 0;
|
|
55
143
|
killed = new AbortController();
|
|
56
144
|
interrupts = 0;
|
|
57
145
|
interrupted = new AbortController();
|
|
58
|
-
|
|
146
|
+
turns = new Set();
|
|
59
147
|
queue = Promise.resolve();
|
|
60
148
|
reports = Promise.resolve();
|
|
149
|
+
starting = null;
|
|
150
|
+
preparing = null;
|
|
151
|
+
told = null;
|
|
152
|
+
untold = false;
|
|
153
|
+
shown = null;
|
|
61
154
|
constructor(id) {
|
|
62
155
|
this.id = id;
|
|
63
156
|
}
|
|
@@ -73,55 +166,82 @@ function asksSecret(request) {
|
|
|
73
166
|
export class Conversations {
|
|
74
167
|
kit;
|
|
75
168
|
conversations = new Map();
|
|
76
|
-
acks = new Map();
|
|
77
169
|
questions = new Map();
|
|
78
|
-
carrying = 0;
|
|
79
170
|
reporting = 0;
|
|
171
|
+
watched = false;
|
|
80
172
|
constructor(kit) {
|
|
81
173
|
this.kit = kit;
|
|
174
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
175
|
+
setInterval(() => this.look(), 2_000);
|
|
82
176
|
}
|
|
83
|
-
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return work;
|
|
101
|
-
})();
|
|
102
|
-
carried
|
|
103
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
104
|
-
.catch(logged)
|
|
105
|
-
.finally(() => {
|
|
106
|
-
this.carrying--;
|
|
107
|
-
this.kit.changed();
|
|
177
|
+
call(call) {
|
|
178
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
179
|
+
const conversation = this.conversation(call.conversation_id);
|
|
180
|
+
if (call.kind === 'kill') {
|
|
181
|
+
conversation.kills++;
|
|
182
|
+
conversation.killed.abort(new Error(KILLED));
|
|
183
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
184
|
+
waiting.settle({ refused: KILLED });
|
|
185
|
+
this.halt(conversation);
|
|
186
|
+
}
|
|
187
|
+
if (call.kind === 'interrupt') {
|
|
188
|
+
conversation.interrupts++;
|
|
189
|
+
conversation.interrupted.abort();
|
|
190
|
+
}
|
|
191
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
192
|
+
conversation.queue = handled.catch(() => undefined);
|
|
193
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
108
194
|
});
|
|
109
195
|
}
|
|
110
196
|
idle() {
|
|
111
|
-
if (this.
|
|
197
|
+
if (this.reporting > 0)
|
|
112
198
|
return false;
|
|
113
199
|
for (const conversation of this.conversations.values()) {
|
|
114
200
|
const turn = conversation.turn;
|
|
115
201
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
116
|
-
if (conversation.
|
|
202
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
117
203
|
return false;
|
|
118
204
|
}
|
|
119
|
-
|
|
205
|
+
const conversations = [...this.conversations.values()];
|
|
206
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
207
|
+
return false;
|
|
208
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
209
|
+
}
|
|
210
|
+
quiet() {
|
|
211
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
212
|
+
}
|
|
213
|
+
look() {
|
|
214
|
+
for (const conversation of this.conversations.values()) {
|
|
215
|
+
for (const scope of conversation.leftovers) {
|
|
216
|
+
if (!scope.empty())
|
|
217
|
+
continue;
|
|
218
|
+
scope.close();
|
|
219
|
+
conversation.leftovers.delete(scope);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.kit.changed();
|
|
120
223
|
}
|
|
121
224
|
opened() {
|
|
225
|
+
this.watched = false;
|
|
226
|
+
for (const conversation of this.conversations.values()) {
|
|
227
|
+
if (conversation.running !== null) {
|
|
228
|
+
this.facts(conversation);
|
|
229
|
+
this.subagentsChanged(conversation);
|
|
230
|
+
}
|
|
231
|
+
else if (conversation.exitUntold)
|
|
232
|
+
this.process(conversation, false);
|
|
233
|
+
if (conversation.untold || conversation.told !== null)
|
|
234
|
+
this.retell(conversation);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
watching(watched) {
|
|
238
|
+
const resend = watched && !this.watched;
|
|
239
|
+
this.watched = watched;
|
|
240
|
+
if (!resend)
|
|
241
|
+
return;
|
|
122
242
|
for (const conversation of this.conversations.values()) {
|
|
123
243
|
if (conversation.turn !== null)
|
|
124
|
-
conversation.turn
|
|
244
|
+
this.replay(conversation, conversation.turn);
|
|
125
245
|
}
|
|
126
246
|
}
|
|
127
247
|
conversation(id) {
|
|
@@ -132,96 +252,320 @@ export class Conversations {
|
|
|
132
252
|
}
|
|
133
253
|
return conversation;
|
|
134
254
|
}
|
|
135
|
-
async carry(conversation,
|
|
136
|
-
if (
|
|
137
|
-
return this.open(conversation,
|
|
138
|
-
if (
|
|
139
|
-
return this.message(conversation,
|
|
140
|
-
if (
|
|
255
|
+
async carry(conversation, call) {
|
|
256
|
+
if (call.kind === 'open')
|
|
257
|
+
return this.open(conversation, call);
|
|
258
|
+
if (call.kind === 'message')
|
|
259
|
+
return this.message(conversation, call);
|
|
260
|
+
if (call.kind === 'done') {
|
|
261
|
+
conversation.done = true;
|
|
262
|
+
this.next(conversation);
|
|
263
|
+
return { result: Promise.resolve({}) };
|
|
264
|
+
}
|
|
265
|
+
await this.launched(conversation);
|
|
266
|
+
if (call.kind === 'interrupt') {
|
|
141
267
|
if (--conversation.interrupts === 0)
|
|
142
268
|
conversation.interrupted = new AbortController();
|
|
143
|
-
await this.interrupt(conversation, String(
|
|
269
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
144
270
|
}
|
|
145
|
-
if (
|
|
271
|
+
if (call.kind === 'kill') {
|
|
146
272
|
await this.kill(conversation);
|
|
273
|
+
await conversation.sending;
|
|
147
274
|
if (--conversation.kills === 0)
|
|
148
275
|
conversation.killed = new AbortController();
|
|
149
276
|
}
|
|
150
|
-
if (
|
|
151
|
-
await this.option(conversation, String(
|
|
152
|
-
if (
|
|
153
|
-
const question = this.questions.get(String(
|
|
277
|
+
if (call.kind === 'option')
|
|
278
|
+
await this.option(conversation, String(call.option), call.value);
|
|
279
|
+
if (call.kind === 'answer') {
|
|
280
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
154
281
|
if (question === undefined) {
|
|
155
|
-
|
|
282
|
+
const text = answerMessage(call.request, call.response);
|
|
283
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
156
284
|
}
|
|
157
|
-
question.answer(
|
|
285
|
+
question.answer(call.response);
|
|
158
286
|
}
|
|
159
|
-
return {};
|
|
287
|
+
return { result: Promise.resolve({}) };
|
|
288
|
+
}
|
|
289
|
+
async open(conversation, call) {
|
|
290
|
+
await this.stop(conversation);
|
|
291
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
292
|
+
return {
|
|
293
|
+
result: launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) })),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async stop(conversation) {
|
|
297
|
+
const running = conversation.running;
|
|
298
|
+
running?.scope.kill();
|
|
299
|
+
await running?.adapter.exited;
|
|
300
|
+
}
|
|
301
|
+
launch(conversation, route, sessionId, empty) {
|
|
302
|
+
let opened;
|
|
303
|
+
let failed;
|
|
304
|
+
const bridge = new Promise((resolve, reject) => {
|
|
305
|
+
opened = resolve;
|
|
306
|
+
failed = reject;
|
|
307
|
+
});
|
|
308
|
+
bridge.catch(() => undefined);
|
|
309
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
310
|
+
session.catch(failed);
|
|
311
|
+
const launch = { bridge, session };
|
|
312
|
+
conversation.launch = launch;
|
|
313
|
+
void session
|
|
314
|
+
.catch(() => undefined)
|
|
315
|
+
.then(() => {
|
|
316
|
+
if (conversation.launch !== launch)
|
|
317
|
+
return;
|
|
318
|
+
conversation.launch = null;
|
|
319
|
+
this.closeIdleBridge(conversation);
|
|
320
|
+
this.next(conversation);
|
|
321
|
+
});
|
|
322
|
+
this.next(conversation);
|
|
323
|
+
return launch;
|
|
324
|
+
}
|
|
325
|
+
async launched(conversation) {
|
|
326
|
+
while (conversation.launch !== null)
|
|
327
|
+
await conversation.launch.session.catch(() => undefined);
|
|
328
|
+
}
|
|
329
|
+
instructed(conversation, route) {
|
|
330
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
331
|
+
if (bridge === undefined)
|
|
332
|
+
return null;
|
|
333
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
334
|
+
read.catch(() => undefined);
|
|
335
|
+
return read;
|
|
160
336
|
}
|
|
161
|
-
async
|
|
162
|
-
|
|
163
|
-
|
|
337
|
+
async message(conversation, call) {
|
|
338
|
+
const content = [
|
|
339
|
+
...(typeof call.recent === 'string' ? [{ type: 'text', text: call.recent }] : []),
|
|
340
|
+
{ type: 'text', text: String(call.text) },
|
|
341
|
+
];
|
|
342
|
+
let route;
|
|
343
|
+
let instructing = null;
|
|
164
344
|
try {
|
|
165
|
-
|
|
345
|
+
route = await this.route(conversation, call.agent_id);
|
|
346
|
+
if (call.first === true)
|
|
347
|
+
instructing = this.instructed(conversation, route);
|
|
348
|
+
if (call.message_id !== null)
|
|
349
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
350
|
+
if (conversation.kills > 0)
|
|
351
|
+
throw new Error(KILLED);
|
|
166
352
|
}
|
|
167
353
|
catch (error) {
|
|
168
|
-
|
|
354
|
+
this.closeIdleBridge(conversation);
|
|
355
|
+
this.prepares(conversation, null);
|
|
356
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
357
|
+
}
|
|
358
|
+
conversation.preparing = null;
|
|
359
|
+
const { promise, resolve: settle } = Promise.withResolvers();
|
|
360
|
+
const waiting = { call, route, content, instructing, settle };
|
|
361
|
+
if (call.at_once === true && conversation.turns.size > 0)
|
|
362
|
+
await this.atOnce(conversation, waiting);
|
|
363
|
+
else
|
|
364
|
+
conversation.waiting.push(waiting);
|
|
365
|
+
this.next(conversation);
|
|
366
|
+
return { result: promise };
|
|
367
|
+
}
|
|
368
|
+
async atOnce(conversation, waiting) {
|
|
369
|
+
while (conversation.sending !== null)
|
|
370
|
+
await conversation.sending;
|
|
371
|
+
const running = conversation.running;
|
|
372
|
+
if (running !== null && conversation.turns.size > 0 && steers(running.adapter)) {
|
|
373
|
+
conversation.sending = this.steer(conversation, running, waiting);
|
|
374
|
+
await conversation.sending;
|
|
375
|
+
conversation.sending = null;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.ahead(conversation, waiting);
|
|
379
|
+
if (conversation.turns.size > 0)
|
|
380
|
+
await this.cancel(conversation);
|
|
381
|
+
}
|
|
382
|
+
ahead(conversation, waiting) {
|
|
383
|
+
const behind = conversation.waiting.findIndex((held) => held.call.at_once !== true);
|
|
384
|
+
conversation.waiting.splice(behind === -1 ? conversation.waiting.length : behind, 0, waiting);
|
|
385
|
+
}
|
|
386
|
+
async steer(conversation, running, waiting) {
|
|
387
|
+
let outcome;
|
|
388
|
+
try {
|
|
389
|
+
({ outcome } = await running.adapter.connection.agent.request('_session/steering', {
|
|
390
|
+
sessionId: running.sessionId,
|
|
391
|
+
prompt: waiting.content,
|
|
392
|
+
_meta: { steering: { idleBehavior: 'promptRequired' } },
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
outcome = error;
|
|
397
|
+
}
|
|
398
|
+
if (outcome === 'promptRequired') {
|
|
399
|
+
this.ahead(conversation, waiting);
|
|
400
|
+
}
|
|
401
|
+
else if (outcome === 'injected' || outcome === 'startedNewTurn') {
|
|
402
|
+
if (outcome === 'injected') {
|
|
403
|
+
for (const turn of conversation.turns)
|
|
404
|
+
turn.steered ||= turn.prompted;
|
|
405
|
+
}
|
|
406
|
+
this.kit.calls.keepResult(String(waiting.call.input_id), {});
|
|
407
|
+
waiting.settle({});
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const cause = outcome instanceof Error ? outcome : new Error(`the CLI answered ${JSON.stringify(outcome)}`);
|
|
411
|
+
waiting.settle({ refused: refusal(conversation, new NotStarted(UNSTEERED, { cause })) });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
next(conversation) {
|
|
415
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
416
|
+
return;
|
|
417
|
+
const waiting = conversation.waiting.shift();
|
|
418
|
+
if (waiting === undefined) {
|
|
419
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
420
|
+
if (conversation.starting !== step)
|
|
421
|
+
this.starts(conversation, step);
|
|
422
|
+
if (conversation.done && conversation.launch === null)
|
|
423
|
+
void this.stop(conversation).catch(logged);
|
|
424
|
+
return;
|
|
169
425
|
}
|
|
426
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
427
|
+
conversation.sending = null;
|
|
428
|
+
this.next(conversation);
|
|
429
|
+
});
|
|
170
430
|
}
|
|
171
|
-
async
|
|
172
|
-
|
|
431
|
+
async send(conversation, waiting) {
|
|
432
|
+
const { call } = waiting;
|
|
433
|
+
let result = {};
|
|
434
|
+
let started = false;
|
|
435
|
+
let instructing = waiting.instructing;
|
|
173
436
|
const prompt = [];
|
|
174
437
|
try {
|
|
175
|
-
const route = await this.route(conversation, input.agent_id);
|
|
176
|
-
const files = input.files;
|
|
177
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
178
|
-
const lines = await this.transcribed(conversation, files, paths);
|
|
179
438
|
if (conversation.running === null) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
439
|
+
this.starts(conversation, 'session');
|
|
440
|
+
await this.launched(conversation);
|
|
441
|
+
}
|
|
442
|
+
if (conversation.running === null) {
|
|
443
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
444
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
445
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
446
|
+
if (call.first === true)
|
|
447
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
448
|
+
started = (await launch.session) !== stored;
|
|
449
|
+
}
|
|
450
|
+
const session = conversation.running.sessionId;
|
|
451
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
452
|
+
result = { provider_session_id: session };
|
|
183
453
|
}
|
|
184
|
-
if (
|
|
185
|
-
prompt.push({
|
|
186
|
-
type: 'text',
|
|
187
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
188
|
-
});
|
|
454
|
+
if (call.first === true) {
|
|
455
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
189
456
|
}
|
|
190
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
191
|
-
if (lines.length > 0)
|
|
192
|
-
prompt.push({ type: 'text', text: lines.join('\n') });
|
|
193
457
|
if (conversation.kills > 0)
|
|
194
458
|
throw new Error(KILLED);
|
|
195
459
|
}
|
|
196
460
|
catch (error) {
|
|
197
|
-
|
|
461
|
+
this.starts(conversation, null);
|
|
462
|
+
if (started) {
|
|
463
|
+
conversation.session = null;
|
|
198
464
|
await this.kill(conversation);
|
|
199
|
-
|
|
465
|
+
}
|
|
466
|
+
const refused = refusal(conversation, error);
|
|
467
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
468
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
469
|
+
return;
|
|
200
470
|
}
|
|
201
|
-
this.
|
|
202
|
-
|
|
471
|
+
this.starts(conversation, null);
|
|
472
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
473
|
+
waiting.settle(result);
|
|
203
474
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
475
|
+
starts(conversation, step) {
|
|
476
|
+
conversation.starting = step;
|
|
477
|
+
if (conversation.shown !== null)
|
|
478
|
+
clearTimeout(conversation.shown);
|
|
479
|
+
conversation.shown = null;
|
|
480
|
+
if (step === null || conversation.told !== null) {
|
|
481
|
+
this.tell(conversation, step);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
conversation.shown = setTimeout(() => {
|
|
485
|
+
conversation.shown = null;
|
|
486
|
+
if (conversation.starting === step)
|
|
487
|
+
this.tell(conversation, step);
|
|
488
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
489
|
+
}
|
|
490
|
+
prepares(conversation, step) {
|
|
491
|
+
conversation.preparing = step;
|
|
492
|
+
this.next(conversation);
|
|
493
|
+
}
|
|
494
|
+
tell(conversation, step) {
|
|
495
|
+
if (conversation.told === step && !conversation.untold)
|
|
496
|
+
return;
|
|
497
|
+
conversation.told = step;
|
|
498
|
+
this.retell(conversation);
|
|
499
|
+
}
|
|
500
|
+
retell(conversation) {
|
|
501
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
502
|
+
}
|
|
503
|
+
async transcribed(conversation, messageId, text) {
|
|
504
|
+
const paths = linkedPaths(text);
|
|
505
|
+
if (paths.length === 0)
|
|
506
|
+
return [];
|
|
507
|
+
const killed = conversation.killed.signal;
|
|
508
|
+
const interrupted = conversation.interrupted.signal;
|
|
509
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
510
|
+
let signal = killed;
|
|
511
|
+
const blocks = [];
|
|
512
|
+
try {
|
|
513
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
514
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
515
|
+
for (const path of paths) {
|
|
516
|
+
let answered = await read(path);
|
|
517
|
+
if (answered === null)
|
|
518
|
+
continue;
|
|
519
|
+
signal = transcribing;
|
|
520
|
+
this.prepares(conversation, 'transcription');
|
|
521
|
+
const said = await transcribe(async () => {
|
|
522
|
+
const address = answered ?? (await read(path));
|
|
523
|
+
answered = null;
|
|
524
|
+
if (address === null)
|
|
525
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
526
|
+
return address;
|
|
527
|
+
}, signal);
|
|
528
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
529
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
530
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
219
531
|
}
|
|
220
532
|
}
|
|
221
|
-
|
|
533
|
+
catch (error) {
|
|
534
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
535
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
return blocks;
|
|
539
|
+
}
|
|
540
|
+
bridged(conversation) {
|
|
541
|
+
if (conversation.bridge === null) {
|
|
542
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
543
|
+
conversation.bridge = bridging;
|
|
544
|
+
bridging.catch(() => {
|
|
545
|
+
if (conversation.bridge === bridging)
|
|
546
|
+
conversation.bridge = null;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return conversation.bridge;
|
|
550
|
+
}
|
|
551
|
+
closeBridge(conversation, bridging, bridge) {
|
|
552
|
+
bridge.close();
|
|
553
|
+
if (conversation.bridge === bridging)
|
|
554
|
+
conversation.bridge = null;
|
|
555
|
+
}
|
|
556
|
+
closeIdleBridge(conversation) {
|
|
557
|
+
const bridging = conversation.bridge;
|
|
558
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
559
|
+
return;
|
|
560
|
+
conversation.bridge = null;
|
|
561
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
222
562
|
}
|
|
223
563
|
async interrupt(conversation, turnId) {
|
|
224
|
-
if (conversation.
|
|
564
|
+
if ([...conversation.turns].some((turn) => turn.id === turnId))
|
|
565
|
+
await this.cancel(conversation);
|
|
566
|
+
}
|
|
567
|
+
async cancel(conversation) {
|
|
568
|
+
if (conversation.running === null)
|
|
225
569
|
return;
|
|
226
570
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
227
571
|
sessionId: conversation.running.sessionId,
|
|
@@ -229,11 +573,13 @@ export class Conversations {
|
|
|
229
573
|
}
|
|
230
574
|
halt(conversation) {
|
|
231
575
|
if (conversation.opening !== null)
|
|
232
|
-
|
|
576
|
+
killMarked(marker(conversation.opening));
|
|
577
|
+
for (const scope of conversation.leftovers)
|
|
578
|
+
scope.kill();
|
|
233
579
|
if (conversation.running === null)
|
|
234
580
|
return;
|
|
235
581
|
conversation.running.killed = true;
|
|
236
|
-
|
|
582
|
+
conversation.running.scope.kill();
|
|
237
583
|
}
|
|
238
584
|
async kill(conversation) {
|
|
239
585
|
const running = conversation.running;
|
|
@@ -245,6 +591,7 @@ export class Conversations {
|
|
|
245
591
|
if (running === null)
|
|
246
592
|
return;
|
|
247
593
|
try {
|
|
594
|
+
running.reader.requested(option, value);
|
|
248
595
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
249
596
|
sessionId: running.sessionId,
|
|
250
597
|
configId: option,
|
|
@@ -253,6 +600,7 @@ export class Conversations {
|
|
|
253
600
|
this.options(conversation, answered.configOptions);
|
|
254
601
|
}
|
|
255
602
|
catch (error) {
|
|
603
|
+
running.reader.refused(option);
|
|
256
604
|
logged(error);
|
|
257
605
|
}
|
|
258
606
|
}
|
|
@@ -269,58 +617,82 @@ export class Conversations {
|
|
|
269
617
|
}
|
|
270
618
|
return route;
|
|
271
619
|
}
|
|
272
|
-
async start(conversation,
|
|
620
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
621
|
+
const route = await routed;
|
|
622
|
+
const reader = readerFor(route.kind);
|
|
623
|
+
reader.session = sessionId;
|
|
273
624
|
const app = client({ name: '@agentshouse/kit' })
|
|
274
|
-
.onNotification('session/update', ({ params }) => this.update(conversation, params))
|
|
275
|
-
.onNotification(TURN_STARTED, (params) => params, () => void this.started(conversation))
|
|
276
|
-
.onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
|
|
277
625
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
278
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
626
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
627
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
279
628
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
280
|
-
const
|
|
629
|
+
const bridging = this.bridged(conversation);
|
|
630
|
+
const bridge = await step(unbridged, bridging);
|
|
281
631
|
if (conversation.kills > 0) {
|
|
282
|
-
|
|
632
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
283
633
|
throw new Error(KILLED);
|
|
284
634
|
}
|
|
285
635
|
conversation.opening = bridge;
|
|
636
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
637
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
638
|
+
let adapter = null;
|
|
286
639
|
try {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
640
|
+
bridged(bridge);
|
|
641
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
642
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
643
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
644
|
+
work.read(message);
|
|
645
|
+
this.read(conversation, reader, message);
|
|
646
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
647
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
290
648
|
const agent = adapter.connection.agent;
|
|
291
649
|
const cwd = route.working_directory;
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
sessionId: opened.sessionId,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
650
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
651
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
652
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
653
|
+
reader.session = opened.sessionId;
|
|
654
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
655
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
656
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
657
|
+
}
|
|
658
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
659
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
660
|
+
void work.settled().then(() => scope.record());
|
|
304
661
|
conversation.running = running;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.
|
|
309
|
-
void adapter.
|
|
662
|
+
adapter.turn(conversation.turns.size > 0);
|
|
663
|
+
conversation.session = running.sessionId;
|
|
664
|
+
conversation.options = options;
|
|
665
|
+
this.facts(conversation);
|
|
666
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
310
667
|
return running.sessionId;
|
|
311
668
|
}
|
|
312
669
|
catch (error) {
|
|
313
|
-
|
|
314
|
-
|
|
670
|
+
adapter?.retire();
|
|
671
|
+
work.close();
|
|
672
|
+
conversation.leftovers.add(scope);
|
|
673
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
315
674
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
316
675
|
}
|
|
317
676
|
finally {
|
|
318
677
|
conversation.opening = null;
|
|
319
678
|
}
|
|
320
679
|
}
|
|
680
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
681
|
+
try {
|
|
682
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
686
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
687
|
+
}
|
|
688
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
321
692
|
async launchSettings(running, route, offered) {
|
|
322
693
|
let options = offered;
|
|
323
694
|
const settings = [
|
|
695
|
+
['mode', route.mode ?? CLIS[route.kind].fullAccess],
|
|
324
696
|
['model', route.model],
|
|
325
697
|
['thought_level', route.effort],
|
|
326
698
|
];
|
|
@@ -328,6 +700,7 @@ export class Conversations {
|
|
|
328
700
|
if (value === null)
|
|
329
701
|
continue;
|
|
330
702
|
const option = options.find((candidate) => candidate.category === category);
|
|
703
|
+
running.reader.requested(option?.id ?? category, value);
|
|
331
704
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
332
705
|
sessionId: running.sessionId,
|
|
333
706
|
configId: option?.id ?? category,
|
|
@@ -337,23 +710,45 @@ export class Conversations {
|
|
|
337
710
|
}
|
|
338
711
|
return options;
|
|
339
712
|
}
|
|
340
|
-
exited(conversation,
|
|
341
|
-
|
|
342
|
-
if (
|
|
343
|
-
|
|
713
|
+
exited(conversation, running, cause) {
|
|
714
|
+
this.detach(conversation, running);
|
|
715
|
+
if (conversation.turn !== null) {
|
|
716
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
717
|
+
}
|
|
718
|
+
this.next(conversation);
|
|
719
|
+
this.kit.changed();
|
|
720
|
+
}
|
|
721
|
+
detach(conversation, running) {
|
|
722
|
+
if (conversation.bridge === running.bridging)
|
|
723
|
+
conversation.bridge = null;
|
|
724
|
+
running.work.close();
|
|
725
|
+
conversation.leftovers.add(running.scope);
|
|
344
726
|
conversation.running = null;
|
|
345
727
|
conversation.commands = null;
|
|
346
|
-
conversation.
|
|
347
|
-
conversation.
|
|
728
|
+
conversation.options = null;
|
|
729
|
+
if (conversation.subagents.size > 0) {
|
|
730
|
+
conversation.subagents.clear();
|
|
731
|
+
this.subagentsChanged(conversation);
|
|
732
|
+
}
|
|
348
733
|
for (const [id, question] of this.questions) {
|
|
349
734
|
if (question.conversation === conversation)
|
|
350
735
|
this.questions.delete(id);
|
|
351
736
|
}
|
|
352
|
-
this.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
737
|
+
this.process(conversation, false);
|
|
738
|
+
}
|
|
739
|
+
process(conversation, running) {
|
|
740
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
741
|
+
}
|
|
742
|
+
facts(conversation) {
|
|
743
|
+
this.process(conversation, true);
|
|
744
|
+
if (conversation.commands !== null)
|
|
745
|
+
this.commands(conversation, conversation.commands);
|
|
746
|
+
if (conversation.options !== null)
|
|
747
|
+
this.options(conversation, conversation.options);
|
|
748
|
+
}
|
|
749
|
+
subagentsChanged(conversation) {
|
|
750
|
+
const subagents = [...conversation.subagents.values()].map(({ description, activity }) => ({ description, activity }));
|
|
751
|
+
this.kit.send({ type: 'subagents', conversation_id: conversation.id, subagents });
|
|
357
752
|
}
|
|
358
753
|
commands(conversation, commands) {
|
|
359
754
|
conversation.commands = commands;
|
|
@@ -364,11 +759,12 @@ export class Conversations {
|
|
|
364
759
|
options(conversation, options) {
|
|
365
760
|
if (conversation.running === null)
|
|
366
761
|
return;
|
|
762
|
+
conversation.options = options;
|
|
367
763
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
368
764
|
}
|
|
369
765
|
deliver(conversation, turn, event, body) {
|
|
370
766
|
this.reporting++;
|
|
371
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
767
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
372
768
|
conversation.reports = delivered
|
|
373
769
|
.catch(() => undefined)
|
|
374
770
|
.finally(() => {
|
|
@@ -377,122 +773,103 @@ export class Conversations {
|
|
|
377
773
|
});
|
|
378
774
|
return delivered;
|
|
379
775
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
.then(() => this.admitted(conversation, turn))
|
|
385
|
-
.catch(logged);
|
|
386
|
-
return turn;
|
|
776
|
+
write(conversation, turn, operation) {
|
|
777
|
+
if (turn.ended || !this.watched)
|
|
778
|
+
return;
|
|
779
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
387
780
|
}
|
|
388
|
-
|
|
389
|
-
turn.
|
|
390
|
-
|
|
391
|
-
this.kit.send(note);
|
|
392
|
-
if (turn.text !== '') {
|
|
393
|
-
turn.unsentFrom = 0;
|
|
394
|
-
this.draft(conversation, turn);
|
|
395
|
-
}
|
|
396
|
-
if (turn.entries !== null)
|
|
397
|
-
this.plan(conversation, turn);
|
|
781
|
+
replay(conversation, turn) {
|
|
782
|
+
for (const operation of turn.parts.operations())
|
|
783
|
+
this.write(conversation, turn, operation);
|
|
398
784
|
}
|
|
399
785
|
begin(conversation) {
|
|
400
|
-
const turn = this.
|
|
786
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
787
|
+
conversation.turns.add(turn);
|
|
788
|
+
conversation.running?.adapter.turn(true);
|
|
789
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
401
790
|
conversation.turn = turn;
|
|
791
|
+
if (conversation.context !== null)
|
|
792
|
+
turn.parts.measure(conversation.context);
|
|
793
|
+
for (const part of conversation.held.splice(0))
|
|
794
|
+
turn.parts.start(part);
|
|
402
795
|
this.kit.changed();
|
|
403
796
|
return turn;
|
|
404
797
|
}
|
|
405
|
-
|
|
406
|
-
if (turn.ended)
|
|
407
|
-
return;
|
|
408
|
-
const segment = turn.segment;
|
|
409
|
-
turn.segment = null;
|
|
410
|
-
if (segment?.phase === 'note')
|
|
411
|
-
this.note(conversation, turn, segment.text);
|
|
412
|
-
else if (segment !== null)
|
|
413
|
-
this.answer(conversation, turn, segment.text);
|
|
414
|
-
this.end(conversation, turn, { text: turn.text });
|
|
415
|
-
}
|
|
416
|
-
end(conversation, turn, outcome) {
|
|
798
|
+
end(conversation, turn, failed, notice = null) {
|
|
417
799
|
if (turn.ended)
|
|
418
800
|
return;
|
|
801
|
+
if (failed !== null)
|
|
802
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
803
|
+
if (conversation.turn === turn) {
|
|
804
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
805
|
+
this.apply(conversation, turn, event);
|
|
806
|
+
}
|
|
807
|
+
if (notice !== null)
|
|
808
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
419
809
|
turn.ended = true;
|
|
420
|
-
conversation.
|
|
810
|
+
conversation.turns.delete(turn);
|
|
811
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
421
812
|
if (conversation.turn === turn)
|
|
422
813
|
conversation.turn = null;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
814
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
815
|
+
parts: turn.parts.saved(),
|
|
816
|
+
context: turn.parts.context,
|
|
817
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
818
|
+
}));
|
|
819
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
820
|
+
this.next(conversation);
|
|
427
821
|
this.kit.changed();
|
|
428
822
|
}
|
|
429
|
-
|
|
430
|
-
if (conversation.open > 0)
|
|
431
|
-
conversation.late.push(cause);
|
|
432
|
-
else
|
|
433
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
434
|
-
}
|
|
435
|
-
prompt(conversation, prompt) {
|
|
823
|
+
prompt(conversation, prompt, taken) {
|
|
436
824
|
const running = conversation.running;
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
else
|
|
448
|
-
this.end(conversation,
|
|
825
|
+
const turn = this.begin(conversation);
|
|
826
|
+
turn.prompted = true;
|
|
827
|
+
turn.taken = taken;
|
|
828
|
+
conversation.prompted = running.sessionId;
|
|
829
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
830
|
+
this.took(turn);
|
|
831
|
+
const notice = answeredFailure(answer);
|
|
832
|
+
if (notice !== null) {
|
|
833
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
837
|
+
}
|
|
838
|
+
}, async (error) => {
|
|
839
|
+
if (running.adapter.connection.signal.aborted) {
|
|
840
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
this.took(turn);
|
|
844
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
845
|
+
running.adapter.retire();
|
|
846
|
+
this.detach(conversation, running);
|
|
847
|
+
}
|
|
848
|
+
this.end(conversation, turn, answeredWith(error));
|
|
449
849
|
}
|
|
450
850
|
});
|
|
451
851
|
}
|
|
452
|
-
attach(turn, sent) {
|
|
453
|
-
turn.prompt = sent;
|
|
454
|
-
sent.turn = turn;
|
|
455
|
-
}
|
|
456
|
-
answered(conversation, sent, failed) {
|
|
457
|
-
const turn = sent.turn;
|
|
458
|
-
if (turn === null) {
|
|
459
|
-
const queued = conversation.queued.indexOf(sent);
|
|
460
|
-
if (queued < 0)
|
|
461
|
-
return;
|
|
462
|
-
conversation.queued.splice(queued, 1);
|
|
463
|
-
if (failed !== null)
|
|
464
|
-
this.failed(conversation, failed);
|
|
465
|
-
}
|
|
466
|
-
else if (failed !== null) {
|
|
467
|
-
if (turn.ended)
|
|
468
|
-
this.failed(conversation, failed);
|
|
469
|
-
else
|
|
470
|
-
this.end(conversation, turn, { failed });
|
|
471
|
-
}
|
|
472
|
-
else if (turn.prompt === sent) {
|
|
473
|
-
this.finish(conversation, turn);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
852
|
started(conversation) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const turn = this.begin(conversation);
|
|
480
|
-
const queued = conversation.queued.shift();
|
|
481
|
-
if (queued !== undefined)
|
|
482
|
-
this.attach(turn, queued);
|
|
853
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
854
|
+
this.took(turn);
|
|
483
855
|
return turn;
|
|
484
856
|
}
|
|
485
|
-
ended(conversation) {
|
|
857
|
+
ended(conversation, stop) {
|
|
486
858
|
const turn = conversation.turn;
|
|
487
859
|
if (turn === null)
|
|
488
860
|
return;
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
this.kit.changed();
|
|
861
|
+
const held = stop !== null && !turn.steered && [...conversation.subagents.values()].some((subagent) => subagent.turn === turn);
|
|
862
|
+
if (!turn.prompted || held) {
|
|
863
|
+
this.took(turn);
|
|
864
|
+
this.end(conversation, turn, stop === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
865
|
+
return;
|
|
495
866
|
}
|
|
867
|
+
if (stop !== null)
|
|
868
|
+
return;
|
|
869
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
870
|
+
this.apply(conversation, turn, event);
|
|
871
|
+
conversation.turn = null;
|
|
872
|
+
this.kit.changed();
|
|
496
873
|
}
|
|
497
874
|
async ask(conversation, method, params, secret, signal) {
|
|
498
875
|
const turn = this.started(conversation);
|
|
@@ -538,101 +915,126 @@ export class Conversations {
|
|
|
538
915
|
if (content !== null)
|
|
539
916
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
540
917
|
}
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
918
|
+
read(conversation, reader, message) {
|
|
919
|
+
for (const event of reader.read(message))
|
|
920
|
+
this.event(conversation, event);
|
|
921
|
+
}
|
|
922
|
+
event(conversation, event) {
|
|
923
|
+
switch (event.kind) {
|
|
924
|
+
case 'started':
|
|
925
|
+
this.started(conversation);
|
|
926
|
+
return;
|
|
927
|
+
case 'ended':
|
|
928
|
+
this.ended(conversation, event.stop);
|
|
929
|
+
return;
|
|
930
|
+
case 'commands':
|
|
931
|
+
this.commands(conversation, event.commands);
|
|
932
|
+
return;
|
|
933
|
+
case 'options':
|
|
934
|
+
this.options(conversation, event.options);
|
|
935
|
+
return;
|
|
936
|
+
case 'context':
|
|
937
|
+
conversation.context = { used: event.used, window: event.window };
|
|
938
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
939
|
+
return;
|
|
940
|
+
case 'subagent-done':
|
|
941
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
942
|
+
return;
|
|
943
|
+
case 'subagent-activity':
|
|
944
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
945
|
+
return;
|
|
946
|
+
case 'marker':
|
|
947
|
+
case 'image':
|
|
948
|
+
if (conversation.turn !== null)
|
|
949
|
+
break;
|
|
950
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
951
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
952
|
+
return;
|
|
565
953
|
}
|
|
954
|
+
this.apply(conversation, this.started(conversation), event);
|
|
566
955
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
conversation.jobs.delete(job.id);
|
|
572
|
-
this.kit.changed();
|
|
956
|
+
took(turn) {
|
|
957
|
+
const taken = turn.taken;
|
|
958
|
+
turn.taken = null;
|
|
959
|
+
taken?.();
|
|
573
960
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
961
|
+
apply(conversation, turn, event) {
|
|
962
|
+
switch (event.kind) {
|
|
963
|
+
case 'text':
|
|
964
|
+
case 'reasoning':
|
|
965
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
966
|
+
return;
|
|
967
|
+
case 'command':
|
|
968
|
+
turn.parts.command();
|
|
969
|
+
return;
|
|
970
|
+
case 'subagent': {
|
|
971
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
972
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index, activity: null });
|
|
973
|
+
this.subagentsChanged(conversation);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
case 'message':
|
|
977
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
978
|
+
return;
|
|
979
|
+
case 'marker':
|
|
980
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
981
|
+
return;
|
|
982
|
+
case 'plan':
|
|
983
|
+
turn.parts.plan(event.entries);
|
|
984
|
+
return;
|
|
985
|
+
case 'image':
|
|
986
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
987
|
+
this.kit.changed();
|
|
988
|
+
return;
|
|
582
989
|
}
|
|
583
|
-
turn.segment ??= { messageId, phase, text: '' };
|
|
584
|
-
turn.segment.text += text;
|
|
585
990
|
}
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
991
|
+
subagentDone(conversation, id, result) {
|
|
992
|
+
const subagent = conversation.subagents.get(id);
|
|
993
|
+
if (subagent === undefined)
|
|
589
994
|
return;
|
|
590
|
-
|
|
591
|
-
this.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (text.trim() === '')
|
|
995
|
+
conversation.subagents.delete(id);
|
|
996
|
+
this.subagentsChanged(conversation);
|
|
997
|
+
if (subagent.turn === conversation.turn) {
|
|
998
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
595
999
|
return;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1000
|
+
}
|
|
1001
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
1002
|
+
if (conversation.turn === null)
|
|
1003
|
+
conversation.held.push(part);
|
|
599
1004
|
else
|
|
600
|
-
turn.
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
1005
|
+
conversation.turn.parts.start(part);
|
|
1006
|
+
}
|
|
1007
|
+
subagentActivity(conversation, id, activity) {
|
|
1008
|
+
const subagent = conversation.subagents.get(id);
|
|
1009
|
+
if (subagent === undefined || subagent.activity === activity)
|
|
1010
|
+
return;
|
|
1011
|
+
subagent.activity = activity;
|
|
1012
|
+
this.subagentsChanged(conversation);
|
|
1013
|
+
}
|
|
1014
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
1015
|
+
const bytes = Buffer.from(data, 'base64');
|
|
1016
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1017
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
1018
|
+
try {
|
|
1019
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
1020
|
+
name,
|
|
1021
|
+
media_type: mediaType,
|
|
1022
|
+
bytes: bytes.length,
|
|
1023
|
+
sha256,
|
|
1024
|
+
}));
|
|
1025
|
+
const saved = await uploaded(declared.upload, {
|
|
1026
|
+
name,
|
|
1027
|
+
bytes: bytes.length,
|
|
1028
|
+
sha256,
|
|
1029
|
+
read: (start, end) => [bytes.subarray(start, end)],
|
|
1030
|
+
send: (url, start, end, headers) => sentPart(url, bytes.subarray(start, end), headers),
|
|
1031
|
+
finish: (upload, partSha256) => this.kit.house.deliver('/kit/uploads/finish', { upload, part_sha256: partSha256 }),
|
|
1032
|
+
});
|
|
1033
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
621
1034
|
}
|
|
622
|
-
|
|
623
|
-
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
1037
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
624
1038
|
}
|
|
625
1039
|
}
|
|
626
|
-
plan(conversation, turn) {
|
|
627
|
-
const sent = this.kit.send({
|
|
628
|
-
type: 'plan',
|
|
629
|
-
conversation_id: conversation.id,
|
|
630
|
-
turn_id: turn.id,
|
|
631
|
-
sequence: turn.planSequence + 1,
|
|
632
|
-
from: 0,
|
|
633
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
634
|
-
});
|
|
635
|
-
if (sent)
|
|
636
|
-
turn.planSequence++;
|
|
637
|
-
}
|
|
638
1040
|
}
|