@paigy/harness 0.3.41 → 0.3.43
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 +307 -161
- package/dist/main.js +302 -156
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -4582,7 +4582,7 @@ import { readFileSync as readFileSync7 } from "fs";
|
|
|
4582
4582
|
import { fileURLToPath } from "url";
|
|
4583
4583
|
import { dirname as dirname4, join as join12 } from "path";
|
|
4584
4584
|
|
|
4585
|
-
//
|
|
4585
|
+
// ../../../../../../private/tmp/paigy-draft-reaches/packages/sdk/dist/index.js
|
|
4586
4586
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
4587
4587
|
import { execFileSync } from "child_process";
|
|
4588
4588
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -8633,7 +8633,7 @@ var coerce = {
|
|
|
8633
8633
|
};
|
|
8634
8634
|
var NEVER = INVALID;
|
|
8635
8635
|
|
|
8636
|
-
//
|
|
8636
|
+
// ../../../../../../private/tmp/paigy-draft-reaches/packages/sdk/dist/index.js
|
|
8637
8637
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
8638
8638
|
import { homedir as homedir2 } from "os";
|
|
8639
8639
|
import { join as join2 } from "path";
|
|
@@ -11041,6 +11041,31 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
11041
11041
|
ifSilent: external_exports.boolean().optional()
|
|
11042
11042
|
});
|
|
11043
11043
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
11044
|
+
var CallQuestionSchema = external_exports.object({
|
|
11045
|
+
id: external_exports.string(),
|
|
11046
|
+
entryId: external_exports.string(),
|
|
11047
|
+
title: external_exports.string(),
|
|
11048
|
+
state: external_exports.enum(["open", "answered"]),
|
|
11049
|
+
answer: external_exports.string().nullable(),
|
|
11050
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
11051
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
11052
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
11053
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
11054
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
11055
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
11056
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
11057
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
11058
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
11059
|
+
options: external_exports.array(OptionSchema).optional(),
|
|
11060
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
11061
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
11062
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
11063
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
11064
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
11065
|
+
choices: external_exports.array(OptionSchema).optional(),
|
|
11066
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
11067
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
11068
|
+
});
|
|
11044
11069
|
var InboxItemSchema = external_exports.object({
|
|
11045
11070
|
id: external_exports.string(),
|
|
11046
11071
|
tokenId: external_exports.string().optional(),
|
|
@@ -11082,31 +11107,10 @@ var InboxItemSchema = external_exports.object({
|
|
|
11082
11107
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
11083
11108
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
11084
11109
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
11085
|
-
questions: external_exports.array(
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
state: external_exports.enum(["open", "answered"]),
|
|
11090
|
-
answer: external_exports.string().nullable(),
|
|
11091
|
-
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
11092
|
-
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
11093
|
-
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
11094
|
-
* author is not an agent this account holds (unpaired since, or a person). */
|
|
11095
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
11096
|
-
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
11097
|
-
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
11098
|
-
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
11099
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
11100
|
-
options: external_exports.array(OptionSchema).optional(),
|
|
11101
|
-
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
11102
|
-
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
11103
|
-
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
11104
|
-
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
11105
|
-
* preview layout for any `options`, and must see exactly what it did. */
|
|
11106
|
-
choices: external_exports.array(OptionSchema).optional(),
|
|
11107
|
-
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
11108
|
-
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
11109
|
-
})).optional(),
|
|
11110
|
+
questions: external_exports.array(CallQuestionSchema).optional(),
|
|
11111
|
+
/** The same call carrying order, including request Entries with no DecisionNeed as reports.
|
|
11112
|
+
* Additive: questions stays decision-only for every other surface and older bundles. */
|
|
11113
|
+
callItems: external_exports.array(CallQuestionSchema.extend({ state: external_exports.enum(["open", "answered", "report"]) })).optional(),
|
|
11110
11114
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
11111
11115
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
11112
11116
|
* agent's suggestion, or a default silly name). */
|
|
@@ -11305,12 +11309,29 @@ var UserSettingsSchema = external_exports.object({
|
|
|
11305
11309
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
11306
11310
|
broker: BrokerTuningSchema.optional()
|
|
11307
11311
|
});
|
|
11312
|
+
var GoalLatestSchema = external_exports.object({
|
|
11313
|
+
from: external_exports.enum(["person", "agent"]),
|
|
11314
|
+
said: external_exports.string(),
|
|
11315
|
+
at: external_exports.string(),
|
|
11316
|
+
entryId: external_exports.string(),
|
|
11317
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
11318
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
11319
|
+
* none was. The Work row reads it before `said`. */
|
|
11320
|
+
summary: external_exports.string().optional(),
|
|
11321
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
11322
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
11323
|
+
titles: external_exports.array(external_exports.string()).optional(),
|
|
11324
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
11325
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
11326
|
+
line: external_exports.string().optional()
|
|
11327
|
+
});
|
|
11308
11328
|
var HistoryWorkSchema = external_exports.object({
|
|
11309
11329
|
id: external_exports.string(),
|
|
11310
11330
|
title: external_exports.string(),
|
|
11311
11331
|
state: external_exports.enum(["done", "cancelled"]),
|
|
11312
11332
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
11313
|
-
assignee: external_exports.string()
|
|
11333
|
+
assignee: external_exports.string(),
|
|
11334
|
+
latest: GoalLatestSchema.nullable().optional()
|
|
11314
11335
|
});
|
|
11315
11336
|
var HistoryEntrySchema = external_exports.union([
|
|
11316
11337
|
external_exports.object({ at: external_exports.string(), card: InboxItemSchema }),
|
|
@@ -11378,7 +11399,7 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
11378
11399
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
11379
11400
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
11380
11401
|
version: external_exports.string().optional(),
|
|
11381
|
-
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
11402
|
+
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(),
|
|
11382
11403
|
workspaces: external_exports.array(external_exports.string()).optional(),
|
|
11383
11404
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
11384
11405
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
@@ -11538,22 +11559,7 @@ var QueueItemSchema = external_exports.object({
|
|
|
11538
11559
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
11539
11560
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
11540
11561
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
11541
|
-
latest:
|
|
11542
|
-
from: external_exports.enum(["person", "agent"]),
|
|
11543
|
-
said: external_exports.string(),
|
|
11544
|
-
at: external_exports.string(),
|
|
11545
|
-
entryId: external_exports.string(),
|
|
11546
|
-
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
11547
|
-
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
11548
|
-
* none was. The Work row reads it before `said`. */
|
|
11549
|
-
summary: external_exports.string().optional(),
|
|
11550
|
-
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
11551
|
-
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
11552
|
-
titles: external_exports.array(external_exports.string()).optional(),
|
|
11553
|
-
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
11554
|
-
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
11555
|
-
line: external_exports.string().optional()
|
|
11556
|
-
}).nullable().optional(),
|
|
11562
|
+
latest: GoalLatestSchema.nullable().optional(),
|
|
11557
11563
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
11558
11564
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
11559
11565
|
* answer to a question, a voice note filed as work. Null when the only hands on it have been its
|
|
@@ -12397,7 +12403,8 @@ var CATALOG = [
|
|
|
12397
12403
|
authProbe: ["claude", "auth", "status"],
|
|
12398
12404
|
loginHint: "Run `claude` once in a terminal and sign in.",
|
|
12399
12405
|
installCli: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
12400
|
-
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
12406
|
+
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp",
|
|
12407
|
+
models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"]
|
|
12401
12408
|
},
|
|
12402
12409
|
{
|
|
12403
12410
|
name: "codex",
|
|
@@ -12407,7 +12414,8 @@ var CATALOG = [
|
|
|
12407
12414
|
authProbe: ["codex", "login", "status"],
|
|
12408
12415
|
loginHint: "Run `codex login` to authenticate.",
|
|
12409
12416
|
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
12410
|
-
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
12417
|
+
installAdapter: "npm install -g @agentclientprotocol/codex-acp",
|
|
12418
|
+
models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"]
|
|
12411
12419
|
},
|
|
12412
12420
|
{
|
|
12413
12421
|
name: "agy",
|
|
@@ -12417,7 +12425,8 @@ var CATALOG = [
|
|
|
12417
12425
|
adapter: "paigy-agy-acp",
|
|
12418
12426
|
authProbe: ["agy", "help"],
|
|
12419
12427
|
loginHint: "Install and set up Antigravity (`agy`).",
|
|
12420
|
-
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
12428
|
+
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash",
|
|
12429
|
+
models: ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-pro-exp"]
|
|
12421
12430
|
}
|
|
12422
12431
|
];
|
|
12423
12432
|
function probeDirs(home = homedir3()) {
|
|
@@ -12453,7 +12462,7 @@ function runProbe(argv) {
|
|
|
12453
12462
|
function detect(entry, deps = {}) {
|
|
12454
12463
|
const find = deps.which ?? which;
|
|
12455
12464
|
const probe = deps.probe ?? runProbe;
|
|
12456
|
-
const base = { name: entry.name, label: entry.label };
|
|
12465
|
+
const base = { name: entry.name, label: entry.label, models: entry.models };
|
|
12457
12466
|
const cliPath = find(entry.cli);
|
|
12458
12467
|
if (!cliPath) return { ...base, status: "missing", hint: `${entry.label} is not installed.`, install: entry.installCli };
|
|
12459
12468
|
const adapterPath = entry.adapter ? find(entry.adapter) : null;
|
|
@@ -13757,7 +13766,7 @@ var zodToJsonSchema2 = (schema, options) => {
|
|
|
13757
13766
|
return combined;
|
|
13758
13767
|
};
|
|
13759
13768
|
|
|
13760
|
-
//
|
|
13769
|
+
// ../../../../../../private/tmp/paigy-draft-reaches/packages/schema/dist/index.js
|
|
13761
13770
|
var OPTIONS_MIN2 = 2;
|
|
13762
13771
|
var OPTIONS_MAX2 = 6;
|
|
13763
13772
|
var OPTION_LABEL_MAX2 = 50;
|
|
@@ -14852,6 +14861,31 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
14852
14861
|
ifSilent: external_exports.boolean().optional()
|
|
14853
14862
|
});
|
|
14854
14863
|
var CLAIM_STALE_MS2 = 30 * 6e4;
|
|
14864
|
+
var CallQuestionSchema2 = external_exports.object({
|
|
14865
|
+
id: external_exports.string(),
|
|
14866
|
+
entryId: external_exports.string(),
|
|
14867
|
+
title: external_exports.string(),
|
|
14868
|
+
state: external_exports.enum(["open", "answered"]),
|
|
14869
|
+
answer: external_exports.string().nullable(),
|
|
14870
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
14871
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
14872
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
14873
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
14874
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
14875
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
14876
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
14877
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
14878
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
14879
|
+
options: external_exports.array(OptionSchema2).optional(),
|
|
14880
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
14881
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
14882
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
14883
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
14884
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
14885
|
+
choices: external_exports.array(OptionSchema2).optional(),
|
|
14886
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
14887
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
14888
|
+
});
|
|
14855
14889
|
var InboxItemSchema2 = external_exports.object({
|
|
14856
14890
|
id: external_exports.string(),
|
|
14857
14891
|
tokenId: external_exports.string().optional(),
|
|
@@ -14893,31 +14927,10 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
14893
14927
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
14894
14928
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
14895
14929
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
14896
|
-
questions: external_exports.array(
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
state: external_exports.enum(["open", "answered"]),
|
|
14901
|
-
answer: external_exports.string().nullable(),
|
|
14902
|
-
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
14903
|
-
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
14904
|
-
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
14905
|
-
* author is not an agent this account holds (unpaired since, or a person). */
|
|
14906
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
14907
|
-
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
14908
|
-
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
14909
|
-
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
14910
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
14911
|
-
options: external_exports.array(OptionSchema2).optional(),
|
|
14912
|
-
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
14913
|
-
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
14914
|
-
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
14915
|
-
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
14916
|
-
* preview layout for any `options`, and must see exactly what it did. */
|
|
14917
|
-
choices: external_exports.array(OptionSchema2).optional(),
|
|
14918
|
-
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
14919
|
-
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
14920
|
-
})).optional(),
|
|
14930
|
+
questions: external_exports.array(CallQuestionSchema2).optional(),
|
|
14931
|
+
/** The same call carrying order, including request Entries with no DecisionNeed as reports.
|
|
14932
|
+
* Additive: questions stays decision-only for every other surface and older bundles. */
|
|
14933
|
+
callItems: external_exports.array(CallQuestionSchema2.extend({ state: external_exports.enum(["open", "answered", "report"]) })).optional(),
|
|
14921
14934
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
14922
14935
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
14923
14936
|
* agent's suggestion, or a default silly name). */
|
|
@@ -15116,12 +15129,29 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
15116
15129
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
15117
15130
|
broker: BrokerTuningSchema2.optional()
|
|
15118
15131
|
});
|
|
15132
|
+
var GoalLatestSchema2 = external_exports.object({
|
|
15133
|
+
from: external_exports.enum(["person", "agent"]),
|
|
15134
|
+
said: external_exports.string(),
|
|
15135
|
+
at: external_exports.string(),
|
|
15136
|
+
entryId: external_exports.string(),
|
|
15137
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
15138
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
15139
|
+
* none was. The Work row reads it before `said`. */
|
|
15140
|
+
summary: external_exports.string().optional(),
|
|
15141
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
15142
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
15143
|
+
titles: external_exports.array(external_exports.string()).optional(),
|
|
15144
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
15145
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
15146
|
+
line: external_exports.string().optional()
|
|
15147
|
+
});
|
|
15119
15148
|
var HistoryWorkSchema2 = external_exports.object({
|
|
15120
15149
|
id: external_exports.string(),
|
|
15121
15150
|
title: external_exports.string(),
|
|
15122
15151
|
state: external_exports.enum(["done", "cancelled"]),
|
|
15123
15152
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
15124
|
-
assignee: external_exports.string()
|
|
15153
|
+
assignee: external_exports.string(),
|
|
15154
|
+
latest: GoalLatestSchema2.nullable().optional()
|
|
15125
15155
|
});
|
|
15126
15156
|
var HistoryEntrySchema2 = external_exports.union([
|
|
15127
15157
|
external_exports.object({ at: external_exports.string(), card: InboxItemSchema2 }),
|
|
@@ -15189,7 +15219,7 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
15189
15219
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
15190
15220
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
15191
15221
|
version: external_exports.string().optional(),
|
|
15192
|
-
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
15222
|
+
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(),
|
|
15193
15223
|
workspaces: external_exports.array(external_exports.string()).optional(),
|
|
15194
15224
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
15195
15225
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
@@ -15349,22 +15379,7 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
15349
15379
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
15350
15380
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
15351
15381
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
15352
|
-
latest:
|
|
15353
|
-
from: external_exports.enum(["person", "agent"]),
|
|
15354
|
-
said: external_exports.string(),
|
|
15355
|
-
at: external_exports.string(),
|
|
15356
|
-
entryId: external_exports.string(),
|
|
15357
|
-
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
15358
|
-
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
15359
|
-
* none was. The Work row reads it before `said`. */
|
|
15360
|
-
summary: external_exports.string().optional(),
|
|
15361
|
-
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
15362
|
-
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
15363
|
-
titles: external_exports.array(external_exports.string()).optional(),
|
|
15364
|
-
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
15365
|
-
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
15366
|
-
line: external_exports.string().optional()
|
|
15367
|
-
}).nullable().optional(),
|
|
15382
|
+
latest: GoalLatestSchema2.nullable().optional(),
|
|
15368
15383
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
15369
15384
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
15370
15385
|
* answer to a question, a voice note filed as work. Null when the only hands on it have been its
|
|
@@ -15692,6 +15707,8 @@ var CallRecapSchema2 = external_exports.object({
|
|
|
15692
15707
|
|
|
15693
15708
|
// src/host.ts
|
|
15694
15709
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
15710
|
+
import { execSync } from "child_process";
|
|
15711
|
+
import { randomBytes } from "crypto";
|
|
15695
15712
|
import { homedir as homedir9 } from "os";
|
|
15696
15713
|
import { join as join11 } from "path";
|
|
15697
15714
|
|
|
@@ -16417,7 +16434,8 @@ function startSession(opts) {
|
|
|
16417
16434
|
if (!existsSync4(cwd)) {
|
|
16418
16435
|
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
16419
16436
|
}
|
|
16420
|
-
|
|
16437
|
+
let codexConfig = opts.harness === "codex" ? JSON.parse(process.env.CODEX_CONFIG || "{}") : null;
|
|
16438
|
+
if (opts.model && opts.harness === "codex") codexConfig = { ...codexConfig, model: opts.model };
|
|
16421
16439
|
if (codexConfig) {
|
|
16422
16440
|
const key3 = "mcp_servers.paigy.env_vars";
|
|
16423
16441
|
const configPath = join9(process.env.CODEX_HOME || join9(homedir8(), ".codex"), "config.toml");
|
|
@@ -16443,6 +16461,8 @@ function startSession(opts) {
|
|
|
16443
16461
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
16444
16462
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
16445
16463
|
...opts.instance ? { PAIGY_INSTANCE_ID: opts.instance } : {},
|
|
16464
|
+
...opts.model && opts.harness === "claude" ? { CLAUDE_MODEL_CONFIG: JSON.stringify({ provider: "anthropic", model: opts.model }) } : {},
|
|
16465
|
+
...opts.model && opts.harness === "agy" ? { AGY_MODEL_CONFIG: JSON.stringify({ model: opts.model }) } : {},
|
|
16446
16466
|
...codexConfig ? { CODEX_CONFIG: JSON.stringify(codexConfig) } : {},
|
|
16447
16467
|
...opts.session ?? opts.resume ? { PAIGY_SESSION_ID: opts.session ?? opts.resume } : {},
|
|
16448
16468
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
@@ -16457,19 +16477,56 @@ function startSession(opts) {
|
|
|
16457
16477
|
stdio: ["pipe", "pipe", "pipe"]
|
|
16458
16478
|
});
|
|
16459
16479
|
let ended2 = false;
|
|
16460
|
-
|
|
16461
|
-
|
|
16480
|
+
let failure;
|
|
16481
|
+
let hush;
|
|
16482
|
+
const pending = /* @__PURE__ */ new Set();
|
|
16483
|
+
const end = (error) => {
|
|
16484
|
+
ended2 = true;
|
|
16485
|
+
failure ??= error;
|
|
16486
|
+
clearTimeout(hush);
|
|
16487
|
+
for (const receipt of pending) receipt.reject(failure);
|
|
16488
|
+
pending.clear();
|
|
16489
|
+
};
|
|
16490
|
+
const broken = (error) => {
|
|
16491
|
+
if (ended2) return;
|
|
16492
|
+
end(new Error(`session_write_failed: ${error.message}`));
|
|
16493
|
+
opts.onEvent({ kind: "error", message: failure.message });
|
|
16494
|
+
child.kill();
|
|
16495
|
+
};
|
|
16496
|
+
child.stdin?.on("error", broken);
|
|
16462
16497
|
const write = (frames) => {
|
|
16463
16498
|
if (ended2) return;
|
|
16464
|
-
for (const f of frames)
|
|
16465
|
-
|
|
16466
|
-
|
|
16499
|
+
for (const f of frames) {
|
|
16500
|
+
if (ended2) break;
|
|
16501
|
+
if (!child.stdin || child.stdin.destroyed || !child.stdin.writable) {
|
|
16502
|
+
broken(new Error("stdin unavailable"));
|
|
16503
|
+
break;
|
|
16504
|
+
}
|
|
16505
|
+
const frame3 = JSON.parse(f);
|
|
16506
|
+
const receipts2 = frame3.method === "session/prompt" ? [...pending].filter((receipt) => !receipt.writing).slice(0, frame3.params.prompt.length) : [];
|
|
16507
|
+
for (const receipt of receipts2) receipt.writing = true;
|
|
16508
|
+
try {
|
|
16509
|
+
child.stdin.write(`${f}
|
|
16510
|
+
`, (error) => {
|
|
16511
|
+
if (error) {
|
|
16512
|
+
broken(error);
|
|
16513
|
+
return;
|
|
16514
|
+
}
|
|
16515
|
+
if (ended2) return;
|
|
16516
|
+
for (const receipt of receipts2) if (pending.delete(receipt)) receipt.resolve();
|
|
16517
|
+
});
|
|
16518
|
+
} catch (error) {
|
|
16519
|
+
broken(error instanceof Error ? error : new Error(String(error)));
|
|
16520
|
+
}
|
|
16521
|
+
}
|
|
16522
|
+
if (frames.length && !ended2) listen();
|
|
16467
16523
|
};
|
|
16468
16524
|
const apply = ({ events, writes }) => {
|
|
16525
|
+
const fatal = events.find((event) => event.kind === "error" && event.fatal);
|
|
16526
|
+
if (fatal?.kind === "error") end(new Error(fatal.message));
|
|
16469
16527
|
write(writes);
|
|
16470
16528
|
for (const event of events) opts.onEvent(event);
|
|
16471
16529
|
};
|
|
16472
|
-
let hush;
|
|
16473
16530
|
const listen = () => {
|
|
16474
16531
|
clearTimeout(hush);
|
|
16475
16532
|
if (!ended2) hush = setTimeout(() => apply(driver.quiet(QUIET_MS)), QUIET_MS);
|
|
@@ -16479,12 +16536,12 @@ function startSession(opts) {
|
|
|
16479
16536
|
if (text) opts.onEvent({ kind: "error", message: text });
|
|
16480
16537
|
});
|
|
16481
16538
|
child.on("exit", (code) => {
|
|
16482
|
-
|
|
16483
|
-
clearTimeout(hush);
|
|
16539
|
+
end(new Error(`session_ended: ${code ?? "signal"}`));
|
|
16484
16540
|
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
16485
16541
|
opts.onExit?.();
|
|
16486
16542
|
});
|
|
16487
16543
|
child.on("error", (e) => {
|
|
16544
|
+
end(new Error(`session_start_failed: ${e.message}`));
|
|
16488
16545
|
opts.onEvent({ kind: "error", message: e.message });
|
|
16489
16546
|
opts.onExit?.();
|
|
16490
16547
|
});
|
|
@@ -16504,15 +16561,24 @@ function startSession(opts) {
|
|
|
16504
16561
|
write(driver.open());
|
|
16505
16562
|
return {
|
|
16506
16563
|
send(text) {
|
|
16507
|
-
|
|
16564
|
+
const sent = new Promise((resolve3, reject) => {
|
|
16565
|
+
if (ended2) {
|
|
16566
|
+
reject(failure ?? new Error("session_ended"));
|
|
16567
|
+
return;
|
|
16568
|
+
}
|
|
16569
|
+
pending.add({ resolve: resolve3, reject, writing: false });
|
|
16570
|
+
write(driver.send(text));
|
|
16571
|
+
});
|
|
16572
|
+
void sent.catch(() => {
|
|
16573
|
+
});
|
|
16574
|
+
return sent;
|
|
16508
16575
|
},
|
|
16509
16576
|
respond(id3, decision) {
|
|
16510
16577
|
write(driver.respond(id3, decision));
|
|
16511
16578
|
},
|
|
16512
16579
|
stop() {
|
|
16513
16580
|
write(driver.close());
|
|
16514
|
-
|
|
16515
|
-
clearTimeout(hush);
|
|
16581
|
+
end(new Error("session_stopped"));
|
|
16516
16582
|
child.kill();
|
|
16517
16583
|
}
|
|
16518
16584
|
};
|
|
@@ -16672,12 +16738,13 @@ var LIMITS = episodes();
|
|
|
16672
16738
|
// src/paigy/bridge.ts
|
|
16673
16739
|
function railFor(opts) {
|
|
16674
16740
|
return {
|
|
16675
|
-
next: async () => {
|
|
16676
|
-
const waiting = await receive(opts);
|
|
16741
|
+
next: async (alreadyRead) => {
|
|
16742
|
+
const waiting = alreadyRead ?? await receive(opts);
|
|
16677
16743
|
const goalId = waiting.claimable?.goalId ?? waiting.review?.[0]?.goalId;
|
|
16678
16744
|
return goalId ? getGoals([goalId], opts).then(([goal]) => goal) : { state: "idle", message: "" };
|
|
16679
16745
|
},
|
|
16680
16746
|
read: (goalId) => getGoals([goalId], opts).then(([goal]) => goal),
|
|
16747
|
+
delivery: (deliveryId) => readDelivery(deliveryId, opts),
|
|
16681
16748
|
complete: async (goalId, revision) => {
|
|
16682
16749
|
const done = await manageGoals({ changes: [{ kind: "state", goalId, state: "completed", ...revision !== void 0 ? { revision } : {} }] }, opts);
|
|
16683
16750
|
const result = done.results[0];
|
|
@@ -16858,7 +16925,7 @@ function beat() {
|
|
|
16858
16925
|
}
|
|
16859
16926
|
var BEAT_REPORT_MS = 10 * 6e4;
|
|
16860
16927
|
var ended = (state) => state === "done" || state === "cancelled";
|
|
16861
|
-
function unheard(brief, state, since = null, after = -1, reviewSince) {
|
|
16928
|
+
function unheard(brief, state, since = null, after = -1, reviewSince, gathered) {
|
|
16862
16929
|
const seen = state.seen ??= /* @__PURE__ */ new Set();
|
|
16863
16930
|
const words = [];
|
|
16864
16931
|
const pendingAt = reviewSince ? Date.parse(reviewSince) : null;
|
|
@@ -16873,10 +16940,13 @@ function unheard(brief, state, since = null, after = -1, reviewSince) {
|
|
|
16873
16940
|
continue;
|
|
16874
16941
|
}
|
|
16875
16942
|
if (since !== null && Date.parse(e.createdAt) <= Date.parse(since)) continue;
|
|
16876
|
-
seen.add(e.entryId);
|
|
16877
16943
|
if (e.aboutId && seen.has(e.aboutId) && brief.answers?.some((a) => a.requestEntryId === e.aboutId)) continue;
|
|
16944
|
+
seen.add(e.entryId);
|
|
16878
16945
|
const text = entryWords(e).trim();
|
|
16879
|
-
if (text)
|
|
16946
|
+
if (text) {
|
|
16947
|
+
words.push(text);
|
|
16948
|
+
gathered?.add(e.entryId);
|
|
16949
|
+
}
|
|
16880
16950
|
}
|
|
16881
16951
|
if (pendingAt !== null) for (const answer of brief.answers ?? []) {
|
|
16882
16952
|
const key3 = `answer:${answer.decisionNeedId}`;
|
|
@@ -16917,12 +16987,28 @@ function named(goalId, title, brief) {
|
|
|
16917
16987
|
const name = line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
|
|
16918
16988
|
return name ? `On Goal "${name}" (${goalId}):` : `On Goal ${goalId}:`;
|
|
16919
16989
|
}
|
|
16920
|
-
async function gather(state, rail, held) {
|
|
16921
|
-
const looks = /* @__PURE__ */ new Map(
|
|
16990
|
+
async function gather(state, rail, held, alreadyRead, log2) {
|
|
16991
|
+
const looks = /* @__PURE__ */ new Map();
|
|
16992
|
+
if (held && state.goal) looks.set(state.goal.id, { via: "held", brief: held });
|
|
16922
16993
|
const look2 = (goalId, via, title, since) => {
|
|
16923
16994
|
if (!looks.has(goalId)) looks.set(goalId, { via, ...title ? { title } : {}, ...since !== void 0 ? { since } : {} });
|
|
16924
16995
|
};
|
|
16925
|
-
const waiting = await rail.waiting().catch(() => null);
|
|
16996
|
+
const waiting = alreadyRead ?? await rail.waiting().catch(() => null);
|
|
16997
|
+
const direct = [];
|
|
16998
|
+
const staged = new Set(state.seen);
|
|
16999
|
+
const deliveries = /* @__PURE__ */ new Set();
|
|
17000
|
+
for (const d of waiting?.deliveries ?? []) {
|
|
17001
|
+
if (d.goalIds.length || deliveries.has(d.deliveryId) || state.acked?.get(`delivery:${d.deliveryId}`) === "handled") continue;
|
|
17002
|
+
deliveries.add(d.deliveryId);
|
|
17003
|
+
try {
|
|
17004
|
+
const full = await rail.delivery(d.deliveryId);
|
|
17005
|
+
const entries = full.entries.map((e) => ({ id: e.entryId, words: entryWords(e) }));
|
|
17006
|
+
if (!entries.length || entries.some((e) => !e.words.trim())) continue;
|
|
17007
|
+
direct.push({ eventId: d.deliveryId, entries, entryIds: entries.map((e) => e.id), words: [] });
|
|
17008
|
+
} catch (error) {
|
|
17009
|
+
log2?.(`\u26A0 could not read instruction ${d.deliveryId.slice(0, 8)}: ${error.message}`);
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
16926
17012
|
const listed = /* @__PURE__ */ new Map();
|
|
16927
17013
|
const list = (goalId, eventId) => {
|
|
16928
17014
|
const ids = listed.get(goalId) ?? [];
|
|
@@ -16930,19 +17016,21 @@ async function gather(state, rail, held) {
|
|
|
16930
17016
|
listed.set(goalId, ids);
|
|
16931
17017
|
};
|
|
16932
17018
|
const announced = state.announced ??= /* @__PURE__ */ new Set();
|
|
16933
|
-
if (
|
|
16934
|
-
|
|
16935
|
-
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
|
|
17019
|
+
if (held) {
|
|
17020
|
+
if (waiting) {
|
|
17021
|
+
for (const id3 of announced) if (!(waiting.assigned ?? []).some((g) => g.goalId === id3)) announced.delete(id3);
|
|
17022
|
+
}
|
|
17023
|
+
for (const g of waiting?.assigned ?? []) if (g.goalId !== state.goal.id && !announced.has(g.goalId)) look2(g.goalId, "assigned", g.title);
|
|
17024
|
+
for (const g of waiting?.review ?? []) {
|
|
17025
|
+
list(g.goalId, g.eventId);
|
|
17026
|
+
look2(g.goalId, "waiting work", g.title);
|
|
17027
|
+
}
|
|
17028
|
+
for (const d of waiting?.deliveries ?? []) for (const id3 of d.goalIds) {
|
|
17029
|
+
list(id3, d.deliveryId);
|
|
17030
|
+
look2(id3, "waiting work");
|
|
17031
|
+
}
|
|
16944
17032
|
}
|
|
16945
|
-
const moved = movedBelow(held, state);
|
|
17033
|
+
const moved = held ? movedBelow(held, state) : [];
|
|
16946
17034
|
for (const { goal, since } of moved) look2(goal.goalId, "subtree", goal.title, since);
|
|
16947
17035
|
const heard = [];
|
|
16948
17036
|
const read3 = /* @__PURE__ */ new Set();
|
|
@@ -16959,10 +17047,15 @@ async function gather(state, rail, held) {
|
|
|
16959
17047
|
read3.add(goalId);
|
|
16960
17048
|
const fresh = !brief.reviewSince && (found.via === "waiting work" || found.via === "assigned") ? sinceTheWork(brief) : { after: -1 };
|
|
16961
17049
|
if (found.via === "assigned") announced.add(goalId);
|
|
16962
|
-
heard.push({ goalId, ...found, brief, words: unheard(brief, state, brief.reviewSince ? null : since ?? null, fresh.after, brief.reviewSince), ...fresh.note ? { note: fresh.note } : {} });
|
|
17050
|
+
heard.push({ goalId, ...found, brief, words: unheard(brief, state, brief.reviewSince ? null : since ?? null, fresh.after, brief.reviewSince, staged), ...fresh.note ? { note: fresh.note } : {} });
|
|
16963
17051
|
}
|
|
17052
|
+
for (const d of direct) d.words = d.entries.filter((e) => {
|
|
17053
|
+
if (staged.has(e.id)) return false;
|
|
17054
|
+
staged.add(e.id);
|
|
17055
|
+
return true;
|
|
17056
|
+
}).map((e) => e.words);
|
|
16964
17057
|
for (const { goal, since } of moved) if (read3.has(goal.goalId)) state.below.goals.set(goal.goalId, { since, at: goal.updatedAt ?? null });
|
|
16965
|
-
return { heard, listed };
|
|
17058
|
+
return { heard, listed, direct };
|
|
16966
17059
|
}
|
|
16967
17060
|
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.";
|
|
16968
17061
|
var says = (h) => h.words.length > 0 || h.via === "assigned";
|
|
@@ -16980,12 +17073,21 @@ var block = (h) => [
|
|
|
16980
17073
|
].join("\n\n");
|
|
16981
17074
|
var short = (ids) => ids.map((id3) => id3.slice(0, 8)).join(", ");
|
|
16982
17075
|
var given = (heard) => heard.filter((h) => h.via === "assigned").map((h) => h.goalId);
|
|
16983
|
-
async function hear(session, state, rail, held, opts) {
|
|
17076
|
+
async function hear(session, state, rail, held, opts, waiting) {
|
|
16984
17077
|
const log2 = opts?.log;
|
|
16985
|
-
const { heard, listed } = await gather(state, rail, held);
|
|
17078
|
+
const { heard, listed, direct } = await gather(state, rail, held, waiting, log2);
|
|
16986
17079
|
const said = heard.filter(says);
|
|
16987
|
-
|
|
16988
|
-
|
|
17080
|
+
const words = [...direct.flatMap((d) => d.words), ...said.map(block)];
|
|
17081
|
+
if (words.length) {
|
|
17082
|
+
const text = words.join("\n\n");
|
|
17083
|
+
try {
|
|
17084
|
+
await send2(session, state, text);
|
|
17085
|
+
} catch (error) {
|
|
17086
|
+
(state.noted ??= []).push({ name: "", goals: said.map((h) => h.goalId), words: text, eventIds: direct.map((d) => d.eventId), at: Date.now(), cause: "read" });
|
|
17087
|
+
for (const d of direct) for (const id3 of d.entryIds) (state.seen ??= /* @__PURE__ */ new Set()).add(id3);
|
|
17088
|
+
throw error;
|
|
17089
|
+
}
|
|
17090
|
+
for (const d of direct) for (const id3 of d.entryIds) (state.seen ??= /* @__PURE__ */ new Set()).add(id3);
|
|
16989
17091
|
const worded = said.filter((h) => h.words.length);
|
|
16990
17092
|
if (worded.length) log2?.(`\u25B8 typed the person's words from ${worded.map((h) => `${h.goalId.slice(0, 8)} (via ${h.via})`).join(", ")}`);
|
|
16991
17093
|
const goals = given(said);
|
|
@@ -16994,18 +17096,21 @@ async function hear(session, state, rail, held, opts) {
|
|
|
16994
17096
|
if (opts?.tally) opts.tally.announced += goals.length;
|
|
16995
17097
|
}
|
|
16996
17098
|
}
|
|
17099
|
+
await acknowledgeDirect(direct.map((d) => d.eventId), state, rail, log2);
|
|
16997
17100
|
for (const h of heard) {
|
|
16998
17101
|
const events = listed.get(h.goalId);
|
|
16999
17102
|
if (events) await acknowledge2(h.goalId, h.brief, events, state, rail, log2);
|
|
17000
17103
|
}
|
|
17104
|
+
return direct.length > 0 || said.length > 0;
|
|
17001
17105
|
}
|
|
17002
17106
|
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.";
|
|
17003
17107
|
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.";
|
|
17004
|
-
async function note(state, rail, held, inbox, how) {
|
|
17005
|
-
const { heard } = await gather(state, rail, held);
|
|
17108
|
+
async function note(state, rail, held, inbox, how, waiting) {
|
|
17109
|
+
const { heard, direct } = await gather(state, rail, held, waiting, how.log);
|
|
17006
17110
|
const said = heard.filter(says);
|
|
17007
|
-
|
|
17008
|
-
|
|
17111
|
+
const parts = [...direct.flatMap((d) => d.words), ...said.map(block)];
|
|
17112
|
+
if (!parts.length) return 0;
|
|
17113
|
+
const words = parts.join("\n\n");
|
|
17009
17114
|
const goals = said.map((h) => h.goalId);
|
|
17010
17115
|
const assigned = given(said);
|
|
17011
17116
|
if (how.tally) how.tally.announced += assigned.length;
|
|
@@ -17019,7 +17124,8 @@ ${words}`);
|
|
|
17019
17124
|
} catch (error) {
|
|
17020
17125
|
how.log?.(`\u26A0 could not note mid-turn (${error.message}): ${short(goals)} wait for the turn's end`);
|
|
17021
17126
|
}
|
|
17022
|
-
(state.noted ??= []).push({ name, goals, words, at: how.now(), cause: how.cause === "wake" ? "wake" : "read" });
|
|
17127
|
+
(state.noted ??= []).push({ name, goals, words, eventIds: direct.map((d) => d.eventId), at: how.now(), cause: how.cause === "wake" ? "wake" : "read" });
|
|
17128
|
+
for (const d of direct) for (const id3 of d.entryIds) (state.seen ??= /* @__PURE__ */ new Set()).add(id3);
|
|
17023
17129
|
return 1;
|
|
17024
17130
|
}
|
|
17025
17131
|
function receipts(state, inbox, log2) {
|
|
@@ -17034,15 +17140,33 @@ function receipts(state, inbox, log2) {
|
|
|
17034
17140
|
}
|
|
17035
17141
|
return seen;
|
|
17036
17142
|
}
|
|
17037
|
-
function settle(session, state, inbox, log2) {
|
|
17143
|
+
async function settle(session, state, rail, inbox, log2) {
|
|
17038
17144
|
const batches = state.noted ?? [];
|
|
17039
|
-
state.noted = [];
|
|
17040
17145
|
const seen = [];
|
|
17041
17146
|
const unseen = [];
|
|
17042
17147
|
for (const b of batches) (b.name && inbox?.settle(b.name) === "taken" ? seen : unseen).push(b);
|
|
17043
|
-
send2(session, state, [...seen.length ? [SEEN_MID_TURN, ...seen.map((b) => b.words)] : [], ...unseen.map((b) => b.words)].join("\n\n"));
|
|
17148
|
+
await send2(session, state, [...seen.length ? [SEEN_MID_TURN, ...seen.map((b) => b.words)] : [], ...unseen.map((b) => b.words)].join("\n\n"));
|
|
17149
|
+
state.noted = [];
|
|
17150
|
+
await acknowledgeDirect(batches.flatMap((b) => b.eventIds ?? []), state, rail, log2);
|
|
17044
17151
|
log2?.(`\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)`);
|
|
17045
17152
|
}
|
|
17153
|
+
async function acknowledgeDirect(events, state, rail, log2) {
|
|
17154
|
+
const acked = state.acked ??= /* @__PURE__ */ new Map();
|
|
17155
|
+
const ids = [...new Set(events)].filter((id3) => acked.get(`delivery:${id3}`) !== "handled");
|
|
17156
|
+
for (let at = 0; at < ids.length; at += 100) {
|
|
17157
|
+
const batch = ids.slice(at, at + 100);
|
|
17158
|
+
try {
|
|
17159
|
+
const results = await rail.ack(batch);
|
|
17160
|
+
for (const id3 of batch) {
|
|
17161
|
+
const result = results.find((r) => r.eventId === id3);
|
|
17162
|
+
if (result?.acknowledged) acked.set(`delivery:${id3}`, "handled");
|
|
17163
|
+
else log2?.(`\u26A0 could not acknowledge ${id3.slice(0, 8)}: ${result?.refused ?? "no result"} \u2014 its words are typed in; it stays listed until acknowledged`);
|
|
17164
|
+
}
|
|
17165
|
+
} catch (error) {
|
|
17166
|
+
log2?.(`\u26A0 could not acknowledge instructions: ${error.message} \u2014 their words are typed in; they stay listed until acknowledged`);
|
|
17167
|
+
}
|
|
17168
|
+
}
|
|
17169
|
+
}
|
|
17046
17170
|
async function acknowledge2(goalId, brief, events, state, rail, log2) {
|
|
17047
17171
|
const ids = [.../* @__PURE__ */ new Set([...brief.reviewEventId ? [brief.reviewEventId] : [], ...events])];
|
|
17048
17172
|
if (!ids.length) return;
|
|
@@ -17068,17 +17192,23 @@ async function step(session, state, rail, opts) {
|
|
|
17068
17192
|
state.carryOn = true;
|
|
17069
17193
|
}
|
|
17070
17194
|
await look(session, state, rail, opts);
|
|
17071
|
-
if (state.carryOn && state.resting && !state.asks?.size) send2(session, state, "");
|
|
17195
|
+
if (state.carryOn && state.resting && !state.asks?.size) await send2(session, state, "");
|
|
17072
17196
|
}
|
|
17073
17197
|
async function look(session, state, rail, opts) {
|
|
17074
17198
|
if (opts?.inbox) {
|
|
17075
17199
|
const seen = receipts(state, opts.inbox, opts.log);
|
|
17076
17200
|
if (opts.tally) opts.tally.seen += seen;
|
|
17077
17201
|
}
|
|
17078
|
-
if (state.resting && state.noted?.length && !state.asks?.size) return settle(session, state, opts?.inbox, opts?.log);
|
|
17202
|
+
if (state.resting && state.noted?.length && !state.asks?.size) return settle(session, state, rail, opts?.inbox, opts?.log);
|
|
17079
17203
|
if (!state.goal) {
|
|
17204
|
+
if (state.asks?.size || !state.resting && !opts?.inbox) return;
|
|
17205
|
+
const waiting = await rail.waiting();
|
|
17206
|
+
if (waiting.deliveries.some((d) => !d.goalIds.length && state.acked?.get(`delivery:${d.deliveryId}`) !== "handled")) {
|
|
17207
|
+
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);
|
|
17208
|
+
if (handled) return;
|
|
17209
|
+
}
|
|
17080
17210
|
if (!state.resting) return;
|
|
17081
|
-
let brief2 = await rail.next();
|
|
17211
|
+
let brief2 = await rail.next(waiting);
|
|
17082
17212
|
if (!brief2.goalId || brief2.revision === void 0) {
|
|
17083
17213
|
if (opts?.initialPrompt?.trim()) {
|
|
17084
17214
|
const prompt = opts.initialPrompt.trim();
|
|
@@ -17087,7 +17217,7 @@ async function look(session, state, rail, opts) {
|
|
|
17087
17217
|
brief2 = await rail.next();
|
|
17088
17218
|
if (!brief2.goalId || brief2.revision === void 0) {
|
|
17089
17219
|
state.goal = { id: admitted.goalId, revision: admitted.revision };
|
|
17090
|
-
send2(session, state, [HARNESS_INSTRUCTION, named(admitted.goalId, void 0, { outcome: prompt }), prompt].join("\n\n"));
|
|
17220
|
+
await send2(session, state, [HARNESS_INSTRUCTION, named(admitted.goalId, void 0, { outcome: prompt }), prompt].join("\n\n"));
|
|
17091
17221
|
return;
|
|
17092
17222
|
}
|
|
17093
17223
|
} else {
|
|
@@ -17105,7 +17235,7 @@ async function look(session, state, rail, opts) {
|
|
|
17105
17235
|
const left = brief2.earlier ? brief2.earlier.omitted + brief2.earlier.shortened : 0;
|
|
17106
17236
|
const earlier = left ? `(${left} earlier entries on this Goal are left out or shortened; get_goals with history: true reads them in full.)` : "";
|
|
17107
17237
|
const fullText = [outcome2, earlier, ...said].filter(Boolean).join("\n\n");
|
|
17108
|
-
send2(session, state, [HARNESS_INSTRUCTION, named(brief2.goalId, void 0, brief2), fullText].filter(Boolean).join("\n\n"));
|
|
17238
|
+
await send2(session, state, [HARNESS_INSTRUCTION, named(brief2.goalId, void 0, brief2), fullText].filter(Boolean).join("\n\n"));
|
|
17109
17239
|
if (ended(brief2.state)) await acknowledge2(brief2.goalId, brief2, [], state, rail, opts?.log);
|
|
17110
17240
|
return;
|
|
17111
17241
|
}
|
|
@@ -17134,19 +17264,27 @@ async function look(session, state, rail, opts) {
|
|
|
17134
17264
|
}
|
|
17135
17265
|
await hear(session, state, rail, brief, { ...opts?.log ? { log: opts.log } : {}, ...opts?.tally ? { tally: opts.tally } : {} });
|
|
17136
17266
|
}
|
|
17137
|
-
function send2(session, state, text) {
|
|
17138
|
-
state.resting = false;
|
|
17267
|
+
async function send2(session, state, text) {
|
|
17139
17268
|
let words = text;
|
|
17140
17269
|
if (state.carryOn) {
|
|
17141
|
-
state.carryOn = false;
|
|
17142
|
-
state.carriedOn = true;
|
|
17143
17270
|
const owed = state.owed;
|
|
17144
|
-
state.owed = void 0;
|
|
17145
17271
|
words = [owed, text].filter(Boolean).join("\n\n");
|
|
17146
17272
|
text = [CARRY_ON, ...owed ? [OWED, owed] : [], text].filter(Boolean).join("\n\n");
|
|
17147
17273
|
}
|
|
17274
|
+
const resting = state.resting;
|
|
17275
|
+
state.resting = false;
|
|
17276
|
+
try {
|
|
17277
|
+
await session.send(text);
|
|
17278
|
+
} catch (error) {
|
|
17279
|
+
state.resting = resting;
|
|
17280
|
+
throw error;
|
|
17281
|
+
}
|
|
17148
17282
|
state.typed = words || void 0;
|
|
17149
|
-
|
|
17283
|
+
if (state.carryOn) {
|
|
17284
|
+
state.carryOn = false;
|
|
17285
|
+
state.carriedOn = true;
|
|
17286
|
+
state.owed = void 0;
|
|
17287
|
+
}
|
|
17150
17288
|
}
|
|
17151
17289
|
async function pump(session, state, rail, opts) {
|
|
17152
17290
|
const clock5 = opts.beat ?? beat();
|
|
@@ -17286,9 +17424,6 @@ ${event.message}`, state, rail, { log: opts.log });
|
|
|
17286
17424
|
opts.log("\u25A0 stopped");
|
|
17287
17425
|
}
|
|
17288
17426
|
return {
|
|
17289
|
-
send(text) {
|
|
17290
|
-
session?.send(text);
|
|
17291
|
-
},
|
|
17292
17427
|
working: () => running && state.resting !== true,
|
|
17293
17428
|
// Paused by a usage limit, the activity line says so: the quiet place the pause is shown.
|
|
17294
17429
|
tail: () => state.paused ? [pausedLine(state.paused)] : [...tail],
|
|
@@ -17411,7 +17546,7 @@ var claimedRun = (sessionId2) => {
|
|
|
17411
17546
|
const slot = sessionSlot(sessionId2);
|
|
17412
17547
|
return { slot, key: runKey(slot) };
|
|
17413
17548
|
};
|
|
17414
|
-
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
17549
|
+
var launchRun = (spec) => ({ session: spec.sessionId, model: spec.model });
|
|
17415
17550
|
var BOUND_ELSEWHERE_MS = 10 * 6e4;
|
|
17416
17551
|
var boundElsewhere = /* @__PURE__ */ new Map();
|
|
17417
17552
|
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";
|
|
@@ -17519,7 +17654,7 @@ function startHost(opts) {
|
|
|
17519
17654
|
});
|
|
17520
17655
|
void heartbeat({
|
|
17521
17656
|
version: VERSION,
|
|
17522
|
-
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
17657
|
+
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status, models: a.models })),
|
|
17523
17658
|
workspaces: listWorkspaces(opts.wsDeps),
|
|
17524
17659
|
// The repos inside them, so a session started for work on one opens IN it (Goal 26982211).
|
|
17525
17660
|
repos: listRepos(opts.wsDeps)
|
|
@@ -17532,6 +17667,17 @@ function startHost(opts) {
|
|
|
17532
17667
|
const specs = await claimSessions(asHost).catch(() => []);
|
|
17533
17668
|
for (const spec of specs) {
|
|
17534
17669
|
const label = spec.name ?? spec.harness;
|
|
17670
|
+
let workspace = spec.workspace;
|
|
17671
|
+
if (spec.worktree) {
|
|
17672
|
+
const suffix = randomBytes(4).toString("hex");
|
|
17673
|
+
const newWt = `/tmp/paigy-${spec.harness}-${suffix}`;
|
|
17674
|
+
try {
|
|
17675
|
+
execSync(`git worktree add ${newWt} -b paigy-${suffix}`, { cwd: workspace, stdio: "ignore" });
|
|
17676
|
+
workspace = newWt;
|
|
17677
|
+
} catch (e) {
|
|
17678
|
+
opts.log(`[${label}] Failed to create worktree, falling back to ${workspace}`);
|
|
17679
|
+
}
|
|
17680
|
+
}
|
|
17535
17681
|
const { slot, key: key3 } = claimedRun(spec.sessionId);
|
|
17536
17682
|
const log2 = (line) => opts.log(`[${label}] ${line}`);
|
|
17537
17683
|
try {
|
|
@@ -17549,7 +17695,7 @@ function startHost(opts) {
|
|
|
17549
17695
|
}
|
|
17550
17696
|
const run = runHarness({
|
|
17551
17697
|
harness: spec.harness,
|
|
17552
|
-
cwd:
|
|
17698
|
+
cwd: workspace,
|
|
17553
17699
|
mode: "bypass",
|
|
17554
17700
|
prompt: "",
|
|
17555
17701
|
token: spec.token,
|