@timqi/pier 0.1.14 → 0.3.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 +44 -22
- package/dist/agent/config.js +2 -2
- package/dist/agent/events.js +32 -3
- package/dist/agent/listing.js +14 -10
- package/dist/agent/packages.js +6 -10
- package/dist/agent/pi.js +113 -70
- package/dist/agent/roles.js +104 -0
- package/dist/agent/types.js +78 -0
- package/dist/boards/boards.js +42 -32
- package/dist/channels/chains.js +0 -4
- package/dist/channels/commands.js +7 -12
- package/dist/channels/config.js +27 -1
- package/dist/channels/control.js +5 -16
- package/dist/channels/conversations.js +4 -9
- package/dist/channels/lark-api.js +20 -26
- package/dist/channels/lark-outbound.js +21 -29
- package/dist/channels/lark-panel.js +1 -1
- package/dist/channels/lark.js +81 -39
- package/dist/channels/lines.js +33 -1
- package/dist/channels/panel.js +17 -107
- package/dist/channels/routes.js +11 -21
- package/dist/channels/runtime.js +79 -25
- package/dist/channels/slack-outbound.js +12 -14
- package/dist/channels/slack-panel.js +1 -1
- package/dist/channels/slack-render.js +1 -1
- package/dist/channels/slack-thread.js +65 -0
- package/dist/channels/slack.js +85 -124
- package/dist/channels/types.js +1 -1
- package/dist/cli.js +7 -2
- package/dist/config-sync.js +42 -37
- package/dist/core/chain.js +221 -0
- package/dist/core/identity.js +36 -13
- package/dist/core/inbound-file.js +11 -4
- package/dist/core/reply.js +111 -81
- package/dist/core/router.js +111 -118
- package/dist/core/search.js +65 -0
- package/dist/core/types.js +38 -77
- package/dist/db.js +41 -2
- package/dist/drain.js +1 -1
- package/dist/main.js +65 -40
- package/dist/settings.js +26 -19
- package/dist/socket.js +1 -0
- package/dist/tasks/agent.js +11 -5
- package/dist/tasks/callbacks.js +55 -5
- package/dist/tasks/cli.js +22 -11
- package/dist/tasks/definitions.js +30 -17
- package/dist/tasks/execution.js +11 -5
- package/dist/tasks/groups.js +5 -2
- package/dist/tasks/messages.js +7 -7
- package/dist/tasks/open-items.js +88 -0
- package/dist/tasks/operations.js +76 -39
- package/dist/tasks/outbox.js +19 -6
- package/dist/tasks/routes.js +19 -249
- package/dist/tasks/service.js +166 -34
- package/dist/tasks/store.js +123 -57
- package/dist/tasks/types.js +3 -0
- package/dist/tools.js +12 -5
- package/dist/web/auth.js +2 -2
- package/dist/web/config-sync.js +4 -5
- package/dist/web/config.js +9 -2
- package/dist/web/explorer.js +2 -2
- package/dist/web/fs.js +16 -1
- package/dist/web/instance.js +8 -5
- package/dist/web/packages.js +1 -1
- package/dist/web/passkeys.js +3 -3
- package/dist/web/providers.js +3 -3
- package/dist/web/public/assets/code-CWT27KH1.js +2 -0
- package/dist/web/public/assets/code-CWT27KH1.js.br +0 -0
- package/dist/web/public/assets/code-CWT27KH1.js.gz +0 -0
- package/dist/web/public/assets/explorer-C4DhXFCm.js +5 -0
- package/dist/web/public/assets/explorer-C4DhXFCm.js.br +0 -0
- package/dist/web/public/assets/explorer-C4DhXFCm.js.gz +0 -0
- package/dist/web/public/assets/highlight-CDSk2hRJ.js +72 -0
- package/dist/web/public/assets/highlight-CDSk2hRJ.js.br +0 -0
- package/dist/web/public/assets/highlight-CDSk2hRJ.js.gz +0 -0
- package/dist/web/public/assets/{hljs-tWqyD59G.js → hljs-BRz6a8Dq.js} +2 -2
- package/dist/web/public/assets/hljs-BRz6a8Dq.js.br +0 -0
- package/dist/web/public/assets/hljs-BRz6a8Dq.js.gz +0 -0
- package/dist/web/public/assets/index-D0ZYdweB.js +14 -0
- package/dist/web/public/assets/index-D0ZYdweB.js.br +0 -0
- package/dist/web/public/assets/index-D0ZYdweB.js.gz +0 -0
- package/dist/web/public/assets/index-JN0Ic8K5.css +2 -0
- package/dist/web/public/assets/index-JN0Ic8K5.css.br +0 -0
- package/dist/web/public/assets/index-JN0Ic8K5.css.gz +0 -0
- package/dist/web/public/assets/settings-DSTeuIyc.js +9 -0
- package/dist/web/public/assets/settings-DSTeuIyc.js.br +0 -0
- package/dist/web/public/assets/settings-DSTeuIyc.js.gz +0 -0
- package/dist/web/public/index.html +39 -109
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/manifest.webmanifest +0 -2
- package/dist/web/push.js +3 -3
- package/dist/web/server.js +89 -116
- package/dist/web/session-state.js +4 -40
- package/docs/deploy.md +5 -3
- package/package.json +2 -1
- package/skills/pier-boards/SKILL.md +14 -4
- package/skills/pier-help/SKILL.md +62 -23
- package/skills/pier-search/SKILL.md +27 -0
- package/skills/pier-tasks/SKILL.md +68 -18
- package/dist/channels/handoff.js +0 -94
- package/dist/web/public/assets/activity-Ds6fCHnb.js +0 -5
- package/dist/web/public/assets/activity-Ds6fCHnb.js.br +0 -0
- package/dist/web/public/assets/activity-Ds6fCHnb.js.gz +0 -0
- package/dist/web/public/assets/boards-BKCj6EwK.js +0 -1
- package/dist/web/public/assets/boards-BKCj6EwK.js.br +0 -0
- package/dist/web/public/assets/boards-BKCj6EwK.js.gz +0 -0
- package/dist/web/public/assets/explorer-DnTm975c.js +0 -4
- package/dist/web/public/assets/explorer-DnTm975c.js.br +0 -0
- package/dist/web/public/assets/explorer-DnTm975c.js.gz +0 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js.br +0 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js.gz +0 -0
- package/dist/web/public/assets/index-BQo-haPN.js +0 -85
- package/dist/web/public/assets/index-BQo-haPN.js.br +0 -0
- package/dist/web/public/assets/index-BQo-haPN.js.gz +0 -0
- package/dist/web/public/assets/index-DiHj0w1i.css +0 -2
- package/dist/web/public/assets/index-DiHj0w1i.css.br +0 -0
- package/dist/web/public/assets/index-DiHj0w1i.css.gz +0 -0
- package/dist/web/public/assets/runs-C_AthWcW.js +0 -1
- package/dist/web/public/assets/runs-C_AthWcW.js.br +0 -0
- package/dist/web/public/assets/runs-C_AthWcW.js.gz +0 -0
- package/dist/web/public/assets/settings-DDaAtFlc.js +0 -5
- package/dist/web/public/assets/settings-DDaAtFlc.js.br +0 -0
- package/dist/web/public/assets/settings-DDaAtFlc.js.gz +0 -0
- package/dist/web/public/assets/task-runs-0pvdITiV.js +0 -3
- package/dist/web/public/assets/task-runs-0pvdITiV.js.br +0 -0
- package/dist/web/public/assets/task-runs-0pvdITiV.js.gz +0 -0
- package/dist/web/public/assets/tasks-Bz29caHJ.js +0 -4
- package/dist/web/public/assets/tasks-Bz29caHJ.js.br +0 -0
- package/dist/web/public/assets/tasks-Bz29caHJ.js.gz +0 -0
package/dist/db.js
CHANGED
|
@@ -143,7 +143,7 @@ const MIGRATIONS = [
|
|
|
143
143
|
created_at INTEGER NOT NULL
|
|
144
144
|
);
|
|
145
145
|
`,
|
|
146
|
-
// 6 — manual order in the
|
|
146
|
+
// 6 — manual order in the session list.
|
|
147
147
|
`
|
|
148
148
|
-- NULL sorts on top, so a fresh database needs no backfill.
|
|
149
149
|
ALTER TABLE session_state ADD COLUMN sort INTEGER;
|
|
@@ -246,7 +246,7 @@ const MIGRATIONS = [
|
|
|
246
246
|
`
|
|
247
247
|
UPDATE session_state SET pinned = 0;
|
|
248
248
|
`,
|
|
249
|
-
// 20 — sort becomes the rank in the working set the
|
|
249
|
+
// 20 — sort becomes the rank in the working set the session list keeps on top; pinned goes.
|
|
250
250
|
`
|
|
251
251
|
-- Pinned rows seed the set at rank -1, capped at the set's size (8).
|
|
252
252
|
UPDATE session_state SET sort = -1 WHERE pinned = 1 AND sort IS NULL;
|
|
@@ -317,6 +317,45 @@ const MIGRATIONS = [
|
|
|
317
317
|
created_at INTEGER NOT NULL,
|
|
318
318
|
last_used_at INTEGER
|
|
319
319
|
);
|
|
320
|
+
`,
|
|
321
|
+
// 28 — the continuous conversation's sessions in order (core/chain.ts); the
|
|
322
|
+
// head is the newest, and the transcripts are the record.
|
|
323
|
+
`
|
|
324
|
+
CREATE TABLE main_chain (
|
|
325
|
+
session_id TEXT PRIMARY KEY,
|
|
326
|
+
started_at INTEGER NOT NULL,
|
|
327
|
+
reason TEXT NOT NULL
|
|
328
|
+
);
|
|
329
|
+
`,
|
|
330
|
+
// 29 — runs by the session they run in: a session's role, asked on every open,
|
|
331
|
+
// every `pier task` call and every callback.
|
|
332
|
+
`
|
|
333
|
+
CREATE INDEX task_runs_target ON task_runs(json_extract(json, '$.targetSessionId'), queued_at);
|
|
334
|
+
`,
|
|
335
|
+
// 30 — a session the operator closed leaves the session list until a human speaks to it (web/session-state.ts).
|
|
336
|
+
`ALTER TABLE session_state ADD COLUMN closed INTEGER NOT NULL DEFAULT 0;`,
|
|
337
|
+
// 31 — the continuous conversation's open items, written from the head's
|
|
338
|
+
// `<open>`/`<done>` markers (tasks/open-items.ts); run_ids a JSON array.
|
|
339
|
+
`
|
|
340
|
+
CREATE TABLE open_items (
|
|
341
|
+
problem TEXT PRIMARY KEY,
|
|
342
|
+
stage TEXT NOT NULL,
|
|
343
|
+
run_ids TEXT NOT NULL,
|
|
344
|
+
updated_at INTEGER NOT NULL
|
|
345
|
+
);
|
|
346
|
+
`,
|
|
347
|
+
// 32 — the working set and the closed flag go; unread is all the workbench keeps.
|
|
348
|
+
`
|
|
349
|
+
ALTER TABLE session_state DROP COLUMN sort;
|
|
350
|
+
ALTER TABLE session_state DROP COLUMN closed;
|
|
351
|
+
`,
|
|
352
|
+
// 33 — columns written and never read.
|
|
353
|
+
`
|
|
354
|
+
ALTER TABLE session_state DROP COLUMN cwd;
|
|
355
|
+
ALTER TABLE session_state DROP COLUMN project_sort;
|
|
356
|
+
ALTER TABLE conversations DROP COLUMN updated_at;
|
|
357
|
+
ALTER TABLE restart_ledger DROP COLUMN created_at;
|
|
358
|
+
ALTER TABLE push_identity DROP COLUMN created_at;
|
|
320
359
|
`,
|
|
321
360
|
];
|
|
322
361
|
/** `BEGIN IMMEDIATE`: taking the write lock up front turns a race with another
|
package/dist/drain.js
CHANGED
|
@@ -15,7 +15,7 @@ export class RestartLedger {
|
|
|
15
15
|
this.db = db;
|
|
16
16
|
}
|
|
17
17
|
record(entry) {
|
|
18
|
-
this.db.prepare("INSERT INTO restart_ledger (channel_id, conversation_id, note
|
|
18
|
+
this.db.prepare("INSERT INTO restart_ledger (channel_id, conversation_id, note) VALUES (?, ?, ?)").run(entry.channelId, entry.conversationId, entry.note);
|
|
19
19
|
}
|
|
20
20
|
list() {
|
|
21
21
|
const rows = this.db.prepare("SELECT id, channel_id, conversation_id, note FROM restart_ledger ORDER BY id").all();
|
package/dist/main.js
CHANGED
|
@@ -15,18 +15,22 @@ import { defaultBoardsDir, registerBoardRoutes, rotateBoardViews } from "./board
|
|
|
15
15
|
import { ChannelStore } from "./channels/config.js";
|
|
16
16
|
import { createControl } from "./channels/control.js";
|
|
17
17
|
import { ConversationStore, resolveConversation } from "./channels/conversations.js";
|
|
18
|
-
import { createHandoff } from "./channels/handoff.js";
|
|
19
18
|
import { registerChannelRoutes } from "./channels/routes.js";
|
|
20
19
|
import { ChannelRuntime } from "./channels/runtime.js";
|
|
20
|
+
import { MainChain } from "./core/chain.js";
|
|
21
21
|
import { EventHub } from "./core/hub.js";
|
|
22
22
|
import { splitSpeaker } from "./core/identity.js";
|
|
23
|
+
import { saidText } from "./core/reply.js";
|
|
23
24
|
import { pierDb } from "./db.js";
|
|
24
25
|
import { deliverLedger, drainForRestart, RestartLedger } from "./drain.js";
|
|
25
|
-
import { surfacePrompt } from "./
|
|
26
|
+
import { surfacePrompt } from "./agent/roles.js";
|
|
26
27
|
import { Router } from "./core/router.js";
|
|
28
|
+
import { searchSessions } from "./core/search.js";
|
|
27
29
|
import { acquireInstanceLock } from "./lock.js";
|
|
28
30
|
import { parseWebParams, runWeb } from "./websearch/run.js";
|
|
29
31
|
import { logger } from "./log.js";
|
|
32
|
+
import { runResultText, runSource } from "./tasks/callbacks.js";
|
|
33
|
+
import { openItemsStatus } from "./tasks/open-items.js";
|
|
30
34
|
import { registerTaskRoutes } from "./tasks/routes.js";
|
|
31
35
|
import { TaskService } from "./tasks/service.js";
|
|
32
36
|
import { TaskStore } from "./tasks/store.js";
|
|
@@ -92,14 +96,16 @@ const configSync = new ConfigSync({
|
|
|
92
96
|
reload: () => readyForConfigReload ? reloadInstance() : Promise.resolve(),
|
|
93
97
|
});
|
|
94
98
|
const skillsDir = fileURLToPath(new URL("../skills", import.meta.url));
|
|
99
|
+
const taskStore = new TaskStore(db);
|
|
95
100
|
const factory = new PiAgentFactory(
|
|
96
101
|
// Getters, read per session open: a Console change reaches the next session
|
|
97
102
|
// without a restart.
|
|
98
103
|
() => surfacePrompt({ boardsDir: defaultBoardsDir(), publicUrl: settings.get().publicUrl }),
|
|
99
104
|
// Documents Pier's own tools, so it loads only inside a Pier session.
|
|
100
105
|
[skillsDir], new CredentialStore(db, secrets), piConfig, () => settings.get().modelMenu, () => settings.get(), () => settings.get().titleModel,
|
|
101
|
-
// Transcripts carry the speaker header core wrote for the model
|
|
102
|
-
|
|
106
|
+
// Transcripts carry the speaker header core wrote for the model, and replies
|
|
107
|
+
// the chat markup core strips before anyone sees them.
|
|
108
|
+
new IndexedListing(undefined, undefined, (text) => splitSpeaker(text).text, saidText), (id) => taskStore.roleOf(id));
|
|
103
109
|
const hub = new EventHub();
|
|
104
110
|
const router = new Router(hub, (key) => {
|
|
105
111
|
// Web conversation ids are session ids; an IM id is a chat, resolved through
|
|
@@ -108,11 +114,39 @@ const router = new Router(hub, (key) => {
|
|
|
108
114
|
return factory.resume(key.conversationId);
|
|
109
115
|
}
|
|
110
116
|
return resolveIm(key);
|
|
111
|
-
}, (key) => conversations.get(key), (id) =>
|
|
117
|
+
}, (key) => conversations.get(key), (id) => chatKeyOf(id));
|
|
118
|
+
const chain = new MainChain(db, {
|
|
119
|
+
factory, router, home: pierPath("home"),
|
|
120
|
+
ledger: (ids, since) => tasks.ledger(ids, since),
|
|
121
|
+
status: (now) => openItemsStatus(tasks.openItems(now), now),
|
|
122
|
+
defaults: () => piConfig.readDefaults(),
|
|
123
|
+
});
|
|
112
124
|
const stopEviction = router.startIdleEviction();
|
|
113
|
-
tasks = new TaskService(
|
|
125
|
+
tasks = new TaskService(taskStore, factory, router, hub, {
|
|
114
126
|
modelMenu: () => settings.get().modelMenu,
|
|
115
|
-
systemActions: {
|
|
127
|
+
systemActions: {
|
|
128
|
+
// A schema gap pauses the subscription mid-run; the task's own switch follows it.
|
|
129
|
+
"config-sync": async (signal) => {
|
|
130
|
+
try {
|
|
131
|
+
return await configSync.sync(signal);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await configurationSync.reconcile();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
continuous: chain,
|
|
139
|
+
// `channels` is assigned below, before any run can settle.
|
|
140
|
+
designLead: (run, state) => {
|
|
141
|
+
if (!run.targetSessionId)
|
|
142
|
+
return;
|
|
143
|
+
void channels.designLead({
|
|
144
|
+
sessionId: run.targetSessionId,
|
|
145
|
+
name: run.context.definition.name,
|
|
146
|
+
origin: { kind: "task-callback", taskId: run.taskId, runId: run.id, sourceSessionId: run.targetSessionId, source: runSource(run), state: run.state },
|
|
147
|
+
text: state === "failed" ? run.error ?? run.state : runResultText(run),
|
|
148
|
+
}, state).catch((err) => log.error(`lead ${run.targetSessionId ?? "?"}: its ${state} state did not reach the home chat`, err));
|
|
149
|
+
},
|
|
116
150
|
});
|
|
117
151
|
const configurationSync = configSyncTask(tasks, configSync);
|
|
118
152
|
const managedTools = new ManagedTools();
|
|
@@ -130,24 +164,17 @@ const control = createControl({
|
|
|
130
164
|
router, factory, conversations, store: channelStore,
|
|
131
165
|
modelMenu: () => settings.get().modelMenu,
|
|
132
166
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
hub,
|
|
145
|
-
publicUrl: () => settings.get().publicUrl,
|
|
146
|
-
taskSessions: () => tasks.taskSessions(),
|
|
147
|
-
workingSet: () => sessionState.flags(),
|
|
148
|
-
log: (m) => logger("channels").info(m),
|
|
149
|
-
});
|
|
150
|
-
const channels = new ChannelRuntime(channelStore, router, control, handoff);
|
|
167
|
+
const channels = new ChannelRuntime(channelStore, router, chain, control, conversations);
|
|
168
|
+
/** The head answers in the home chat while its adapter runs (docs/design/11), else on
|
|
169
|
+
* the web with Web Push; an older member gets no home key, or an adapter's start
|
|
170
|
+
* would re-key it over the head. Any other session: its conversations row. */
|
|
171
|
+
function chatKeyOf(sessionId) {
|
|
172
|
+
const home = channelStore.home();
|
|
173
|
+
if (home && channels.live(home.platform) && chain.chainOf(sessionId)?.[0] === sessionId) {
|
|
174
|
+
return { channelId: home.platform, conversationId: home.chatId };
|
|
175
|
+
}
|
|
176
|
+
return conversations.keyOf(sessionId);
|
|
177
|
+
}
|
|
151
178
|
resolveIm = resolveConversation(conversations, factory, control.launchFor, (key, message) => {
|
|
152
179
|
logger("channels").warn(`${key.channelId}:${key.conversationId} ${message}`);
|
|
153
180
|
void channels.notify(key.channelId, key.conversationId, message)
|
|
@@ -169,16 +196,8 @@ const reloadInstance = async (includeWatched = false) => {
|
|
|
169
196
|
await channels.reload();
|
|
170
197
|
return router.evictIdle(0, Date.now(), { includeWatched });
|
|
171
198
|
};
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
// sync() already logged the cause; this line says what the boot did about it.
|
|
175
|
-
try {
|
|
176
|
-
await configSync.sync();
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
log.error("Startup configuration sync failed; using the last local configuration");
|
|
180
|
-
}
|
|
181
|
-
}
|
|
199
|
+
// No sync at boot: a restart is not a reason to replace the configuration;
|
|
200
|
+
// the owned task's next check is.
|
|
182
201
|
await configurationSync.reconcile();
|
|
183
202
|
tasks.start();
|
|
184
203
|
readyForConfigReload = true;
|
|
@@ -278,8 +297,8 @@ registerConfigSyncRoutes(app, {
|
|
|
278
297
|
reconcile: configurationSync.reconcile,
|
|
279
298
|
run: configurationSync.run,
|
|
280
299
|
});
|
|
281
|
-
registerTaskRoutes(app, tasks
|
|
282
|
-
registerChannelRoutes(app, channelStore, channels,
|
|
300
|
+
registerTaskRoutes(app, tasks);
|
|
301
|
+
registerChannelRoutes(app, channelStore, channels, conversations);
|
|
283
302
|
registerVaultRoutes(app, { vault, doctor: () => secrets.doctor() });
|
|
284
303
|
registerBoardRoutes(app);
|
|
285
304
|
registerPushRoutes(app, {
|
|
@@ -299,6 +318,7 @@ app.route("/", createServer({
|
|
|
299
318
|
packages,
|
|
300
319
|
providers: factory,
|
|
301
320
|
settings,
|
|
321
|
+
subscribed: () => configSync.status().enabled,
|
|
302
322
|
// Assembled here: the catalog spawns ubix, which web/ may not.
|
|
303
323
|
catalog: async () => {
|
|
304
324
|
const { tools, customTools } = settings.get();
|
|
@@ -320,8 +340,12 @@ app.route("/", createServer({
|
|
|
320
340
|
reload: () => reloadInstance(true),
|
|
321
341
|
backgroundRuns: (id) => tasks.backgroundRuns(id),
|
|
322
342
|
activeBackgroundRunCounts: () => tasks.activeBackgroundRunCounts(),
|
|
343
|
+
parkedMessages: (id) => tasks.parkedMessages(id),
|
|
323
344
|
taskSessions: () => tasks.taskSessions(),
|
|
324
|
-
|
|
345
|
+
leads: () => taskStore.leads(),
|
|
346
|
+
openItems: () => tasks.openItems(),
|
|
347
|
+
channelOf: (id) => chatKeyOf(id)?.channelId,
|
|
348
|
+
continuous: chain,
|
|
325
349
|
}));
|
|
326
350
|
const port = Number(process.env.PORT ?? 3141);
|
|
327
351
|
const hostname = process.env.HOST ?? "127.0.0.1";
|
|
@@ -339,6 +363,7 @@ servePier({
|
|
|
339
363
|
note(`done ${JSON.stringify(result.details)}`);
|
|
340
364
|
return result;
|
|
341
365
|
},
|
|
366
|
+
search: searchSessions(factory),
|
|
342
367
|
// Live in the router, or on disk: the same two places a callback target is looked for.
|
|
343
368
|
knows: async (id) => router.stateOf(id) !== undefined || (await factory.find(id)) !== undefined,
|
|
344
369
|
login: () => `${settings.get().publicUrl || `http://127.0.0.1:${String(port)}`}/login/${auth.mintLink()}`,
|
|
@@ -374,8 +399,8 @@ const shutdown = (stopTasks = true) => {
|
|
|
374
399
|
// 90-second wait for SIGKILL.
|
|
375
400
|
setTimeout(() => process.exit(0), 3000).unref();
|
|
376
401
|
stopEviction();
|
|
377
|
-
//
|
|
378
|
-
//
|
|
402
|
+
// Aborted runs end interrupted, as boot recovery records them; the drain path
|
|
403
|
+
// leaves the ones it outlasted to that recovery.
|
|
379
404
|
if (stopTasks)
|
|
380
405
|
tasks.stop();
|
|
381
406
|
void channels.stop().finally(() => {
|
package/dist/settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Instance settings: the facts about *this* Pier that are neither a credential
|
|
2
2
|
// nor per-session. A key-value table, so the next setting is not the next table.
|
|
3
|
-
import { isThinkingLevel } from "./core/types.js";
|
|
3
|
+
import { isModelTier, isThinkingLevel, MODEL_TIERS, THINKING_LEVELS } from "./core/types.js";
|
|
4
4
|
import { pierDb, transact } from "./db.js";
|
|
5
5
|
import { logger } from "./log.js";
|
|
6
6
|
import { normalizeCustomTools } from "./tools.js";
|
|
@@ -48,29 +48,33 @@ export function normalizePublicUrl(raw) {
|
|
|
48
48
|
return `${url.origin}${url.pathname}`.replace(/\/+$/, "");
|
|
49
49
|
}
|
|
50
50
|
/** Rejecting rather than repairing: a "fixed" entry would advertise a model the
|
|
51
|
-
* operator never picked.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
* operator never picked. The refusal names the row (1-based, as the Console
|
|
52
|
+
* lists them) and the field. A `note` left by an older Pier is dropped. One
|
|
53
|
+
* model may be pinned at several levels; the same model at the same level twice
|
|
54
|
+
* is refused, naming both rows. */
|
|
55
|
+
export function parseModelMenu(raw) {
|
|
56
|
+
if (!Array.isArray(raw))
|
|
57
|
+
return "modelMenu must be a list";
|
|
58
|
+
if (raw.length > 32)
|
|
59
|
+
return `modelMenu has ${String(raw.length)} rows; at most 32`;
|
|
55
60
|
const menu = [];
|
|
56
|
-
for (const item of raw) {
|
|
61
|
+
for (const [i, item] of raw.entries()) {
|
|
62
|
+
const row = `modelMenu row ${String(i + 1)}`;
|
|
57
63
|
const ref = normalizeModelRef(item);
|
|
58
64
|
if (!ref)
|
|
59
|
-
return
|
|
60
|
-
const { thinking,
|
|
65
|
+
return `${row}: provider and id must be non-empty strings`;
|
|
66
|
+
const { thinking, tier } = item;
|
|
61
67
|
// Repaired, not rejected: rows stored before the level was required have
|
|
62
68
|
// none, and dropping the menu over it would lose the pins.
|
|
63
69
|
const level = thinking === undefined ? "medium" : thinking;
|
|
64
70
|
if (!isThinkingLevel(level))
|
|
65
|
-
return
|
|
66
|
-
if (
|
|
67
|
-
return
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
...(cleaned ? { note: cleaned } : {}),
|
|
73
|
-
});
|
|
71
|
+
return `${row} (${ref.provider}/${ref.id}): thinking must be one of ${THINKING_LEVELS.join(", ")}`;
|
|
72
|
+
if (tier !== undefined && !isModelTier(tier))
|
|
73
|
+
return `${row} (${ref.provider}/${ref.id}): tier must be one of ${MODEL_TIERS.join(", ")}, or none`;
|
|
74
|
+
const twin = menu.findIndex((e) => e.provider === ref.provider && e.id === ref.id && e.thinking === level);
|
|
75
|
+
if (twin >= 0)
|
|
76
|
+
return `${row} (${ref.provider}/${ref.id}): already pinned at ${level} by row ${String(twin + 1)}`;
|
|
77
|
+
menu.push({ ...ref, thinking: level, ...(tier !== undefined ? { tier } : {}) });
|
|
74
78
|
}
|
|
75
79
|
return menu;
|
|
76
80
|
}
|
|
@@ -88,7 +92,7 @@ export function normalizeModelRef(raw) {
|
|
|
88
92
|
}
|
|
89
93
|
/** Shape only: tools.ts owns the catalog, and an unknown name is ignored
|
|
90
94
|
* there, so a downgrade cannot lose a setting it cannot explain. */
|
|
91
|
-
|
|
95
|
+
function normalizeTools(raw) {
|
|
92
96
|
return normalizeNames(raw);
|
|
93
97
|
}
|
|
94
98
|
function normalizeNames(raw) {
|
|
@@ -117,7 +121,10 @@ export class SettingsStore {
|
|
|
117
121
|
log.warn("settings.accent is not a preset — ignoring it");
|
|
118
122
|
return {
|
|
119
123
|
publicUrl: this.#value("publicUrl") ?? "",
|
|
120
|
-
modelMenu: this.#json("modelMenu",
|
|
124
|
+
modelMenu: this.#json("modelMenu", (raw) => {
|
|
125
|
+
const menu = parseModelMenu(raw);
|
|
126
|
+
return typeof menu === "string" ? null : menu;
|
|
127
|
+
}, "a valid menu") ?? [],
|
|
121
128
|
...(titleModel ? { titleModel } : {}),
|
|
122
129
|
autoUpdate: this.#value("autoUpdate") === "1",
|
|
123
130
|
skillsOff: this.#json("skillsOff", normalizeNames, "a list of names") ?? [],
|
package/dist/socket.js
CHANGED
package/dist/tasks/agent.js
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
import { quietLabel, splitReply } from "../core/reply.js";
|
|
5
5
|
import { logger } from "../log.js";
|
|
6
6
|
import { runSource } from "./callbacks.js";
|
|
7
|
+
import { createdRole } from "./types.js";
|
|
7
8
|
// Agent runs are I/O-bound: the cap is there for API pressure and runaway
|
|
8
9
|
// fan-out, not for this machine's CPU.
|
|
9
10
|
const MAX_ACTIVE_AGENTS = 6;
|
|
10
11
|
const log = logger("tasks");
|
|
11
12
|
/** Every session gets the chat-surface contract, task runs included, so the
|
|
12
13
|
* delegation prompt says which of it does not apply. Skipped on resume. */
|
|
13
|
-
const preamble = (run, supervised) => {
|
|
14
|
+
const preamble = (run, supervised, role) => {
|
|
14
15
|
// A cron/watch task with a session callback is read by an agent too.
|
|
15
16
|
const audience = run.invokedBySessionId
|
|
16
17
|
? "read by the agent that delegated this run"
|
|
@@ -19,8 +20,10 @@ const preamble = (run, supervised) => {
|
|
|
19
20
|
: "read by the operator";
|
|
20
21
|
return `[Pier task run ${run.id} — "${run.context.definition.name}"] ` +
|
|
21
22
|
`Your final reply is recorded verbatim as the run result, ${audience}; ` +
|
|
22
|
-
`next-step buttons and file:// attachments do not render there. A question only that reader can answer is your result: state it and end your turn; the answer resumes this session.` +
|
|
23
|
-
(
|
|
23
|
+
`next-step buttons and file:// attachments do not render there. Return the conclusion and the paths it rests on — no process, no log of attempts; a deliverable longer than a screen goes to a file the result names. A question only that reader can answer is your result: state it and end your turn; the answer resumes this session.` +
|
|
24
|
+
(role === "lead"
|
|
25
|
+
? " You are a feature lead: you may delegate to workers with `pier task run` (never `--role lead`), and their results come back to you."
|
|
26
|
+
: supervised || role === "worker" ? " You cannot delegate from here — `pier task` is refused; if the work needs another agent, say so in your result and your supervisor will run it." : "") +
|
|
24
27
|
"\n\n";
|
|
25
28
|
};
|
|
26
29
|
export class AgentTaskRunner {
|
|
@@ -61,7 +64,8 @@ export class AgentTaskRunner {
|
|
|
61
64
|
const input = run.input === undefined || run.input === null
|
|
62
65
|
? ""
|
|
63
66
|
: `\n\n<task_input>\n${JSON.stringify(run.input).replaceAll("</task_input>", "<\\/task_input>")}\n</task_input>`;
|
|
64
|
-
const prompt = run.context.resumePrompt ??
|
|
67
|
+
const prompt = run.context.resumePrompt ??
|
|
68
|
+
`${preamble(run, this.store.supervised(run), this.store.roleOf(session.id))}${action.prompt}${input}`;
|
|
65
69
|
run.context.sessionId = session.id;
|
|
66
70
|
run.context.model = session.model;
|
|
67
71
|
// The level the session settled on: an unspecified effort inherits the caller's.
|
|
@@ -144,12 +148,14 @@ export class AgentTaskRunner {
|
|
|
144
148
|
throw new Error("could not resolve child working directory");
|
|
145
149
|
const opts = {
|
|
146
150
|
cwd,
|
|
147
|
-
|
|
151
|
+
// The session list's title: the caller's `--name`, else the prompt's first line.
|
|
152
|
+
name: run.context.definition.name,
|
|
148
153
|
// Unspecified model inherits the caller's live model, not the global
|
|
149
154
|
// default; falls back to the default when the caller isn't attached.
|
|
150
155
|
model: action.launch?.model ??
|
|
151
156
|
(run.sourceSessionId ? this.router.modelOf(run.sourceSessionId) : undefined),
|
|
152
157
|
thinking: action.launch?.thinking,
|
|
158
|
+
role: createdRole(run),
|
|
153
159
|
};
|
|
154
160
|
const opening = this.factory.create(opts).then(async (session) => {
|
|
155
161
|
// SDK creation cannot be cancelled; a late session still belongs to this
|
package/dist/tasks/callbacks.js
CHANGED
|
@@ -10,6 +10,45 @@ export const runSource = (run) => ({
|
|
|
10
10
|
...(run.context.model ? { model: run.context.model } : {}),
|
|
11
11
|
...(run.context.thinking ? { thinking: run.context.thinking } : {}),
|
|
12
12
|
});
|
|
13
|
+
/** The directory a run worked in: the one its session opened on, else the one its action names. */
|
|
14
|
+
export const runCwd = (run) => {
|
|
15
|
+
const action = run.context.definition.action;
|
|
16
|
+
return run.context.cwd ??
|
|
17
|
+
(action.type === "bash" ? action.cwd : action.type === "agent" && action.session.mode === "fresh" ? action.session.cwd : null);
|
|
18
|
+
};
|
|
19
|
+
/** What a callback's card says about the one run it carries. */
|
|
20
|
+
const oneRun = (run) => {
|
|
21
|
+
const cwd = runCwd(run);
|
|
22
|
+
return { source: runSource(run), state: run.state, ...(cwd ? { cwd } : {}) };
|
|
23
|
+
};
|
|
24
|
+
/** Heads a milestone resume's prompt: the lead's reply is what its supervisor reads. */
|
|
25
|
+
export const MILESTONE = "[Pier: the last result you were waiting on follows; nothing owed to you is still running. Your reply is the milestone your supervisor reads: what is done, what is next, any decision you need.]";
|
|
26
|
+
/** The line a design lead ends on once the user confirms: its milestone, whether a run's or a turn outside any. */
|
|
27
|
+
export const DESIGN_FINAL = /^Design final:/m;
|
|
28
|
+
/** On the record of a lead run that owed its supervisor nothing, so the run says why. */
|
|
29
|
+
export const LEAD_TURN = "a lead's turn, not a milestone";
|
|
30
|
+
/** Decided once, as the run finishes. A lead reports milestones only: any
|
|
31
|
+
* other turn of its the user reads in its session, and settles as `--callback none`.
|
|
32
|
+
* A build lead's turn that leaves nothing coming to it is its milestone: it
|
|
33
|
+
* declares its own completion, where only the user finalizes a design.
|
|
34
|
+
* A turn that did not succeed is not a turn the user read: it calls back. */
|
|
35
|
+
export function settleCallback(run, store) {
|
|
36
|
+
if (!run.callbackSessionId)
|
|
37
|
+
return;
|
|
38
|
+
// A watch probe that did not match is the interval passing, not a result.
|
|
39
|
+
if (run.matched === false && run.state === "succeeded")
|
|
40
|
+
return;
|
|
41
|
+
// The session's creator fixes its role: a `--session` or `--run` turn on a lead's session is a lead's.
|
|
42
|
+
const lead = run.targetSessionId;
|
|
43
|
+
const phase = lead === null ? undefined : store.leadPhaseOf(lead);
|
|
44
|
+
const milestone = () => run.context.resumePrompt?.startsWith(MILESTONE) === true ||
|
|
45
|
+
(run.result?.type === "agent" && DESIGN_FINAL.test(run.result.text)) ||
|
|
46
|
+
(phase === "build" && lead !== null && !store.awaitsResults(lead));
|
|
47
|
+
if (phase !== undefined && run.state === "succeeded" && !milestone())
|
|
48
|
+
run.callbackError = LEAD_TURN;
|
|
49
|
+
else
|
|
50
|
+
run.callbackState = "pending";
|
|
51
|
+
}
|
|
13
52
|
export function runResultText(run) {
|
|
14
53
|
let result = run.error ?? "No result";
|
|
15
54
|
if (run.result?.type === "agent" || run.result?.type === "system")
|
|
@@ -26,9 +65,17 @@ export function runResultText(run) {
|
|
|
26
65
|
}
|
|
27
66
|
export class TaskCallbacks {
|
|
28
67
|
store;
|
|
68
|
+
headOf;
|
|
69
|
+
conversation;
|
|
29
70
|
outbox;
|
|
30
|
-
constructor(store, router, changed, unreachable
|
|
71
|
+
constructor(store, router, changed, unreachable,
|
|
72
|
+
/** Where a result owed to a session goes now: the continuous conversation's head, for a member. */
|
|
73
|
+
headOf = (id) => id, milestone,
|
|
74
|
+
/** The continuous conversation's head, null before its first message. */
|
|
75
|
+
conversation = () => null) {
|
|
31
76
|
this.store = store;
|
|
77
|
+
this.headOf = headOf;
|
|
78
|
+
this.conversation = conversation;
|
|
32
79
|
this.outbox = new Outbox(router, {
|
|
33
80
|
id: (run) => run.id,
|
|
34
81
|
reload: (id) => this.store.getRun(id),
|
|
@@ -43,11 +90,12 @@ export class TaskCallbacks {
|
|
|
43
90
|
sourceSessionId: runs[0].targetSessionId,
|
|
44
91
|
runIds: runs.map((run) => run.id),
|
|
45
92
|
// Only for one run: a batch's caption would attribute every result
|
|
46
|
-
// to the first run's name and
|
|
47
|
-
...(runs.length === 1 ?
|
|
93
|
+
// to the first run's name, model and directory.
|
|
94
|
+
...(runs.length === 1 ? oneRun(runs[0]) : {}),
|
|
48
95
|
},
|
|
49
96
|
}),
|
|
50
97
|
abandoned: (run, sessionId, why) => unreachable(sessionId, `the result of "${run.context.definition.name}"`, why),
|
|
98
|
+
milestone,
|
|
51
99
|
});
|
|
52
100
|
}
|
|
53
101
|
target(callback, origin) {
|
|
@@ -55,6 +103,8 @@ export class TaskCallbacks {
|
|
|
55
103
|
return callback.sessionId;
|
|
56
104
|
if (callback.type === "origin")
|
|
57
105
|
return origin;
|
|
106
|
+
if (callback.type === "conversation")
|
|
107
|
+
return this.conversation();
|
|
58
108
|
return null;
|
|
59
109
|
}
|
|
60
110
|
recover(now = Date.now()) {
|
|
@@ -67,10 +117,10 @@ export class TaskCallbacks {
|
|
|
67
117
|
const first = this.store.getRun(candidate.id);
|
|
68
118
|
if (!first?.callbackSessionId || (first.callbackState !== "pending" && first.callbackState !== "failed"))
|
|
69
119
|
return;
|
|
70
|
-
const sessionId = first.callbackSessionId;
|
|
120
|
+
const sessionId = this.headOf(first.callbackSessionId);
|
|
71
121
|
// Once one callback is deliverable, everything pending for the session rides along.
|
|
72
122
|
const batch = this.store.listPendingCallbacks(Number.MAX_SAFE_INTEGER)
|
|
73
|
-
.filter((run) => run.callbackSessionId === sessionId);
|
|
123
|
+
.filter((run) => run.callbackSessionId !== null && this.headOf(run.callbackSessionId) === sessionId);
|
|
74
124
|
if (!batch.some((run) => run.id === first.id))
|
|
75
125
|
return;
|
|
76
126
|
await this.outbox.deliver(sessionId, batch);
|
package/dist/tasks/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const OPTIONS = {
|
|
|
10
10
|
timeout: { type: "string" }, callback: { type: "string" }, "callback-session": { type: "string" }, join: { type: "string" },
|
|
11
11
|
bash: { type: "string" }, cron: { type: "string" }, tz: { type: "string" },
|
|
12
12
|
watch: { type: "string" }, every: { type: "string" }, repeat: { type: "boolean" },
|
|
13
|
-
group: { type: "string" }, reason: { type: "string" },
|
|
13
|
+
group: { type: "string" }, reason: { type: "string" }, role: { type: "string" }, design: { type: "boolean" },
|
|
14
14
|
help: { type: "boolean", short: "h" },
|
|
15
15
|
};
|
|
16
16
|
/** The usage line is the contract (docs/design/09-tasks-cli.md); the flags a
|
|
@@ -18,17 +18,21 @@ const OPTIONS = {
|
|
|
18
18
|
const COMMANDS = {
|
|
19
19
|
run: {
|
|
20
20
|
usage: "run [--prompt <text|-> | --bash <script>] [--run <id> [--after]] [--task-id <id>] [--session <id>]\n" +
|
|
21
|
-
" [--model <name|?>] [--thinking <level>] [--cwd <dir>] [--name <text>] [--timeout <seconds>]\n" +
|
|
21
|
+
" [--model <name|?>] [--thinking <level>] [--role lead [--design]] [--cwd <dir>] [--name <text>] [--timeout <seconds>]\n" +
|
|
22
22
|
" [--callback origin|none|steer] [--callback-session <id>] [--join all|first] [--member <flags…>]…",
|
|
23
23
|
help: "a new run (--prompt | --bash | --task-id | --session … --prompt), a batch (--member), or a prompt on an existing one (--run)",
|
|
24
24
|
},
|
|
25
25
|
save: {
|
|
26
26
|
usage: "save [--task-id <id>] --name <text> (--prompt <text|-> | --bash <script>)\n" +
|
|
27
27
|
" [--cron \"<expr>\" --tz <zone> | --watch <script> --every <seconds> [--repeat]]\n" +
|
|
28
|
-
" [--cwd <dir>] [--timeout <seconds>] [--model <name>] [--thinking <level>] [--callback-session <id>]",
|
|
29
|
-
help: "file a definition the operator sees, or update one by --task-id; no trigger means manual",
|
|
28
|
+
" [--cwd <dir>] [--timeout <seconds>] [--model <name>] [--thinking <level>] [--callback-session <id|none>]",
|
|
29
|
+
help: "file a definition the operator sees, or update one by --task-id; no trigger means manual; results reach the continuous conversation unless --callback-session",
|
|
30
30
|
},
|
|
31
|
-
list: { usage: "list", help: "stored definitions, as JSON" },
|
|
31
|
+
list: { usage: "list", help: "stored definitions with nextRun and lastRun, as JSON" },
|
|
32
|
+
pause: { usage: "pause --task-id <id>", help: "stop a definition's schedule; run --task-id still runs it" },
|
|
33
|
+
resume: { usage: "resume --task-id <id>", help: "restart a paused definition's schedule" },
|
|
34
|
+
archive: { usage: "archive --task-id <id>", help: "retire a definition for good" },
|
|
35
|
+
runs: { usage: "runs", help: "the runs you launched (in the continuous conversation, any of its sessions), in flight and finished in the last 24h, as JSON" },
|
|
32
36
|
cancel: { usage: "cancel (--run <id> | --group <id>)", help: "a run or a group, descendants included" },
|
|
33
37
|
recover: { usage: "recover (--run <id> | --group <id>) --reason <text>", help: "a finished result after its callback settled; never a progress check" },
|
|
34
38
|
};
|
|
@@ -58,7 +62,9 @@ const seconds = (flag, raw) => {
|
|
|
58
62
|
return Number.isInteger(n) ? n : refuse(`--${flag} must be a whole number of seconds`);
|
|
59
63
|
};
|
|
60
64
|
/** `launch` as the server takes it: `model` is a menu name it resolves. */
|
|
61
|
-
const launchOf = (v) => v.model === undefined && v.thinking === undefined
|
|
65
|
+
const launchOf = (v) => v.model === undefined && v.thinking === undefined && v.role === undefined && v.design === undefined
|
|
66
|
+
? undefined
|
|
67
|
+
: compact({ model: v.model, thinking: v.thinking, role: v.role, design: v.design });
|
|
62
68
|
/** Argv is split at each bare `--member`; a value equal to it is unreachable,
|
|
63
69
|
* since parseArgs (strict) refuses option-like values anyway. */
|
|
64
70
|
function segments(argv) {
|
|
@@ -125,8 +131,8 @@ function build(name, parsed, io) {
|
|
|
125
131
|
if (parsed.some((v) => v.model === "?"))
|
|
126
132
|
return { operation: "run", launch: { model: "?" } };
|
|
127
133
|
const [values, ...members] = parsed;
|
|
128
|
-
if (name === "list")
|
|
129
|
-
return { operation:
|
|
134
|
+
if (name === "list" || name === "runs")
|
|
135
|
+
return { operation: name };
|
|
130
136
|
if (name === "cancel" || name === "recover") {
|
|
131
137
|
if ((values.run === undefined) === (values.group === undefined))
|
|
132
138
|
refuse(`${name} takes exactly one of --run or --group`);
|
|
@@ -136,13 +142,16 @@ function build(name, parsed, io) {
|
|
|
136
142
|
}
|
|
137
143
|
if (name === "save")
|
|
138
144
|
return saveParams(values, text);
|
|
145
|
+
if (name === "pause" || name === "resume" || name === "archive") {
|
|
146
|
+
return { operation: name, task_id: values["task-id"] ?? refuse(`${name} needs --task-id`) };
|
|
147
|
+
}
|
|
139
148
|
/** One new run, in the three shapes `tasks[]` accepts. A saved
|
|
140
149
|
* definition runs as is: batch defaults pass it by, its own flags are refused. */
|
|
141
150
|
const entry = (v, own = v) => {
|
|
142
151
|
const launch = launchOf(v);
|
|
143
152
|
const timeoutSeconds = seconds("timeout", v.timeout);
|
|
144
153
|
if (v["task-id"] !== undefined) {
|
|
145
|
-
const extra = flagsOf(own).find((flag) => ["prompt", "bash", "session", "cwd", "model", "thinking", "name", "timeout"].includes(flag));
|
|
154
|
+
const extra = flagsOf(own).find((flag) => ["prompt", "bash", "session", "cwd", "model", "thinking", "role", "design", "name", "timeout"].includes(flag));
|
|
146
155
|
if (extra)
|
|
147
156
|
refuse(`--${extra} does not apply to a saved definition (--task-id)`);
|
|
148
157
|
return { task_id: v["task-id"] };
|
|
@@ -151,7 +160,7 @@ function build(name, parsed, io) {
|
|
|
151
160
|
if (v.prompt !== undefined)
|
|
152
161
|
refuse("a run takes exactly one of --prompt or --bash");
|
|
153
162
|
if (launch)
|
|
154
|
-
refuse("--model/--thinking apply to a prompt, not --bash");
|
|
163
|
+
refuse("--model/--thinking/--role/--design apply to a prompt, not --bash");
|
|
155
164
|
if (v.session !== undefined)
|
|
156
165
|
refuse("--session continues an agent session; --bash is a command, not a turn");
|
|
157
166
|
return compact({ name: v.name, timeoutSeconds, action: compact({ type: "bash", script: v.bash, cwd: v.cwd }) });
|
|
@@ -216,7 +225,9 @@ function saveParams(v, text) {
|
|
|
216
225
|
name: v.name,
|
|
217
226
|
timeoutSeconds: seconds("timeout", v.timeout),
|
|
218
227
|
trigger,
|
|
219
|
-
callback: v["callback-session"] === undefined
|
|
228
|
+
callback: v["callback-session"] === undefined
|
|
229
|
+
? { type: "conversation" }
|
|
230
|
+
: v["callback-session"] === "none" ? { type: "none" } : { type: "session", sessionId: v["callback-session"] },
|
|
220
231
|
...(v.bash === undefined
|
|
221
232
|
? { prompt: text(v.prompt), cwd: v.cwd, launch }
|
|
222
233
|
: { action: compact({ type: "bash", script: v.bash, cwd: v.cwd }) }),
|