aibroker 0.61.2 → 0.61.4
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/dist/adapters/iterm/core.d.ts +34 -4
- package/dist/adapters/iterm/core.d.ts.map +1 -1
- package/dist/adapters/iterm/core.js +139 -104
- package/dist/adapters/iterm/core.js.map +1 -1
- package/dist/adapters/iterm/iterm2-api.d.ts.map +1 -1
- package/dist/adapters/iterm/iterm2-api.js +4 -2
- package/dist/adapters/iterm/iterm2-api.js.map +1 -1
- package/dist/adapters/iterm/sessions.d.ts.map +1 -1
- package/dist/adapters/iterm/sessions.js +47 -114
- package/dist/adapters/iterm/sessions.js.map +1 -1
- package/dist/adapters/pailot/gateway.d.ts.map +1 -1
- package/dist/adapters/pailot/gateway.js +6 -52
- package/dist/adapters/pailot/gateway.js.map +1 -1
- package/dist/daemon/commands.d.ts.map +1 -1
- package/dist/daemon/commands.js +4 -29
- package/dist/daemon/commands.js.map +1 -1
- package/dist/daemon/core-handlers.js +1 -1
- package/dist/daemon/core-handlers.js.map +1 -1
- package/dist/daemon/manage.d.ts +29 -18
- package/dist/daemon/manage.d.ts.map +1 -1
- package/dist/daemon/manage.js +120 -123
- package/dist/daemon/manage.js.map +1 -1
- package/dist/daemon/screenshot.d.ts.map +1 -1
- package/dist/daemon/screenshot.js +6 -42
- package/dist/daemon/screenshot.js.map +1 -1
- package/dist/daemon/session-content.d.ts +1 -1
- package/dist/daemon/session-content.d.ts.map +1 -1
- package/dist/daemon/session-content.js +10 -25
- package/dist/daemon/session-content.js.map +1 -1
- package/dist/daemon/sessions.d.ts.map +1 -1
- package/dist/daemon/sessions.js +7 -13
- package/dist/daemon/sessions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +7 -13
- package/dist/mcp/index.js.map +1 -1
- package/dist/transport/iterm.d.ts.map +1 -1
- package/dist/transport/iterm.js +12 -20
- package/dist/transport/iterm.js.map +1 -1
- package/hooks/manage-hook.mjs +4 -1
- package/package.json +1 -1
package/dist/daemon/manage.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* carries one-shot instructions from the operator into the next arming, and it
|
|
20
20
|
* says what it did. Everything requiring judgement stays with the person.
|
|
21
21
|
*/
|
|
22
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync, unlinkSync } from "node:fs";
|
|
22
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync, unlinkSync, chmodSync } from "node:fs";
|
|
23
23
|
import { execFileSync } from "node:child_process";
|
|
24
24
|
import { join, dirname } from "node:path";
|
|
25
25
|
import { homedir } from "node:os";
|
|
@@ -1727,19 +1727,17 @@ export function writeStandingRules(text, path = RULES_FILE) {
|
|
|
1727
1727
|
*/
|
|
1728
1728
|
const GOAL_MAX_CHARS = 3800;
|
|
1729
1729
|
/**
|
|
1730
|
-
* The most a LINE TYPED AT A SESSION may be, in characters
|
|
1731
|
-
* than GOAL_MAX_CHARS above, and for a different reason.
|
|
1730
|
+
* The most a LINE TYPED AT A SESSION may be, in characters.
|
|
1732
1731
|
*
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
* set at all. A ~1,150-char line failed this way that night; a ~900-char
|
|
1739
|
-
* line of the same shape still typed correctly on Claude Code 2.1.267 the
|
|
1740
|
-
* night before. 700 leaves headroom under both observed points.
|
|
1732
|
+
* Measured twice: above roughly 300 characters the prompt turns the typed
|
|
1733
|
+
* `/goal` into a pasted attachment, Enter never submits it, and the text sits
|
|
1734
|
+
* in the operator's input line while the log says "armed". Only a short line
|
|
1735
|
+
* arms. Everything long goes to a brief file the line points at (see
|
|
1736
|
+
* {@link buildGoalLine}); 250 leaves headroom under the measured point.
|
|
1741
1737
|
*/
|
|
1742
|
-
const GOAL_LINE_MAX_CHARS =
|
|
1738
|
+
const GOAL_LINE_MAX_CHARS = 250;
|
|
1739
|
+
/** Where per-session briefs are written. */
|
|
1740
|
+
const BRIEF_DIR = join(homedir(), ".aibroker", "manage-briefs");
|
|
1743
1741
|
/** `/goal` plus whichever of the given bits are non-empty, one line, no doubled spaces. */
|
|
1744
1742
|
function assembleGoalLine(agentish, objective, rulesPointer, screenGrant) {
|
|
1745
1743
|
const bits = [agentish, objective, rulesPointer, screenGrant].filter((s) => s.length > 0);
|
|
@@ -1981,65 +1979,91 @@ function screenStatus(m, now = Date.now()) {
|
|
|
1981
1979
|
}
|
|
1982
1980
|
return `screen: granted until ${at(lease.until)}, but ${describeControls(tool, now).replace(/^screen: /, "")} — renewing`;
|
|
1983
1981
|
}
|
|
1982
|
+
/** A file-name-safe slug for a session, stable across armings. */
|
|
1983
|
+
export function briefSlug(m) {
|
|
1984
|
+
const slug = (m.name || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
1985
|
+
return slug || m.sessionId.replace(/[^a-zA-Z0-9]+/g, "").slice(0, 40) || "session";
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* The line typed at the session: `/goal` plus a pointer to the brief, plus as
|
|
1989
|
+
* much of the objective's first words as fits. Never longer than `max`.
|
|
1990
|
+
*/
|
|
1991
|
+
export function buildGoalLine(briefPath, objective, max = GOAL_LINE_MAX_CHARS) {
|
|
1992
|
+
const head = `/goal Follow ${briefPath} until done`;
|
|
1993
|
+
const room = max - head.length - 2;
|
|
1994
|
+
const words = oneLine(objective);
|
|
1995
|
+
return room >= 20 && words ? `${head}: ${truncateAtWord(words, room)}` : head;
|
|
1996
|
+
}
|
|
1997
|
+
/** Everything that does not fit on the typed line, as the file it points to. */
|
|
1998
|
+
export function buildBrief(parts) {
|
|
1999
|
+
const sections = [];
|
|
2000
|
+
if (parts.agentish)
|
|
2001
|
+
sections.push(`## Message format between agents\n\n${parts.agentish}`);
|
|
2002
|
+
sections.push(`## Objective\n\n${parts.objective}`);
|
|
2003
|
+
if (parts.shiftRules)
|
|
2004
|
+
sections.push(`## Shift rules\n\n${parts.shiftRules}`);
|
|
2005
|
+
if (parts.rulesSource) {
|
|
2006
|
+
sections.push(`## Standing rules\n\nFIRST, before anything else: read ${parts.rulesSource} and follow every rule in it for the whole of this work — they are not optional and they are not summarised here.`);
|
|
2007
|
+
}
|
|
2008
|
+
if (parts.hands.trim())
|
|
2009
|
+
sections.push(`## Screen\n\n${parts.hands.trim()}`);
|
|
2010
|
+
if (parts.pending.length) {
|
|
2011
|
+
sections.push(`## Operator notes since you were last armed\n\n${parts.pending.map((p) => `- ${p}`).join("\n")}`);
|
|
2012
|
+
}
|
|
2013
|
+
return `${sections.join("\n\n")}\n`;
|
|
2014
|
+
}
|
|
1984
2015
|
/**
|
|
1985
|
-
* The
|
|
1986
|
-
* afterwards to know it landed.
|
|
2016
|
+
* The line typed at the session, after rewriting its brief file (0600).
|
|
1987
2017
|
*
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
1990
|
-
*
|
|
1991
|
-
* there is no case left where trying the longer composeGoal() inline form
|
|
1992
|
-
* first would help.
|
|
2018
|
+
* Rewritten on every arming so standing rules, shift state, the screen grant
|
|
2019
|
+
* and pending operator notes are always current; pending notes are consumed
|
|
2020
|
+
* into the brief, not the line.
|
|
1993
2021
|
*/
|
|
1994
2022
|
function goalText(m) {
|
|
1995
|
-
const extra = m.pending.length ? ` OPERATOR, since you were last armed: ${m.pending.join(" ")}` : "";
|
|
1996
|
-
// The screen rule has to ride along with EVERY arming. Delivered once, it
|
|
1997
|
-
// lasts only until the session next reads a goal — and the goal is what tells
|
|
1998
|
-
// it what to do. So a standing rule that is not in the goal is a rule with a
|
|
1999
|
-
// lifetime of one turn, and the next arming would send it back to clicking.
|
|
2000
|
-
//
|
|
2001
|
-
// The positive case has to ride along too, and for a sharper reason. Silence
|
|
2002
|
-
// about the screen is not neutral: a session that has been careful with the
|
|
2003
|
-
// operator's machine all night reads it as permission having quietly ended,
|
|
2004
|
-
// and one of them announced it had handed the controls back while the grant
|
|
2005
|
-
// on disk still had two hours on it. Saying the expiry out loud, every time,
|
|
2006
|
-
// is what stops a session inventing one.
|
|
2007
|
-
//
|
|
2008
|
-
// `rules clear` is the operator saying objectives now carry only themselves,
|
|
2009
|
-
// so the screen guidance goes with the rest of the standing prose and only
|
|
2010
|
-
// the expiry rides along. The prohibition below is NOT shortened with it: a
|
|
2011
|
-
// goal template that hands the screen over says nothing about the case where
|
|
2012
|
-
// the screen is withheld, so that guidance exists nowhere else.
|
|
2013
2023
|
const rules = readStandingRules();
|
|
2024
|
+
// The screen rule rides along with EVERY arming: delivered once it lasts
|
|
2025
|
+
// only until the session next reads a goal, and silence about the screen is
|
|
2026
|
+
// read as permission having quietly ended. Now it lives in the brief.
|
|
2014
2027
|
const hands = m.noScreen
|
|
2015
|
-
? "
|
|
2028
|
+
? "THE OPERATOR HAS THE SCREEN: do no screen or pointer work at all, and do not ask for it. Everything else continues as normal. Where something would need checking on screen, write down what would need checking instead of checking it."
|
|
2016
2029
|
: (() => {
|
|
2017
2030
|
const lease = screenLease(m, Date.now());
|
|
2018
2031
|
return lease ? screenGrantedClause(lease.until, !rules) : "";
|
|
2019
2032
|
})();
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
: ""
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
*
|
|
2036
|
-
*
|
|
2037
|
-
*
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2033
|
+
const shift = shiftObjective();
|
|
2034
|
+
const path = join(BRIEF_DIR, `${briefSlug(m)}.md`);
|
|
2035
|
+
mkdirSync(BRIEF_DIR, { recursive: true, mode: 0o700 });
|
|
2036
|
+
writeFileSync(path, buildBrief({
|
|
2037
|
+
objective: m.objective,
|
|
2038
|
+
agentish: AG2_SPEC,
|
|
2039
|
+
rulesSource: rules ? standingRulesSource() : "",
|
|
2040
|
+
shiftRules: m.shift && m.objective !== shift ? shift : "",
|
|
2041
|
+
hands,
|
|
2042
|
+
pending: m.pending,
|
|
2043
|
+
}), { mode: 0o600 });
|
|
2044
|
+
chmodSync(path, 0o600);
|
|
2045
|
+
return { text: buildGoalLine(foldHome(path), m.objective) };
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Did it arm? Within a bounded wait the pane must show the goal marker with an
|
|
2049
|
+
* empty input line. If our own line is still sitting in the input, it is
|
|
2050
|
+
* cleared — never left in the operator's prompt — and the arm counts as failed.
|
|
2051
|
+
*
|
|
2052
|
+
* Text is not proof of a landing (a stranded line shows up in the transcript
|
|
2053
|
+
* area too, which is what the earlier fragment check could not tell apart).
|
|
2054
|
+
*/
|
|
2055
|
+
export async function confirmArmed(typed, deps, waits = 5, waitMs = 2_000) {
|
|
2056
|
+
let unsent = null;
|
|
2057
|
+
for (let i = 0; i < waits; i++) {
|
|
2058
|
+
await deps.sleep(waitMs);
|
|
2059
|
+
const pane = deps.readPane();
|
|
2060
|
+
unsent = promptUnsentText(pane);
|
|
2061
|
+
if (GOAL_ACTIVE.test(pane) && !unsent)
|
|
2062
|
+
return true;
|
|
2063
|
+
}
|
|
2064
|
+
if (unsent && typedLineMatches(unsent, typed))
|
|
2065
|
+
deps.clearInput();
|
|
2066
|
+
return false;
|
|
2043
2067
|
}
|
|
2044
2068
|
function readPane(sessionId, opts = {}) {
|
|
2045
2069
|
try {
|
|
@@ -2053,7 +2077,7 @@ async function sleep(ms) {
|
|
|
2053
2077
|
return new Promise((r) => setTimeout(r, ms));
|
|
2054
2078
|
}
|
|
2055
2079
|
async function arm(m, reason) {
|
|
2056
|
-
const { text
|
|
2080
|
+
const { text } = goalText(m);
|
|
2057
2081
|
/**
|
|
2058
2082
|
* NEVER TYPE A GOAL INTO A BARE SHELL.
|
|
2059
2083
|
*
|
|
@@ -2155,22 +2179,20 @@ async function arm(m, reason) {
|
|
|
2155
2179
|
}
|
|
2156
2180
|
sendEnterKey(m.sessionId);
|
|
2157
2181
|
// Typed is not sent, and sent is not received.
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
return true;
|
|
2171
|
-
}
|
|
2182
|
+
const landed = await confirmArmed(text, {
|
|
2183
|
+
readPane: () => readPane(m.sessionId, { fresh: true }),
|
|
2184
|
+
sleep,
|
|
2185
|
+
clearInput: () => sendControlU(m.sessionId),
|
|
2186
|
+
});
|
|
2187
|
+
if (landed) {
|
|
2188
|
+
m.lastRearmAt = Date.now();
|
|
2189
|
+
const carried = m.pending.length;
|
|
2190
|
+
m.pending = [];
|
|
2191
|
+
armingsSinceReport.set(m.sessionId, (armingsSinceReport.get(m.sessionId) ?? 0) + 1);
|
|
2192
|
+
note(m, `armed: ${reason}${carried ? ` (carrying ${carried} operator instruction${carried > 1 ? "s" : ""})` : ""}`);
|
|
2193
|
+
return true;
|
|
2172
2194
|
}
|
|
2173
|
-
notify(m, `typed but the
|
|
2195
|
+
notify(m, `typed but the goal never took (no active marker, or the line stayed in the input) — input cleared, treating as NOT armed (${reason})`);
|
|
2174
2196
|
return false;
|
|
2175
2197
|
}
|
|
2176
2198
|
/**
|
|
@@ -2278,26 +2300,8 @@ function beat() {
|
|
|
2278
2300
|
*/
|
|
2279
2301
|
const DIALOG_EVERY_TICKS = 3;
|
|
2280
2302
|
let tickCount = 0;
|
|
2281
|
-
/**
|
|
2282
|
-
|
|
2283
|
-
/**
|
|
2284
|
-
* Pure decision for one tick: a transient failure (an osascript hitting the
|
|
2285
|
-
* LaunchServices window) clears within a tick or two and must not page anyone;
|
|
2286
|
-
* only a sustained one alerts, once, and only that one earns a recovery line.
|
|
2287
|
-
*/
|
|
2288
|
-
export function enumerationStep(ep, now, reliable) {
|
|
2289
|
-
if (reliable) {
|
|
2290
|
-
const downMs = ep.since === null ? 0 : now - ep.since;
|
|
2291
|
-
return { ep: { since: null, alerted: false }, action: ep.alerted ? "recovered" : "none", downMs };
|
|
2292
|
-
}
|
|
2293
|
-
const since = ep.since ?? now;
|
|
2294
|
-
const downMs = now - since;
|
|
2295
|
-
if (!ep.alerted && downMs >= ENUMERATION_ALERT_AFTER_MS) {
|
|
2296
|
-
return { ep: { since, alerted: true }, action: "alert", downMs };
|
|
2297
|
-
}
|
|
2298
|
-
return { ep: { since, alerted: ep.alerted }, action: "none", downMs };
|
|
2299
|
-
}
|
|
2300
|
-
let enumerationEpisode = { since: null, alerted: false };
|
|
2303
|
+
/** When the current iTerm enumeration failure began; null while reliable. Logged, never alerted. */
|
|
2304
|
+
let enumerationDownSince = null;
|
|
2301
2305
|
function answerBlockingDialogs() {
|
|
2302
2306
|
if (Object.keys(state).length === 0)
|
|
2303
2307
|
return;
|
|
@@ -2323,26 +2327,14 @@ async function tick() {
|
|
|
2323
2327
|
// failure this exists to catch. Reuses the flag every `sessions`/`status`
|
|
2324
2328
|
// caller already sets; no separate probe.
|
|
2325
2329
|
const reliable = wasLastEnumerationReliable();
|
|
2326
|
-
if (!reliable &&
|
|
2327
|
-
|
|
2328
|
-
const step = enumerationStep(enumerationEpisode, now, reliable);
|
|
2329
|
-
enumerationEpisode = step.ep;
|
|
2330
|
-
if (step.action === "alert") {
|
|
2331
|
-
let text = "iTerm session enumeration is failing (AppleScript errors) for over 2 minutes — the session list may be empty or stale until this clears.";
|
|
2330
|
+
if (!reliable && enumerationDownSince === null) {
|
|
2331
|
+
enumerationDownSince = now;
|
|
2332
2332
|
const impostors = findItermBundleIdImpostors();
|
|
2333
|
-
|
|
2334
|
-
const named = impostors.map((i) => `pid ${i.pid} (${i.executablePath})`).join(", ");
|
|
2335
|
-
text += ` Found ${named} registered under iTerm's bundle id — a process spawned with iTerm's __CFBundleIdentifier; killing it restores enumeration.`;
|
|
2336
|
-
}
|
|
2337
|
-
alertOperator(text);
|
|
2338
|
-
log(`[manage] iTerm enumeration unreliable — alerted the operator${impostors.length ? ` (impostors: ${impostors.map((i) => i.pid).join(", ")})` : ""}`);
|
|
2339
|
-
}
|
|
2340
|
-
else if (step.action === "recovered") {
|
|
2341
|
-
alertOperator(`iTerm session enumeration recovered after ${Math.round(step.downMs / 1000)}s.`);
|
|
2342
|
-
log("[manage] iTerm enumeration recovered — alerted the operator");
|
|
2333
|
+
log(`[manage] iTerm enumeration unreliable — episode started${impostors.length ? ` (impostors: ${impostors.map((i) => i.pid).join(", ")})` : ""}`);
|
|
2343
2334
|
}
|
|
2344
|
-
else if (reliable &&
|
|
2345
|
-
log(`[manage] iTerm enumeration recovered after ${Math.round(
|
|
2335
|
+
else if (reliable && enumerationDownSince !== null) {
|
|
2336
|
+
log(`[manage] iTerm enumeration recovered after ${Math.round((now - enumerationDownSince) / 1000)}s`);
|
|
2337
|
+
enumerationDownSince = null;
|
|
2346
2338
|
}
|
|
2347
2339
|
if (++tickCount % DIALOG_EVERY_TICKS === 0) {
|
|
2348
2340
|
try {
|
|
@@ -3068,6 +3060,9 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3068
3060
|
message: `manage — keep a session working on a standing objective.\n\n` +
|
|
3069
3061
|
` <objective> start managing, or once running, an instruction carried\n` +
|
|
3070
3062
|
` into the next arming ("do the tests before the docs")\n` +
|
|
3063
|
+
` start <text> start managing, or REPLACE the objective when already managed\n` +
|
|
3064
|
+
` instruct <text>\n` +
|
|
3065
|
+
` a one-shot note for the next arming (same as plain text)\n` +
|
|
3071
3066
|
` status what the session looks like right now, and what the\n` +
|
|
3072
3067
|
` manager has done. Also: state, what, info, show\n` +
|
|
3073
3068
|
` hands off the operator needs the screen: stops visual work at once,\n` +
|
|
@@ -3204,8 +3199,8 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3204
3199
|
}
|
|
3205
3200
|
const { hours, visual, workers } = parseShift(rest);
|
|
3206
3201
|
const until = Date.now() + hours * 3_600_000;
|
|
3207
|
-
existing.objective
|
|
3208
|
-
|
|
3202
|
+
if (!existing.objective.trim())
|
|
3203
|
+
existing.objective = shiftObjective();
|
|
3209
3204
|
existing.paused = false;
|
|
3210
3205
|
existing.noScreen = !visual;
|
|
3211
3206
|
existing.handsUntil = until;
|
|
@@ -3241,7 +3236,7 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3241
3236
|
message: `${name} is on shift for ${hours} hour(s), until ${ends}.\n` +
|
|
3242
3237
|
` ${visual ? "The screen is its own until then, and reverts by itself." : "No screen work — the operator has the machine."}\n` +
|
|
3243
3238
|
screenLine +
|
|
3244
|
-
` Objective set to the tracker's open issues; the standing rules ride along with every arming.\n` +
|
|
3239
|
+
` ${existing.objective === shiftObjective() ? "Objective set to the tracker's open issues" : "Objective kept, the shift's issue-work rules go in the brief"}; the standing rules ride along with every arming.\n` +
|
|
3245
3240
|
(workers > 1
|
|
3246
3241
|
? ` Asked for ${workers} workers. Only one runs today — worktrees and the claim protocol are designed but not built, and a second worker without them would share a checkout with the first.\n`
|
|
3247
3242
|
: "") +
|
|
@@ -3310,7 +3305,7 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3310
3305
|
* rather than misleading once. That is the difference between an objective
|
|
3311
3306
|
* and a message, and it is why this needs its own verb.
|
|
3312
3307
|
*/
|
|
3313
|
-
const setMatch = arg.match(/^(?:set|objective|replace)\s+([\s\S]+)$/i);
|
|
3308
|
+
const setMatch = arg.match(/^(?:set|objective|replace|start)\s+([\s\S]+)$/i);
|
|
3314
3309
|
if (setMatch && existing) {
|
|
3315
3310
|
const before = existing.objective;
|
|
3316
3311
|
existing.objective = setMatch[1].trim();
|
|
@@ -3592,10 +3587,11 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3592
3587
|
`If you meant a different session, name it first: aibroker manage <session> <objective>`,
|
|
3593
3588
|
};
|
|
3594
3589
|
}
|
|
3590
|
+
const objective = arg.match(/^start\s+([\s\S]+)$/i)?.[1].trim() ?? arg;
|
|
3595
3591
|
const m = {
|
|
3596
3592
|
sessionId,
|
|
3597
3593
|
name,
|
|
3598
|
-
objective
|
|
3594
|
+
objective,
|
|
3599
3595
|
pending: [],
|
|
3600
3596
|
history: [],
|
|
3601
3597
|
// Give the session the benefit of the grace period rather than arming
|
|
@@ -3617,7 +3613,7 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3617
3613
|
return {
|
|
3618
3614
|
ok: true,
|
|
3619
3615
|
managed: true,
|
|
3620
|
-
message: `managing ${name}. It will be re-armed with this objective whenever it stops:\n ${
|
|
3616
|
+
message: `managing ${name}. It will be re-armed with this objective whenever it stops:\n ${objective}`,
|
|
3621
3617
|
};
|
|
3622
3618
|
}
|
|
3623
3619
|
if (word === "now") {
|
|
@@ -3625,8 +3621,9 @@ export async function handleManage(sessionIdOrName, rawArg) {
|
|
|
3625
3621
|
saveState(state);
|
|
3626
3622
|
return { ok: true, managed: true, message: `${name} will be armed on the next tick` };
|
|
3627
3623
|
}
|
|
3628
|
-
|
|
3629
|
-
|
|
3624
|
+
const instruction = arg.match(/^instruct\s+([\s\S]+)$/i)?.[1].trim() ?? arg;
|
|
3625
|
+
existing.pending.push(instruction);
|
|
3626
|
+
note(existing, `operator: ${instruction.slice(0, 80)}`);
|
|
3630
3627
|
saveState(state);
|
|
3631
3628
|
return {
|
|
3632
3629
|
ok: true,
|