@assuredloop/orca-bot-kit 0.36.0 → 0.36.1
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/package.json +1 -1
- package/skills/obk-bot-building/SKILL.md +16 -0
- package/skills/obk-finops/SKILL.md +3 -0
- package/src/cli.js +4 -0
- package/src/message.js +31 -9
- package/src/orca.js +48 -11
package/package.json
CHANGED
|
@@ -127,6 +127,22 @@ the bots folder moves. `obk session add` and `obk session change` say so when a
|
|
|
127
127
|
work dir leads out of the bot home; take that back to the user rather than
|
|
128
128
|
past them.
|
|
129
129
|
|
|
130
|
+
## How its sessions choose a subagent's model
|
|
131
|
+
|
|
132
|
+
A session runs on one model of its own, but the model of each subagent it starts
|
|
133
|
+
can be chosen task by task. A bot that should choose carries its way of working
|
|
134
|
+
for this as a rule unit of its own. The bot can decide that way of working, or
|
|
135
|
+
the user can. Write it in their words as a unit in `rules/` of the bots folder,
|
|
136
|
+
name it in that bot's `rules:` list, and run `obk rules build`: every session
|
|
137
|
+
of the bot then reads it in its `AGENTS.md`. Another bot can carry another
|
|
138
|
+
rule, or none. A bot with none works as before, and each subagent runs on what
|
|
139
|
+
the harness gives it.
|
|
140
|
+
|
|
141
|
+
The rule acts where the harness lets a session choose a model for a subagent,
|
|
142
|
+
as Claude Code's Agent tool does with its `model`. Where the harness does not,
|
|
143
|
+
nothing changes. The kit names no model and no table from a task to a model:
|
|
144
|
+
any model in the rule is the bot's or the user's choice.
|
|
145
|
+
|
|
130
146
|
## Offer a line-up rather than a blank page
|
|
131
147
|
|
|
132
148
|
Someone making their first bots does not know what a good set looks like, so
|
|
@@ -119,6 +119,9 @@ A usual shape: high effort for the session that orchestrates, medium for the
|
|
|
119
119
|
ones doing the work, and the top of the range only for deep read-only
|
|
120
120
|
analysis.
|
|
121
121
|
|
|
122
|
+
A bot can also carry its own rule on the model each subagent gets: see "How
|
|
123
|
+
its sessions choose a subagent's model" in the obk-bot-building skill.
|
|
124
|
+
|
|
122
125
|
## When a model is not up to the work
|
|
123
126
|
|
|
124
127
|
The signs are in the work, not in the answers. The same instruction given
|
package/src/cli.js
CHANGED
|
@@ -1177,6 +1177,10 @@ const commands = {
|
|
|
1177
1177
|
answer,
|
|
1178
1178
|
lines: [
|
|
1179
1179
|
`${'sent'.padEnd(9)} ${where.padEnd(24)} ${answer.subject}`,
|
|
1180
|
+
// Orca took it, and its answer named no letter (#730).
|
|
1181
|
+
...(answer.id === undefined
|
|
1182
|
+
? [' Orca queued it, and its answer gave no message id for it.']
|
|
1183
|
+
: []),
|
|
1180
1184
|
...(answer.file === undefined
|
|
1181
1185
|
? []
|
|
1182
1186
|
: [` it was too long to travel as itself, so it went as a file: ${answer.file}`]),
|
package/src/message.js
CHANGED
|
@@ -203,19 +203,21 @@ export async function sendMessage(bots, { to: target, from: sender, tab, subject
|
|
|
203
203
|
const shown = await loaded(to);
|
|
204
204
|
// The Escape goes first, so the turn has ended when the letter is in.
|
|
205
205
|
const interrupted = interrupt ? interruptBusy(to) : {};
|
|
206
|
-
const
|
|
206
|
+
const posted = postMessage({
|
|
207
207
|
to: road.address,
|
|
208
208
|
from: `run:${from.mailbox}`,
|
|
209
209
|
subject,
|
|
210
210
|
body: written.body,
|
|
211
211
|
thread,
|
|
212
212
|
});
|
|
213
|
+
// Orca took it: sent, whatever else its answer says or leaves out (#730).
|
|
214
|
+
const message = posted?.message;
|
|
213
215
|
|
|
214
216
|
return {
|
|
215
217
|
...answer,
|
|
216
218
|
sent: true,
|
|
217
|
-
id: message
|
|
218
|
-
thread: message
|
|
219
|
+
id: message?.id ?? posted?.messageId,
|
|
220
|
+
thread: message?.thread_id ?? thread,
|
|
219
221
|
file: written.file,
|
|
220
222
|
...interrupted,
|
|
221
223
|
...shown,
|
|
@@ -356,12 +358,14 @@ function mailOf(bots, who, peek, until) {
|
|
|
356
358
|
trouble: `${who.bot}/${who.session} is not up, and its mailbox is bound to no tab, so there is nothing to read it as. Its mail waits until its tab is back: \`up\` brings it back, or \`unpause\` when it is paused.`,
|
|
357
359
|
};
|
|
358
360
|
}
|
|
359
|
-
// Orca hands mail over a batch at a time
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
361
|
+
// Orca hands mail over a batch at a time, so a read takes every batch in
|
|
362
|
+
// turn, or newer mail waits behind an older one (#299). Up to 1.4.245 it
|
|
363
|
+
// replays the oldest until it is acknowledged, and the acknowledgement
|
|
364
|
+
// answers with the next batch: a batch this check stops short of
|
|
365
|
+
// acknowledging is handed over again to the next. From 1.4.246 a read
|
|
366
|
+
// acknowledges the batch it hands over and says so (`deliveryAcknowledged`),
|
|
367
|
+
// and an acknowledgement answers with no batch, so the next is read again
|
|
368
|
+
// (#729; read in the 1.4.246 and 1.4.255 source).
|
|
365
369
|
const messages = [];
|
|
366
370
|
const read = { bots, bot: who.bot, session: who.session, mailbox: who.mailbox, read: !peek, messages };
|
|
367
371
|
let found = readMailbox(who.mailbox, { peek, handle }, timeLeft(until));
|
|
@@ -372,6 +376,24 @@ function mailOf(bots, who, peek, until) {
|
|
|
372
376
|
return read;
|
|
373
377
|
}
|
|
374
378
|
const wait = timeLeft(until, false);
|
|
379
|
+
if (found.deliveryAcknowledged === true) {
|
|
380
|
+
// Read already, so shown whatever comes after.
|
|
381
|
+
messages.push(...batch);
|
|
382
|
+
if (wait === undefined) {
|
|
383
|
+
return { ...read, trouble: `${who.bot}/${who.session} has more mail than one check reads in ${HOLD_MS / 1000} seconds, so it stopped there. The rest is still waiting: check again.` };
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
found = readMailbox(who.mailbox, { handle }, wait);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
// A read Orca did not answer about may have taken the next batch all
|
|
389
|
+
// the same, and taken is read: that batch is not handed over again.
|
|
390
|
+
if (error.code === TIMED_OUT) {
|
|
391
|
+
return { ...read, trouble: `${error.message} Orca may have taken the next letters as read without the kit seeing them. The mail shown above is read.` };
|
|
392
|
+
}
|
|
393
|
+
return { ...read, trouble: `${error.message} The mail shown above is read, and the rest waits for the next check.` };
|
|
394
|
+
}
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
375
397
|
if (wait === undefined) {
|
|
376
398
|
return { ...read, trouble: `${who.bot}/${who.session} has more mail than one check reads in ${HOLD_MS / 1000} seconds, so it stopped there. The rest is still waiting: check again.` };
|
|
377
399
|
}
|
package/src/orca.js
CHANGED
|
@@ -1203,6 +1203,33 @@ export const pressEscape = (handle) =>
|
|
|
1203
1203
|
/** Where Orca names the terminal a process runs in, in every pane it opens. */
|
|
1204
1204
|
export const TERMINAL_ENV = 'ORCA_TERMINAL_HANDLE';
|
|
1205
1205
|
|
|
1206
|
+
/**
|
|
1207
|
+
* One `orca orchestration` command, answered in full. From 1.4.246 Orca prints
|
|
1208
|
+
* a short answer for each of these unless it is given `--full`, and the short
|
|
1209
|
+
* one leaves out what the kit reads: a Run's `id` and `coordinator_handle`, a
|
|
1210
|
+
* letter's `from_handle`, `created_at`, `thread_id`, `read` and `delivered_at`,
|
|
1211
|
+
* and a sent letter's `message` (#729, #730; read in the 1.4.246 and 1.4.255
|
|
1212
|
+
* source, and seen live on 1.4.255). Orca 1.4.245 and older do not know the
|
|
1213
|
+
* flag on any of them but `inbox`, and refuse it before they do anything, as
|
|
1214
|
+
* `deleteProject` finds with `--force` (#528). Only that refusal sends the
|
|
1215
|
+
* command again without it: one Orca took, or did not answer, is never sent a
|
|
1216
|
+
* second time. Once refused, the rest of this run sends no `--full`.
|
|
1217
|
+
*/
|
|
1218
|
+
function orchestration(args, options) {
|
|
1219
|
+
if (fullKnown) {
|
|
1220
|
+
try {
|
|
1221
|
+
return orca([...args, '--full'], options);
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
if (error.code !== 'invalid_argument' || !error.reason?.startsWith('Unknown flag --full for command: ')) throw error;
|
|
1224
|
+
fullKnown = false;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return orca(args, options);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/** Whether this Orca may know `--full` on its orchestration commands: false once it has refused it. */
|
|
1231
|
+
let fullKnown = true;
|
|
1232
|
+
|
|
1206
1233
|
/**
|
|
1207
1234
|
* A mailbox of one session's own: an Orca Run, which is a name and an inbox and
|
|
1208
1235
|
* nothing else — it schedules nothing and runs nobody. The objective is what a
|
|
@@ -1215,7 +1242,7 @@ export const TERMINAL_ENV = 'ORCA_TERMINAL_HANDLE';
|
|
|
1215
1242
|
* tab and names no `--from` (tech notes, section 1; #317).
|
|
1216
1243
|
*/
|
|
1217
1244
|
export const makeMailbox = (objective, options) =>
|
|
1218
|
-
|
|
1245
|
+
orchestration(['orchestration', 'run-create', '--objective', `obk ${objective}`], options).run.id;
|
|
1219
1246
|
|
|
1220
1247
|
/**
|
|
1221
1248
|
* Bind a mailbox to the terminal `handle`, which makes it the Run's coordinator
|
|
@@ -1226,7 +1253,7 @@ export const makeMailbox = (objective, options) =>
|
|
|
1226
1253
|
* tab may name no other (#317).
|
|
1227
1254
|
*/
|
|
1228
1255
|
export const useMailbox = (id, handle, options) =>
|
|
1229
|
-
|
|
1256
|
+
orchestration(['orchestration', 'run-use', '--id', id, ...(handle === undefined ? [] : ['--from', handle])], options).run;
|
|
1230
1257
|
|
|
1231
1258
|
/**
|
|
1232
1259
|
* The terminal a mailbox is bound to, or undefined when it has none. A closed
|
|
@@ -1234,7 +1261,7 @@ export const useMailbox = (id, handle, options) =>
|
|
|
1234
1261
|
* nothing, only `run-use` does (tech notes, section 1).
|
|
1235
1262
|
*/
|
|
1236
1263
|
export const coordinatorOf = (id, options) =>
|
|
1237
|
-
|
|
1264
|
+
orchestration(['orchestration', 'run-show', '--id', id], options).run.coordinator_handle ?? undefined;
|
|
1238
1265
|
|
|
1239
1266
|
/**
|
|
1240
1267
|
* Whether this Orca has no Run of that id at all, as when the book was written
|
|
@@ -1245,29 +1272,36 @@ export const coordinatorOf = (id, options) =>
|
|
|
1245
1272
|
*/
|
|
1246
1273
|
export function runMissing(id, options) {
|
|
1247
1274
|
try {
|
|
1248
|
-
|
|
1275
|
+
orchestration(['orchestration', 'run-show', '--id', id], options);
|
|
1249
1276
|
return false;
|
|
1250
1277
|
} catch (error) {
|
|
1251
1278
|
return error.code === 'run_not_found';
|
|
1252
1279
|
}
|
|
1253
1280
|
}
|
|
1254
1281
|
|
|
1255
|
-
/**
|
|
1282
|
+
/**
|
|
1283
|
+
* Queue one message. `to` and `from` are mailboxes, written `run:<id>`. Orca's
|
|
1284
|
+
* whole answer comes back: the letter it queued is its `message`, where Orca
|
|
1285
|
+
* says so. Once Orca has taken it, an answer the kit cannot read is no reason
|
|
1286
|
+
* to say it was not sent (#730).
|
|
1287
|
+
*/
|
|
1256
1288
|
export function postMessage({ to, from, subject, body, type = 'status', thread }) {
|
|
1257
1289
|
const args = ['orchestration', 'send', '--to', to, '--subject', subject, '--body', body, '--type', type];
|
|
1258
1290
|
if (from !== undefined) args.push('--from', from);
|
|
1259
1291
|
if (thread !== undefined) args.push('--thread-id', thread);
|
|
1260
|
-
return
|
|
1292
|
+
return orchestration(args);
|
|
1261
1293
|
}
|
|
1262
1294
|
|
|
1263
1295
|
/**
|
|
1264
1296
|
* What is waiting in a mailbox: the oldest batch that has not been
|
|
1265
|
-
* acknowledged, or, peeking, whatever is unread without touching it.
|
|
1297
|
+
* acknowledged, or, peeking, whatever is unread without touching it. From
|
|
1298
|
+
* Orca 1.4.246 a read that is not a peek acknowledges the batch it hands over,
|
|
1299
|
+
* and says so with `deliveryAcknowledged: true` (#729). Read as
|
|
1266
1300
|
* the terminal `handle` when one is given, which is how a read works from a tab
|
|
1267
1301
|
* bound to another Run; otherwise as this process's own terminal.
|
|
1268
1302
|
*/
|
|
1269
1303
|
export const readMailbox = (id, { peek = false, handle } = {}, options) =>
|
|
1270
|
-
|
|
1304
|
+
orchestration(['orchestration', 'check', '--run', id, ...as(handle), ...(peek ? ['--peek'] : [])], options);
|
|
1271
1305
|
|
|
1272
1306
|
/**
|
|
1273
1307
|
* The newest `limit` messages in a mailbox, newest first, with every column
|
|
@@ -1278,11 +1312,14 @@ export const readMailbox = (id, { peek = false, handle } = {}, options) =>
|
|
|
1278
1312
|
* cap in Orca, so the answer is taken whatever its size.
|
|
1279
1313
|
*/
|
|
1280
1314
|
export const mailIn = (id, limit) =>
|
|
1281
|
-
|
|
1315
|
+
orchestration(['orchestration', 'inbox', '--terminal', `run:${id}`, '--limit', String(limit)], { whole: true }).messages ?? [];
|
|
1282
1316
|
|
|
1283
|
-
/**
|
|
1317
|
+
/**
|
|
1318
|
+
* Say a batch has been read. Up to Orca 1.4.245 Orca answers with the batch
|
|
1319
|
+
* after it; from 1.4.246 with no batch, since its read acknowledged it (#729).
|
|
1320
|
+
*/
|
|
1284
1321
|
export const ackMailbox = (id, delivery, handle, options) =>
|
|
1285
|
-
|
|
1322
|
+
orchestration(['orchestration', 'check', '--run', id, ...as(handle), '--ack', delivery], options);
|
|
1286
1323
|
|
|
1287
1324
|
/** The reader of a `check`: the terminal named, or this process's own. */
|
|
1288
1325
|
const as = (handle) => (handle === undefined ? [] : ['--terminal', handle]);
|