@paigy/harness 0.3.41 → 0.3.42
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/cli.js +305 -159
- package/dist/main.js +299 -153
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -23101,7 +23101,8 @@ var CATALOG = [
|
|
|
23101
23101
|
authProbe: ["claude", "auth", "status"],
|
|
23102
23102
|
loginHint: "Run `claude` once in a terminal and sign in.",
|
|
23103
23103
|
installCli: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
23104
|
-
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
23104
|
+
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp",
|
|
23105
|
+
models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"]
|
|
23105
23106
|
},
|
|
23106
23107
|
{
|
|
23107
23108
|
name: "codex",
|
|
@@ -23111,7 +23112,8 @@ var CATALOG = [
|
|
|
23111
23112
|
authProbe: ["codex", "login", "status"],
|
|
23112
23113
|
loginHint: "Run `codex login` to authenticate.",
|
|
23113
23114
|
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
23114
|
-
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
23115
|
+
installAdapter: "npm install -g @agentclientprotocol/codex-acp",
|
|
23116
|
+
models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"]
|
|
23115
23117
|
},
|
|
23116
23118
|
{
|
|
23117
23119
|
name: "agy",
|
|
@@ -23121,7 +23123,8 @@ var CATALOG = [
|
|
|
23121
23123
|
adapter: "paigy-agy-acp",
|
|
23122
23124
|
authProbe: ["agy", "help"],
|
|
23123
23125
|
loginHint: "Install and set up Antigravity (`agy`).",
|
|
23124
|
-
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
23126
|
+
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash",
|
|
23127
|
+
models: ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-pro-exp"]
|
|
23125
23128
|
}
|
|
23126
23129
|
];
|
|
23127
23130
|
function probeDirs(home = homedir()) {
|
|
@@ -23157,7 +23160,7 @@ function runProbe(argv) {
|
|
|
23157
23160
|
function detect(entry, deps = {}) {
|
|
23158
23161
|
const find = deps.which ?? which;
|
|
23159
23162
|
const probe = deps.probe ?? runProbe;
|
|
23160
|
-
const base = { name: entry.name, label: entry.label };
|
|
23163
|
+
const base = { name: entry.name, label: entry.label, models: entry.models };
|
|
23161
23164
|
const cliPath = find(entry.cli);
|
|
23162
23165
|
if (!cliPath) return { ...base, status: "missing", hint: `${entry.label} is not installed.`, install: entry.installCli };
|
|
23163
23166
|
const adapterPath = entry.adapter ? find(entry.adapter) : null;
|
|
@@ -29745,6 +29748,31 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
29745
29748
|
ifSilent: external_exports.boolean().optional()
|
|
29746
29749
|
});
|
|
29747
29750
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
29751
|
+
var CallQuestionSchema = external_exports.object({
|
|
29752
|
+
id: external_exports.string(),
|
|
29753
|
+
entryId: external_exports.string(),
|
|
29754
|
+
title: external_exports.string(),
|
|
29755
|
+
state: external_exports.enum(["open", "answered"]),
|
|
29756
|
+
answer: external_exports.string().nullable(),
|
|
29757
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
29758
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
29759
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
29760
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
29761
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
29762
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
29763
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
29764
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
29765
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
29766
|
+
options: external_exports.array(OptionSchema).optional(),
|
|
29767
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
29768
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
29769
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
29770
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
29771
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
29772
|
+
choices: external_exports.array(OptionSchema).optional(),
|
|
29773
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
29774
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
29775
|
+
});
|
|
29748
29776
|
var InboxItemSchema = external_exports.object({
|
|
29749
29777
|
id: external_exports.string(),
|
|
29750
29778
|
tokenId: external_exports.string().optional(),
|
|
@@ -29786,31 +29814,10 @@ var InboxItemSchema = external_exports.object({
|
|
|
29786
29814
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
29787
29815
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
29788
29816
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
29789
|
-
questions: external_exports.array(
|
|
29790
|
-
|
|
29791
|
-
|
|
29792
|
-
|
|
29793
|
-
state: external_exports.enum(["open", "answered"]),
|
|
29794
|
-
answer: external_exports.string().nullable(),
|
|
29795
|
-
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
29796
|
-
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
29797
|
-
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
29798
|
-
* author is not an agent this account holds (unpaired since, or a person). */
|
|
29799
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
29800
|
-
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
29801
|
-
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
29802
|
-
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
29803
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
29804
|
-
options: external_exports.array(OptionSchema).optional(),
|
|
29805
|
-
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
29806
|
-
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
29807
|
-
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
29808
|
-
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
29809
|
-
* preview layout for any `options`, and must see exactly what it did. */
|
|
29810
|
-
choices: external_exports.array(OptionSchema).optional(),
|
|
29811
|
-
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
29812
|
-
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
29813
|
-
})).optional(),
|
|
29817
|
+
questions: external_exports.array(CallQuestionSchema).optional(),
|
|
29818
|
+
/** The same call carrying order, including request Entries with no DecisionNeed as reports.
|
|
29819
|
+
* Additive: questions stays decision-only for every other surface and older bundles. */
|
|
29820
|
+
callItems: external_exports.array(CallQuestionSchema.extend({ state: external_exports.enum(["open", "answered", "report"]) })).optional(),
|
|
29814
29821
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
29815
29822
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
29816
29823
|
* agent's suggestion, or a default silly name). */
|
|
@@ -30009,12 +30016,29 @@ var UserSettingsSchema = external_exports.object({
|
|
|
30009
30016
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
30010
30017
|
broker: BrokerTuningSchema.optional()
|
|
30011
30018
|
});
|
|
30019
|
+
var GoalLatestSchema = external_exports.object({
|
|
30020
|
+
from: external_exports.enum(["person", "agent"]),
|
|
30021
|
+
said: external_exports.string(),
|
|
30022
|
+
at: external_exports.string(),
|
|
30023
|
+
entryId: external_exports.string(),
|
|
30024
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
30025
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
30026
|
+
* none was. The Work row reads it before `said`. */
|
|
30027
|
+
summary: external_exports.string().optional(),
|
|
30028
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
30029
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
30030
|
+
titles: external_exports.array(external_exports.string()).optional(),
|
|
30031
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
30032
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
30033
|
+
line: external_exports.string().optional()
|
|
30034
|
+
});
|
|
30012
30035
|
var HistoryWorkSchema = external_exports.object({
|
|
30013
30036
|
id: external_exports.string(),
|
|
30014
30037
|
title: external_exports.string(),
|
|
30015
30038
|
state: external_exports.enum(["done", "cancelled"]),
|
|
30016
30039
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
30017
|
-
assignee: external_exports.string()
|
|
30040
|
+
assignee: external_exports.string(),
|
|
30041
|
+
latest: GoalLatestSchema.nullable().optional()
|
|
30018
30042
|
});
|
|
30019
30043
|
var HistoryEntrySchema = external_exports.union([
|
|
30020
30044
|
external_exports.object({ at: external_exports.string(), card: InboxItemSchema }),
|
|
@@ -30082,7 +30106,7 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
30082
30106
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
30083
30107
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
30084
30108
|
version: external_exports.string().optional(),
|
|
30085
|
-
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
30109
|
+
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string(), models: external_exports.array(external_exports.string()).optional() })).optional(),
|
|
30086
30110
|
workspaces: external_exports.array(external_exports.string()).optional(),
|
|
30087
30111
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
30088
30112
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
@@ -30242,22 +30266,7 @@ var QueueItemSchema = external_exports.object({
|
|
|
30242
30266
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
30243
30267
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
30244
30268
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
30245
|
-
latest:
|
|
30246
|
-
from: external_exports.enum(["person", "agent"]),
|
|
30247
|
-
said: external_exports.string(),
|
|
30248
|
-
at: external_exports.string(),
|
|
30249
|
-
entryId: external_exports.string(),
|
|
30250
|
-
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
30251
|
-
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
30252
|
-
* none was. The Work row reads it before `said`. */
|
|
30253
|
-
summary: external_exports.string().optional(),
|
|
30254
|
-
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
30255
|
-
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
30256
|
-
titles: external_exports.array(external_exports.string()).optional(),
|
|
30257
|
-
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
30258
|
-
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
30259
|
-
line: external_exports.string().optional()
|
|
30260
|
-
}).nullable().optional(),
|
|
30269
|
+
latest: GoalLatestSchema.nullable().optional(),
|
|
30261
30270
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
30262
30271
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
30263
30272
|
* answer to a question, a voice note filed as work. Null when the only hands on it have been its
|
|
@@ -31130,7 +31139,7 @@ var import_qrcode = __toESM(require_lib(), 1);
|
|
|
31130
31139
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
31131
31140
|
import { hostname } from "os";
|
|
31132
31141
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
31133
|
-
import { execFile as execFile2, execSync } from "child_process";
|
|
31142
|
+
import { execFile as execFile2, execSync as execSync2 } from "child_process";
|
|
31134
31143
|
import { homedir as homedir9 } from "os";
|
|
31135
31144
|
import { join as join11 } from "path";
|
|
31136
31145
|
|
|
@@ -31512,7 +31521,8 @@ function startSession(opts) {
|
|
|
31512
31521
|
if (!existsSync3(cwd)) {
|
|
31513
31522
|
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
31514
31523
|
}
|
|
31515
|
-
|
|
31524
|
+
let codexConfig = opts.harness === "codex" ? JSON.parse(process.env.CODEX_CONFIG || "{}") : null;
|
|
31525
|
+
if (opts.model && opts.harness === "codex") codexConfig = { ...codexConfig, model: opts.model };
|
|
31516
31526
|
if (codexConfig) {
|
|
31517
31527
|
const key3 = "mcp_servers.paigy.env_vars";
|
|
31518
31528
|
const configPath = join5(process.env.CODEX_HOME || join5(homedir4(), ".codex"), "config.toml");
|
|
@@ -31538,6 +31548,8 @@ function startSession(opts) {
|
|
|
31538
31548
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
31539
31549
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
31540
31550
|
...opts.instance ? { PAIGY_INSTANCE_ID: opts.instance } : {},
|
|
31551
|
+
...opts.model && opts.harness === "claude" ? { CLAUDE_MODEL_CONFIG: JSON.stringify({ provider: "anthropic", model: opts.model }) } : {},
|
|
31552
|
+
...opts.model && opts.harness === "agy" ? { AGY_MODEL_CONFIG: JSON.stringify({ model: opts.model }) } : {},
|
|
31541
31553
|
...codexConfig ? { CODEX_CONFIG: JSON.stringify(codexConfig) } : {},
|
|
31542
31554
|
...opts.session ?? opts.resume ? { PAIGY_SESSION_ID: opts.session ?? opts.resume } : {},
|
|
31543
31555
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
@@ -31552,19 +31564,56 @@ function startSession(opts) {
|
|
|
31552
31564
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31553
31565
|
});
|
|
31554
31566
|
let ended2 = false;
|
|
31555
|
-
|
|
31556
|
-
|
|
31567
|
+
let failure;
|
|
31568
|
+
let hush;
|
|
31569
|
+
const pending = /* @__PURE__ */ new Set();
|
|
31570
|
+
const end = (error) => {
|
|
31571
|
+
ended2 = true;
|
|
31572
|
+
failure ??= error;
|
|
31573
|
+
clearTimeout(hush);
|
|
31574
|
+
for (const receipt of pending) receipt.reject(failure);
|
|
31575
|
+
pending.clear();
|
|
31576
|
+
};
|
|
31577
|
+
const broken = (error) => {
|
|
31578
|
+
if (ended2) return;
|
|
31579
|
+
end(new Error(`session_write_failed: ${error.message}`));
|
|
31580
|
+
opts.onEvent({ kind: "error", message: failure.message });
|
|
31581
|
+
child.kill();
|
|
31582
|
+
};
|
|
31583
|
+
child.stdin?.on("error", broken);
|
|
31557
31584
|
const write = (frames) => {
|
|
31558
31585
|
if (ended2) return;
|
|
31559
|
-
for (const f of frames)
|
|
31560
|
-
|
|
31561
|
-
|
|
31586
|
+
for (const f of frames) {
|
|
31587
|
+
if (ended2) break;
|
|
31588
|
+
if (!child.stdin || child.stdin.destroyed || !child.stdin.writable) {
|
|
31589
|
+
broken(new Error("stdin unavailable"));
|
|
31590
|
+
break;
|
|
31591
|
+
}
|
|
31592
|
+
const frame3 = JSON.parse(f);
|
|
31593
|
+
const receipts2 = frame3.method === "session/prompt" ? [...pending].filter((receipt) => !receipt.writing).slice(0, frame3.params.prompt.length) : [];
|
|
31594
|
+
for (const receipt of receipts2) receipt.writing = true;
|
|
31595
|
+
try {
|
|
31596
|
+
child.stdin.write(`${f}
|
|
31597
|
+
`, (error) => {
|
|
31598
|
+
if (error) {
|
|
31599
|
+
broken(error);
|
|
31600
|
+
return;
|
|
31601
|
+
}
|
|
31602
|
+
if (ended2) return;
|
|
31603
|
+
for (const receipt of receipts2) if (pending.delete(receipt)) receipt.resolve();
|
|
31604
|
+
});
|
|
31605
|
+
} catch (error) {
|
|
31606
|
+
broken(error instanceof Error ? error : new Error(String(error)));
|
|
31607
|
+
}
|
|
31608
|
+
}
|
|
31609
|
+
if (frames.length && !ended2) listen();
|
|
31562
31610
|
};
|
|
31563
31611
|
const apply = ({ events, writes }) => {
|
|
31612
|
+
const fatal = events.find((event) => event.kind === "error" && event.fatal);
|
|
31613
|
+
if (fatal?.kind === "error") end(new Error(fatal.message));
|
|
31564
31614
|
write(writes);
|
|
31565
31615
|
for (const event of events) opts.onEvent(event);
|
|
31566
31616
|
};
|
|
31567
|
-
let hush;
|
|
31568
31617
|
const listen = () => {
|
|
31569
31618
|
clearTimeout(hush);
|
|
31570
31619
|
if (!ended2) hush = setTimeout(() => apply(driver.quiet(QUIET_MS)), QUIET_MS);
|
|
@@ -31574,12 +31623,12 @@ function startSession(opts) {
|
|
|
31574
31623
|
if (text) opts.onEvent({ kind: "error", message: text });
|
|
31575
31624
|
});
|
|
31576
31625
|
child.on("exit", (code) => {
|
|
31577
|
-
|
|
31578
|
-
clearTimeout(hush);
|
|
31626
|
+
end(new Error(`session_ended: ${code ?? "signal"}`));
|
|
31579
31627
|
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
31580
31628
|
opts.onExit?.();
|
|
31581
31629
|
});
|
|
31582
31630
|
child.on("error", (e) => {
|
|
31631
|
+
end(new Error(`session_start_failed: ${e.message}`));
|
|
31583
31632
|
opts.onEvent({ kind: "error", message: e.message });
|
|
31584
31633
|
opts.onExit?.();
|
|
31585
31634
|
});
|
|
@@ -31599,15 +31648,24 @@ function startSession(opts) {
|
|
|
31599
31648
|
write(driver.open());
|
|
31600
31649
|
return {
|
|
31601
31650
|
send(text) {
|
|
31602
|
-
|
|
31651
|
+
const sent = new Promise((resolve3, reject) => {
|
|
31652
|
+
if (ended2) {
|
|
31653
|
+
reject(failure ?? new Error("session_ended"));
|
|
31654
|
+
return;
|
|
31655
|
+
}
|
|
31656
|
+
pending.add({ resolve: resolve3, reject, writing: false });
|
|
31657
|
+
write(driver.send(text));
|
|
31658
|
+
});
|
|
31659
|
+
void sent.catch(() => {
|
|
31660
|
+
});
|
|
31661
|
+
return sent;
|
|
31603
31662
|
},
|
|
31604
31663
|
respond(id3, decision) {
|
|
31605
31664
|
write(driver.respond(id3, decision));
|
|
31606
31665
|
},
|
|
31607
31666
|
stop() {
|
|
31608
31667
|
write(driver.close());
|
|
31609
|
-
|
|
31610
|
-
clearTimeout(hush);
|
|
31668
|
+
end(new Error("session_stopped"));
|
|
31611
31669
|
child.kill();
|
|
31612
31670
|
}
|
|
31613
31671
|
};
|
|
@@ -33992,6 +34050,31 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
33992
34050
|
ifSilent: external_exports.boolean().optional()
|
|
33993
34051
|
});
|
|
33994
34052
|
var CLAIM_STALE_MS2 = 30 * 6e4;
|
|
34053
|
+
var CallQuestionSchema2 = external_exports.object({
|
|
34054
|
+
id: external_exports.string(),
|
|
34055
|
+
entryId: external_exports.string(),
|
|
34056
|
+
title: external_exports.string(),
|
|
34057
|
+
state: external_exports.enum(["open", "answered"]),
|
|
34058
|
+
answer: external_exports.string().nullable(),
|
|
34059
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
34060
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
34061
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
34062
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
34063
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
34064
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
34065
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
34066
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
34067
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
34068
|
+
options: external_exports.array(OptionSchema2).optional(),
|
|
34069
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
34070
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
34071
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
34072
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
34073
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
34074
|
+
choices: external_exports.array(OptionSchema2).optional(),
|
|
34075
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
34076
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
34077
|
+
});
|
|
33995
34078
|
var InboxItemSchema2 = external_exports.object({
|
|
33996
34079
|
id: external_exports.string(),
|
|
33997
34080
|
tokenId: external_exports.string().optional(),
|
|
@@ -34033,31 +34116,10 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
34033
34116
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
34034
34117
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
34035
34118
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
34036
|
-
questions: external_exports.array(
|
|
34037
|
-
|
|
34038
|
-
|
|
34039
|
-
|
|
34040
|
-
state: external_exports.enum(["open", "answered"]),
|
|
34041
|
-
answer: external_exports.string().nullable(),
|
|
34042
|
-
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
34043
|
-
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
34044
|
-
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
34045
|
-
* author is not an agent this account holds (unpaired since, or a person). */
|
|
34046
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
34047
|
-
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
34048
|
-
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
34049
|
-
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
34050
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
34051
|
-
options: external_exports.array(OptionSchema2).optional(),
|
|
34052
|
-
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
34053
|
-
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
34054
|
-
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
34055
|
-
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
34056
|
-
* preview layout for any `options`, and must see exactly what it did. */
|
|
34057
|
-
choices: external_exports.array(OptionSchema2).optional(),
|
|
34058
|
-
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
34059
|
-
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
34060
|
-
})).optional(),
|
|
34119
|
+
questions: external_exports.array(CallQuestionSchema2).optional(),
|
|
34120
|
+
/** The same call carrying order, including request Entries with no DecisionNeed as reports.
|
|
34121
|
+
* Additive: questions stays decision-only for every other surface and older bundles. */
|
|
34122
|
+
callItems: external_exports.array(CallQuestionSchema2.extend({ state: external_exports.enum(["open", "answered", "report"]) })).optional(),
|
|
34061
34123
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
34062
34124
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
34063
34125
|
* agent's suggestion, or a default silly name). */
|
|
@@ -34256,12 +34318,29 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
34256
34318
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
34257
34319
|
broker: BrokerTuningSchema2.optional()
|
|
34258
34320
|
});
|
|
34321
|
+
var GoalLatestSchema2 = external_exports.object({
|
|
34322
|
+
from: external_exports.enum(["person", "agent"]),
|
|
34323
|
+
said: external_exports.string(),
|
|
34324
|
+
at: external_exports.string(),
|
|
34325
|
+
entryId: external_exports.string(),
|
|
34326
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
34327
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
34328
|
+
* none was. The Work row reads it before `said`. */
|
|
34329
|
+
summary: external_exports.string().optional(),
|
|
34330
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
34331
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
34332
|
+
titles: external_exports.array(external_exports.string()).optional(),
|
|
34333
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
34334
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
34335
|
+
line: external_exports.string().optional()
|
|
34336
|
+
});
|
|
34259
34337
|
var HistoryWorkSchema2 = external_exports.object({
|
|
34260
34338
|
id: external_exports.string(),
|
|
34261
34339
|
title: external_exports.string(),
|
|
34262
34340
|
state: external_exports.enum(["done", "cancelled"]),
|
|
34263
34341
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
34264
|
-
assignee: external_exports.string()
|
|
34342
|
+
assignee: external_exports.string(),
|
|
34343
|
+
latest: GoalLatestSchema2.nullable().optional()
|
|
34265
34344
|
});
|
|
34266
34345
|
var HistoryEntrySchema2 = external_exports.union([
|
|
34267
34346
|
external_exports.object({ at: external_exports.string(), card: InboxItemSchema2 }),
|
|
@@ -34329,7 +34408,7 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
34329
34408
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
34330
34409
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
34331
34410
|
version: external_exports.string().optional(),
|
|
34332
|
-
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
34411
|
+
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string(), models: external_exports.array(external_exports.string()).optional() })).optional(),
|
|
34333
34412
|
workspaces: external_exports.array(external_exports.string()).optional(),
|
|
34334
34413
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
34335
34414
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
@@ -34489,22 +34568,7 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
34489
34568
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
34490
34569
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
34491
34570
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
34492
|
-
latest:
|
|
34493
|
-
from: external_exports.enum(["person", "agent"]),
|
|
34494
|
-
said: external_exports.string(),
|
|
34495
|
-
at: external_exports.string(),
|
|
34496
|
-
entryId: external_exports.string(),
|
|
34497
|
-
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
34498
|
-
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
34499
|
-
* none was. The Work row reads it before `said`. */
|
|
34500
|
-
summary: external_exports.string().optional(),
|
|
34501
|
-
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
34502
|
-
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
34503
|
-
titles: external_exports.array(external_exports.string()).optional(),
|
|
34504
|
-
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
34505
|
-
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
34506
|
-
line: external_exports.string().optional()
|
|
34507
|
-
}).nullable().optional(),
|
|
34571
|
+
latest: GoalLatestSchema2.nullable().optional(),
|
|
34508
34572
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
34509
34573
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
34510
34574
|
* answer to a question, a voice note filed as work. Null when the only hands on it have been its
|
|
@@ -35004,12 +35068,13 @@ var LIMITS = episodes();
|
|
|
35004
35068
|
// src/paigy/bridge.ts
|
|
35005
35069
|
function railFor(opts) {
|
|
35006
35070
|
return {
|
|
35007
|
-
next: async () => {
|
|
35008
|
-
const waiting = await receive(opts);
|
|
35071
|
+
next: async (alreadyRead) => {
|
|
35072
|
+
const waiting = alreadyRead ?? await receive(opts);
|
|
35009
35073
|
const goalId = waiting.claimable?.goalId ?? waiting.review?.[0]?.goalId;
|
|
35010
35074
|
return goalId ? getGoals([goalId], opts).then(([goal]) => goal) : { state: "idle", message: "" };
|
|
35011
35075
|
},
|
|
35012
35076
|
read: (goalId) => getGoals([goalId], opts).then(([goal]) => goal),
|
|
35077
|
+
delivery: (deliveryId) => readDelivery(deliveryId, opts),
|
|
35013
35078
|
complete: async (goalId, revision) => {
|
|
35014
35079
|
const done = await manageGoals({ changes: [{ kind: "state", goalId, state: "completed", ...revision !== void 0 ? { revision } : {} }] }, opts);
|
|
35015
35080
|
const result = done.results[0];
|
|
@@ -35190,7 +35255,7 @@ function beat() {
|
|
|
35190
35255
|
}
|
|
35191
35256
|
var BEAT_REPORT_MS = 10 * 6e4;
|
|
35192
35257
|
var ended = (state) => state === "done" || state === "cancelled";
|
|
35193
|
-
function unheard(brief, state, since = null, after = -1, reviewSince) {
|
|
35258
|
+
function unheard(brief, state, since = null, after = -1, reviewSince, gathered) {
|
|
35194
35259
|
const seen = state.seen ??= /* @__PURE__ */ new Set();
|
|
35195
35260
|
const words = [];
|
|
35196
35261
|
const pendingAt = reviewSince ? Date.parse(reviewSince) : null;
|
|
@@ -35205,10 +35270,13 @@ function unheard(brief, state, since = null, after = -1, reviewSince) {
|
|
|
35205
35270
|
continue;
|
|
35206
35271
|
}
|
|
35207
35272
|
if (since !== null && Date.parse(e.createdAt) <= Date.parse(since)) continue;
|
|
35208
|
-
seen.add(e.entryId);
|
|
35209
35273
|
if (e.aboutId && seen.has(e.aboutId) && brief.answers?.some((a) => a.requestEntryId === e.aboutId)) continue;
|
|
35274
|
+
seen.add(e.entryId);
|
|
35210
35275
|
const text = entryWords(e).trim();
|
|
35211
|
-
if (text)
|
|
35276
|
+
if (text) {
|
|
35277
|
+
words.push(text);
|
|
35278
|
+
gathered?.add(e.entryId);
|
|
35279
|
+
}
|
|
35212
35280
|
}
|
|
35213
35281
|
if (pendingAt !== null) for (const answer of brief.answers ?? []) {
|
|
35214
35282
|
const key3 = `answer:${answer.decisionNeedId}`;
|
|
@@ -35249,12 +35317,28 @@ function named(goalId, title, brief) {
|
|
|
35249
35317
|
const name = line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
|
|
35250
35318
|
return name ? `On Goal "${name}" (${goalId}):` : `On Goal ${goalId}:`;
|
|
35251
35319
|
}
|
|
35252
|
-
async function gather(state, rail, held) {
|
|
35253
|
-
const looks = /* @__PURE__ */ new Map(
|
|
35320
|
+
async function gather(state, rail, held, alreadyRead, log) {
|
|
35321
|
+
const looks = /* @__PURE__ */ new Map();
|
|
35322
|
+
if (held && state.goal) looks.set(state.goal.id, { via: "held", brief: held });
|
|
35254
35323
|
const look2 = (goalId, via, title, since) => {
|
|
35255
35324
|
if (!looks.has(goalId)) looks.set(goalId, { via, ...title ? { title } : {}, ...since !== void 0 ? { since } : {} });
|
|
35256
35325
|
};
|
|
35257
|
-
const waiting = await rail.waiting().catch(() => null);
|
|
35326
|
+
const waiting = alreadyRead ?? await rail.waiting().catch(() => null);
|
|
35327
|
+
const direct = [];
|
|
35328
|
+
const staged = new Set(state.seen);
|
|
35329
|
+
const deliveries = /* @__PURE__ */ new Set();
|
|
35330
|
+
for (const d of waiting?.deliveries ?? []) {
|
|
35331
|
+
if (d.goalIds.length || deliveries.has(d.deliveryId) || state.acked?.get(`delivery:${d.deliveryId}`) === "handled") continue;
|
|
35332
|
+
deliveries.add(d.deliveryId);
|
|
35333
|
+
try {
|
|
35334
|
+
const full = await rail.delivery(d.deliveryId);
|
|
35335
|
+
const entries = full.entries.map((e) => ({ id: e.entryId, words: entryWords(e) }));
|
|
35336
|
+
if (!entries.length || entries.some((e) => !e.words.trim())) continue;
|
|
35337
|
+
direct.push({ eventId: d.deliveryId, entries, entryIds: entries.map((e) => e.id), words: [] });
|
|
35338
|
+
} catch (error) {
|
|
35339
|
+
log?.(`\u26A0 could not read instruction ${d.deliveryId.slice(0, 8)}: ${error.message}`);
|
|
35340
|
+
}
|
|
35341
|
+
}
|
|
35258
35342
|
const listed = /* @__PURE__ */ new Map();
|
|
35259
35343
|
const list = (goalId, eventId) => {
|
|
35260
35344
|
const ids = listed.get(goalId) ?? [];
|
|
@@ -35262,19 +35346,21 @@ async function gather(state, rail, held) {
|
|
|
35262
35346
|
listed.set(goalId, ids);
|
|
35263
35347
|
};
|
|
35264
35348
|
const announced = state.announced ??= /* @__PURE__ */ new Set();
|
|
35265
|
-
if (
|
|
35266
|
-
|
|
35267
|
-
|
|
35268
|
-
|
|
35269
|
-
|
|
35270
|
-
|
|
35271
|
-
|
|
35272
|
-
|
|
35273
|
-
|
|
35274
|
-
|
|
35275
|
-
|
|
35349
|
+
if (held) {
|
|
35350
|
+
if (waiting) {
|
|
35351
|
+
for (const id3 of announced) if (!(waiting.assigned ?? []).some((g) => g.goalId === id3)) announced.delete(id3);
|
|
35352
|
+
}
|
|
35353
|
+
for (const g of waiting?.assigned ?? []) if (g.goalId !== state.goal.id && !announced.has(g.goalId)) look2(g.goalId, "assigned", g.title);
|
|
35354
|
+
for (const g of waiting?.review ?? []) {
|
|
35355
|
+
list(g.goalId, g.eventId);
|
|
35356
|
+
look2(g.goalId, "waiting work", g.title);
|
|
35357
|
+
}
|
|
35358
|
+
for (const d of waiting?.deliveries ?? []) for (const id3 of d.goalIds) {
|
|
35359
|
+
list(id3, d.deliveryId);
|
|
35360
|
+
look2(id3, "waiting work");
|
|
35361
|
+
}
|
|
35276
35362
|
}
|
|
35277
|
-
const moved = movedBelow(held, state);
|
|
35363
|
+
const moved = held ? movedBelow(held, state) : [];
|
|
35278
35364
|
for (const { goal, since } of moved) look2(goal.goalId, "subtree", goal.title, since);
|
|
35279
35365
|
const heard = [];
|
|
35280
35366
|
const read3 = /* @__PURE__ */ new Set();
|
|
@@ -35291,10 +35377,15 @@ async function gather(state, rail, held) {
|
|
|
35291
35377
|
read3.add(goalId);
|
|
35292
35378
|
const fresh = !brief.reviewSince && (found.via === "waiting work" || found.via === "assigned") ? sinceTheWork(brief) : { after: -1 };
|
|
35293
35379
|
if (found.via === "assigned") announced.add(goalId);
|
|
35294
|
-
heard.push({ goalId, ...found, brief, words: unheard(brief, state, brief.reviewSince ? null : since ?? null, fresh.after, brief.reviewSince), ...fresh.note ? { note: fresh.note } : {} });
|
|
35380
|
+
heard.push({ goalId, ...found, brief, words: unheard(brief, state, brief.reviewSince ? null : since ?? null, fresh.after, brief.reviewSince, staged), ...fresh.note ? { note: fresh.note } : {} });
|
|
35295
35381
|
}
|
|
35382
|
+
for (const d of direct) d.words = d.entries.filter((e) => {
|
|
35383
|
+
if (staged.has(e.id)) return false;
|
|
35384
|
+
staged.add(e.id);
|
|
35385
|
+
return true;
|
|
35386
|
+
}).map((e) => e.words);
|
|
35296
35387
|
for (const { goal, since } of moved) if (read3.has(goal.goalId)) state.below.goals.set(goal.goalId, { since, at: goal.updatedAt ?? null });
|
|
35297
|
-
return { heard, listed };
|
|
35388
|
+
return { heard, listed, direct };
|
|
35298
35389
|
}
|
|
35299
35390
|
var GIVEN = "This Goal was given to you, and nobody has started it. Carry on with what you're doing unless it changes that; your first write to it (a contact update naming it) starts it when you take it up.";
|
|
35300
35391
|
var says = (h) => h.words.length > 0 || h.via === "assigned";
|
|
@@ -35312,12 +35403,21 @@ var block = (h) => [
|
|
|
35312
35403
|
].join("\n\n");
|
|
35313
35404
|
var short = (ids) => ids.map((id3) => id3.slice(0, 8)).join(", ");
|
|
35314
35405
|
var given = (heard) => heard.filter((h) => h.via === "assigned").map((h) => h.goalId);
|
|
35315
|
-
async function hear(session, state, rail, held, opts) {
|
|
35406
|
+
async function hear(session, state, rail, held, opts, waiting) {
|
|
35316
35407
|
const log = opts?.log;
|
|
35317
|
-
const { heard, listed } = await gather(state, rail, held);
|
|
35408
|
+
const { heard, listed, direct } = await gather(state, rail, held, waiting, log);
|
|
35318
35409
|
const said = heard.filter(says);
|
|
35319
|
-
|
|
35320
|
-
|
|
35410
|
+
const words = [...direct.flatMap((d) => d.words), ...said.map(block)];
|
|
35411
|
+
if (words.length) {
|
|
35412
|
+
const text = words.join("\n\n");
|
|
35413
|
+
try {
|
|
35414
|
+
await send2(session, state, text);
|
|
35415
|
+
} catch (error) {
|
|
35416
|
+
(state.noted ??= []).push({ name: "", goals: said.map((h) => h.goalId), words: text, eventIds: direct.map((d) => d.eventId), at: Date.now(), cause: "read" });
|
|
35417
|
+
for (const d of direct) for (const id3 of d.entryIds) (state.seen ??= /* @__PURE__ */ new Set()).add(id3);
|
|
35418
|
+
throw error;
|
|
35419
|
+
}
|
|
35420
|
+
for (const d of direct) for (const id3 of d.entryIds) (state.seen ??= /* @__PURE__ */ new Set()).add(id3);
|
|
35321
35421
|
const worded = said.filter((h) => h.words.length);
|
|
35322
35422
|
if (worded.length) log?.(`\u25B8 typed the person's words from ${worded.map((h) => `${h.goalId.slice(0, 8)} (via ${h.via})`).join(", ")}`);
|
|
35323
35423
|
const goals = given(said);
|
|
@@ -35326,18 +35426,21 @@ async function hear(session, state, rail, held, opts) {
|
|
|
35326
35426
|
if (opts?.tally) opts.tally.announced += goals.length;
|
|
35327
35427
|
}
|
|
35328
35428
|
}
|
|
35429
|
+
await acknowledgeDirect(direct.map((d) => d.eventId), state, rail, log);
|
|
35329
35430
|
for (const h of heard) {
|
|
35330
35431
|
const events = listed.get(h.goalId);
|
|
35331
35432
|
if (events) await acknowledge2(h.goalId, h.brief, events, state, rail, log);
|
|
35332
35433
|
}
|
|
35434
|
+
return direct.length > 0 || said.length > 0;
|
|
35333
35435
|
}
|
|
35334
35436
|
var NOTED = "New from the person while you work. Carry on unless it changes what you're doing: it comes back to you as your next turn when this one ends.";
|
|
35335
35437
|
var SEEN_MID_TURN = "While you worked, the person said this, and you saw it mid-task. Act on it now, unless you already have.";
|
|
35336
|
-
async function note(state, rail, held, inbox, how) {
|
|
35337
|
-
const { heard } = await gather(state, rail, held);
|
|
35438
|
+
async function note(state, rail, held, inbox, how, waiting) {
|
|
35439
|
+
const { heard, direct } = await gather(state, rail, held, waiting, how.log);
|
|
35338
35440
|
const said = heard.filter(says);
|
|
35339
|
-
|
|
35340
|
-
|
|
35441
|
+
const parts = [...direct.flatMap((d) => d.words), ...said.map(block)];
|
|
35442
|
+
if (!parts.length) return 0;
|
|
35443
|
+
const words = parts.join("\n\n");
|
|
35341
35444
|
const goals = said.map((h) => h.goalId);
|
|
35342
35445
|
const assigned = given(said);
|
|
35343
35446
|
if (how.tally) how.tally.announced += assigned.length;
|
|
@@ -35351,7 +35454,8 @@ ${words}`);
|
|
|
35351
35454
|
} catch (error) {
|
|
35352
35455
|
how.log?.(`\u26A0 could not note mid-turn (${error.message}): ${short(goals)} wait for the turn's end`);
|
|
35353
35456
|
}
|
|
35354
|
-
(state.noted ??= []).push({ name, goals, words, at: how.now(), cause: how.cause === "wake" ? "wake" : "read" });
|
|
35457
|
+
(state.noted ??= []).push({ name, goals, words, eventIds: direct.map((d) => d.eventId), at: how.now(), cause: how.cause === "wake" ? "wake" : "read" });
|
|
35458
|
+
for (const d of direct) for (const id3 of d.entryIds) (state.seen ??= /* @__PURE__ */ new Set()).add(id3);
|
|
35355
35459
|
return 1;
|
|
35356
35460
|
}
|
|
35357
35461
|
function receipts(state, inbox, log) {
|
|
@@ -35366,15 +35470,33 @@ function receipts(state, inbox, log) {
|
|
|
35366
35470
|
}
|
|
35367
35471
|
return seen;
|
|
35368
35472
|
}
|
|
35369
|
-
function settle(session, state, inbox, log) {
|
|
35473
|
+
async function settle(session, state, rail, inbox, log) {
|
|
35370
35474
|
const batches = state.noted ?? [];
|
|
35371
|
-
state.noted = [];
|
|
35372
35475
|
const seen = [];
|
|
35373
35476
|
const unseen = [];
|
|
35374
35477
|
for (const b of batches) (b.name && inbox?.settle(b.name) === "taken" ? seen : unseen).push(b);
|
|
35375
|
-
send2(session, state, [...seen.length ? [SEEN_MID_TURN, ...seen.map((b) => b.words)] : [], ...unseen.map((b) => b.words)].join("\n\n"));
|
|
35478
|
+
await send2(session, state, [...seen.length ? [SEEN_MID_TURN, ...seen.map((b) => b.words)] : [], ...unseen.map((b) => b.words)].join("\n\n"));
|
|
35479
|
+
state.noted = [];
|
|
35480
|
+
await acknowledgeDirect(batches.flatMap((b) => b.eventIds ?? []), state, rail, log);
|
|
35376
35481
|
log?.(`\u25B8 typed the person's words from ${short([...new Set(batches.flatMap((b) => b.goals))])} (noted mid-turn: ${seen.length} seen by the hook, ${unseen.length} not)`);
|
|
35377
35482
|
}
|
|
35483
|
+
async function acknowledgeDirect(events, state, rail, log) {
|
|
35484
|
+
const acked = state.acked ??= /* @__PURE__ */ new Map();
|
|
35485
|
+
const ids = [...new Set(events)].filter((id3) => acked.get(`delivery:${id3}`) !== "handled");
|
|
35486
|
+
for (let at = 0; at < ids.length; at += 100) {
|
|
35487
|
+
const batch = ids.slice(at, at + 100);
|
|
35488
|
+
try {
|
|
35489
|
+
const results = await rail.ack(batch);
|
|
35490
|
+
for (const id3 of batch) {
|
|
35491
|
+
const result = results.find((r) => r.eventId === id3);
|
|
35492
|
+
if (result?.acknowledged) acked.set(`delivery:${id3}`, "handled");
|
|
35493
|
+
else log?.(`\u26A0 could not acknowledge ${id3.slice(0, 8)}: ${result?.refused ?? "no result"} \u2014 its words are typed in; it stays listed until acknowledged`);
|
|
35494
|
+
}
|
|
35495
|
+
} catch (error) {
|
|
35496
|
+
log?.(`\u26A0 could not acknowledge instructions: ${error.message} \u2014 their words are typed in; they stay listed until acknowledged`);
|
|
35497
|
+
}
|
|
35498
|
+
}
|
|
35499
|
+
}
|
|
35378
35500
|
async function acknowledge2(goalId, brief, events, state, rail, log) {
|
|
35379
35501
|
const ids = [.../* @__PURE__ */ new Set([...brief.reviewEventId ? [brief.reviewEventId] : [], ...events])];
|
|
35380
35502
|
if (!ids.length) return;
|
|
@@ -35400,17 +35522,23 @@ async function step(session, state, rail, opts) {
|
|
|
35400
35522
|
state.carryOn = true;
|
|
35401
35523
|
}
|
|
35402
35524
|
await look(session, state, rail, opts);
|
|
35403
|
-
if (state.carryOn && state.resting && !state.asks?.size) send2(session, state, "");
|
|
35525
|
+
if (state.carryOn && state.resting && !state.asks?.size) await send2(session, state, "");
|
|
35404
35526
|
}
|
|
35405
35527
|
async function look(session, state, rail, opts) {
|
|
35406
35528
|
if (opts?.inbox) {
|
|
35407
35529
|
const seen = receipts(state, opts.inbox, opts.log);
|
|
35408
35530
|
if (opts.tally) opts.tally.seen += seen;
|
|
35409
35531
|
}
|
|
35410
|
-
if (state.resting && state.noted?.length && !state.asks?.size) return settle(session, state, opts?.inbox, opts?.log);
|
|
35532
|
+
if (state.resting && state.noted?.length && !state.asks?.size) return settle(session, state, rail, opts?.inbox, opts?.log);
|
|
35411
35533
|
if (!state.goal) {
|
|
35534
|
+
if (state.asks?.size || !state.resting && !opts?.inbox) return;
|
|
35535
|
+
const waiting = await rail.waiting();
|
|
35536
|
+
if (waiting.deliveries.some((d) => !d.goalIds.length && state.acked?.get(`delivery:${d.deliveryId}`) !== "handled")) {
|
|
35537
|
+
const handled = state.resting ? await hear(session, state, rail, void 0, opts, waiting) : await note(state, rail, void 0, opts.inbox, { cause: opts?.cause ?? "safety", now: opts?.now ?? Date.now, log: opts?.log, tally: opts?.tally }, waiting);
|
|
35538
|
+
if (handled) return;
|
|
35539
|
+
}
|
|
35412
35540
|
if (!state.resting) return;
|
|
35413
|
-
let brief2 = await rail.next();
|
|
35541
|
+
let brief2 = await rail.next(waiting);
|
|
35414
35542
|
if (!brief2.goalId || brief2.revision === void 0) {
|
|
35415
35543
|
if (opts?.initialPrompt?.trim()) {
|
|
35416
35544
|
const prompt = opts.initialPrompt.trim();
|
|
@@ -35419,7 +35547,7 @@ async function look(session, state, rail, opts) {
|
|
|
35419
35547
|
brief2 = await rail.next();
|
|
35420
35548
|
if (!brief2.goalId || brief2.revision === void 0) {
|
|
35421
35549
|
state.goal = { id: admitted.goalId, revision: admitted.revision };
|
|
35422
|
-
send2(session, state, [HARNESS_INSTRUCTION, named(admitted.goalId, void 0, { outcome: prompt }), prompt].join("\n\n"));
|
|
35550
|
+
await send2(session, state, [HARNESS_INSTRUCTION, named(admitted.goalId, void 0, { outcome: prompt }), prompt].join("\n\n"));
|
|
35423
35551
|
return;
|
|
35424
35552
|
}
|
|
35425
35553
|
} else {
|
|
@@ -35437,7 +35565,7 @@ async function look(session, state, rail, opts) {
|
|
|
35437
35565
|
const left = brief2.earlier ? brief2.earlier.omitted + brief2.earlier.shortened : 0;
|
|
35438
35566
|
const earlier = left ? `(${left} earlier entries on this Goal are left out or shortened; get_goals with history: true reads them in full.)` : "";
|
|
35439
35567
|
const fullText = [outcome2, earlier, ...said].filter(Boolean).join("\n\n");
|
|
35440
|
-
send2(session, state, [HARNESS_INSTRUCTION, named(brief2.goalId, void 0, brief2), fullText].filter(Boolean).join("\n\n"));
|
|
35568
|
+
await send2(session, state, [HARNESS_INSTRUCTION, named(brief2.goalId, void 0, brief2), fullText].filter(Boolean).join("\n\n"));
|
|
35441
35569
|
if (ended(brief2.state)) await acknowledge2(brief2.goalId, brief2, [], state, rail, opts?.log);
|
|
35442
35570
|
return;
|
|
35443
35571
|
}
|
|
@@ -35466,19 +35594,27 @@ async function look(session, state, rail, opts) {
|
|
|
35466
35594
|
}
|
|
35467
35595
|
await hear(session, state, rail, brief, { ...opts?.log ? { log: opts.log } : {}, ...opts?.tally ? { tally: opts.tally } : {} });
|
|
35468
35596
|
}
|
|
35469
|
-
function send2(session, state, text) {
|
|
35470
|
-
state.resting = false;
|
|
35597
|
+
async function send2(session, state, text) {
|
|
35471
35598
|
let words = text;
|
|
35472
35599
|
if (state.carryOn) {
|
|
35473
|
-
state.carryOn = false;
|
|
35474
|
-
state.carriedOn = true;
|
|
35475
35600
|
const owed = state.owed;
|
|
35476
|
-
state.owed = void 0;
|
|
35477
35601
|
words = [owed, text].filter(Boolean).join("\n\n");
|
|
35478
35602
|
text = [CARRY_ON, ...owed ? [OWED, owed] : [], text].filter(Boolean).join("\n\n");
|
|
35479
35603
|
}
|
|
35604
|
+
const resting = state.resting;
|
|
35605
|
+
state.resting = false;
|
|
35606
|
+
try {
|
|
35607
|
+
await session.send(text);
|
|
35608
|
+
} catch (error) {
|
|
35609
|
+
state.resting = resting;
|
|
35610
|
+
throw error;
|
|
35611
|
+
}
|
|
35480
35612
|
state.typed = words || void 0;
|
|
35481
|
-
|
|
35613
|
+
if (state.carryOn) {
|
|
35614
|
+
state.carryOn = false;
|
|
35615
|
+
state.carriedOn = true;
|
|
35616
|
+
state.owed = void 0;
|
|
35617
|
+
}
|
|
35482
35618
|
}
|
|
35483
35619
|
async function pump(session, state, rail, opts) {
|
|
35484
35620
|
const clock5 = opts.beat ?? beat();
|
|
@@ -35618,9 +35754,6 @@ ${event.message}`, state, rail, { log: opts.log });
|
|
|
35618
35754
|
opts.log("\u25A0 stopped");
|
|
35619
35755
|
}
|
|
35620
35756
|
return {
|
|
35621
|
-
send(text) {
|
|
35622
|
-
session?.send(text);
|
|
35623
|
-
},
|
|
35624
35757
|
working: () => running && state.resting !== true,
|
|
35625
35758
|
// Paused by a usage limit, the activity line says so: the quiet place the pause is shown.
|
|
35626
35759
|
tail: () => state.paused ? [pausedLine(state.paused)] : [...tail],
|
|
@@ -35631,6 +35764,8 @@ ${event.message}`, state, rail, { log: opts.log });
|
|
|
35631
35764
|
|
|
35632
35765
|
// src/host.ts
|
|
35633
35766
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
35767
|
+
import { execSync } from "child_process";
|
|
35768
|
+
import { randomBytes } from "crypto";
|
|
35634
35769
|
import { homedir as homedir8 } from "os";
|
|
35635
35770
|
import { join as join10 } from "path";
|
|
35636
35771
|
|
|
@@ -36048,7 +36183,7 @@ var claimedRun = (sessionId2) => {
|
|
|
36048
36183
|
const slot = sessionSlot(sessionId2);
|
|
36049
36184
|
return { slot, key: runKey(slot) };
|
|
36050
36185
|
};
|
|
36051
|
-
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
36186
|
+
var launchRun = (spec) => ({ session: spec.sessionId, model: spec.model });
|
|
36052
36187
|
var BOUND_ELSEWHERE_MS = 10 * 6e4;
|
|
36053
36188
|
var boundElsewhere = /* @__PURE__ */ new Map();
|
|
36054
36189
|
var BOUND_SKIP = "is bound to the session it was paired in, which this host cannot name \u2014 run `paigy-harness handoff` as it, in its folder, to record it";
|
|
@@ -36156,7 +36291,7 @@ function startHost(opts) {
|
|
|
36156
36291
|
});
|
|
36157
36292
|
void heartbeat({
|
|
36158
36293
|
version: VERSION,
|
|
36159
|
-
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
36294
|
+
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status, models: a.models })),
|
|
36160
36295
|
workspaces: listWorkspaces(opts.wsDeps),
|
|
36161
36296
|
// The repos inside them, so a session started for work on one opens IN it (Goal 26982211).
|
|
36162
36297
|
repos: listRepos(opts.wsDeps)
|
|
@@ -36169,6 +36304,17 @@ function startHost(opts) {
|
|
|
36169
36304
|
const specs = await claimSessions(asHost).catch(() => []);
|
|
36170
36305
|
for (const spec of specs) {
|
|
36171
36306
|
const label = spec.name ?? spec.harness;
|
|
36307
|
+
let workspace = spec.workspace;
|
|
36308
|
+
if (spec.worktree) {
|
|
36309
|
+
const suffix = randomBytes(4).toString("hex");
|
|
36310
|
+
const newWt = `/tmp/paigy-${spec.harness}-${suffix}`;
|
|
36311
|
+
try {
|
|
36312
|
+
execSync(`git worktree add ${newWt} -b paigy-${suffix}`, { cwd: workspace, stdio: "ignore" });
|
|
36313
|
+
workspace = newWt;
|
|
36314
|
+
} catch (e) {
|
|
36315
|
+
opts.log(`[${label}] Failed to create worktree, falling back to ${workspace}`);
|
|
36316
|
+
}
|
|
36317
|
+
}
|
|
36172
36318
|
const { slot, key: key3 } = claimedRun(spec.sessionId);
|
|
36173
36319
|
const log = (line) => opts.log(`[${label}] ${line}`);
|
|
36174
36320
|
try {
|
|
@@ -36186,7 +36332,7 @@ function startHost(opts) {
|
|
|
36186
36332
|
}
|
|
36187
36333
|
const run = runHarness({
|
|
36188
36334
|
harness: spec.harness,
|
|
36189
|
-
cwd:
|
|
36335
|
+
cwd: workspace,
|
|
36190
36336
|
mode: "bypass",
|
|
36191
36337
|
prompt: "",
|
|
36192
36338
|
token: spec.token,
|
|
@@ -36641,7 +36787,7 @@ async function main() {
|
|
|
36641
36787
|
}
|
|
36642
36788
|
if (parsed.enableTools) {
|
|
36643
36789
|
try {
|
|
36644
|
-
|
|
36790
|
+
execSync2(`${ENABLE_TOOLS_COMMAND} --scope ${parsed.scope}`, { stdio: "inherit", shell: "/bin/sh" });
|
|
36645
36791
|
} catch {
|
|
36646
36792
|
console.error(`Couldn't run it \u2014 try directly: ${ENABLE_TOOLS_COMMAND} --scope ${parsed.scope}`);
|
|
36647
36793
|
process.exit(1);
|
|
@@ -36701,7 +36847,7 @@ async function main() {
|
|
|
36701
36847
|
}
|
|
36702
36848
|
if (process.platform === "darwin") {
|
|
36703
36849
|
try {
|
|
36704
|
-
|
|
36850
|
+
execSync2(`${JSON.stringify(process.execPath)} ${JSON.stringify(process.argv[1])} service`, { stdio: "inherit", shell: "/bin/sh" });
|
|
36705
36851
|
} catch {
|
|
36706
36852
|
console.log("service install failed \u2014 run `paigy-harness host` to host in the foreground");
|
|
36707
36853
|
}
|
|
@@ -36709,7 +36855,7 @@ async function main() {
|
|
|
36709
36855
|
for (const t of MCP_WIRES) {
|
|
36710
36856
|
if (!which(t.cli)) continue;
|
|
36711
36857
|
try {
|
|
36712
|
-
|
|
36858
|
+
execSync2(t.wire, { stdio: "ignore", shell: "/bin/sh" });
|
|
36713
36859
|
if (t.cli === "codex") forwardCodexThreadId();
|
|
36714
36860
|
console.log(`\u2713 paigy wired for ${t.cli} \u2014 each session hatches its own identity (restart open sessions)`);
|
|
36715
36861
|
} catch (e) {
|
|
@@ -36718,7 +36864,7 @@ async function main() {
|
|
|
36718
36864
|
}
|
|
36719
36865
|
if (which("claude")) {
|
|
36720
36866
|
try {
|
|
36721
|
-
|
|
36867
|
+
execSync2(ENABLE_TOOLS_COMMAND, { stdio: "ignore", shell: "/bin/sh" });
|
|
36722
36868
|
console.log("\u2713 Paigy's tools allowlisted in Claude Code (they won't prompt each time)");
|
|
36723
36869
|
} catch {
|
|
36724
36870
|
console.log(` allowlist it later with: paigy-harness enable-tools`);
|
|
@@ -36791,7 +36937,7 @@ async function main() {
|
|
|
36791
36937
|
mkdirSync4(dir, { recursive: true });
|
|
36792
36938
|
const path = join11(dir, `${SERVICE_LABEL}.plist`);
|
|
36793
36939
|
writeFileSync5(path, plist);
|
|
36794
|
-
|
|
36940
|
+
execSync2(`launchctl unload ${path} 2>/dev/null; launchctl load ${path}`, { shell: "/bin/sh" });
|
|
36795
36941
|
console.log(`\u2713 host installed as a login service (${path}) \u2014 logs at ~/.paigy/host.log`);
|
|
36796
36942
|
console.log(" file limit raised to 65536 for the host and every session it spawns");
|
|
36797
36943
|
return;
|