@paigy/harness 0.3.29 → 0.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/cli.js +204 -57
- package/dist/main.js +207 -60
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -10040,7 +10040,7 @@ var GetGoalSchema = external_exports.object({
|
|
|
10040
10040
|
history: external_exports.boolean().optional(),
|
|
10041
10041
|
/** WHEN A READ HAS CONFUSED YOU. Adds `diagnosis`: every reader that already answers a question
|
|
10042
10042
|
* about this work, each answer attributed to the reader that gave it, and every disagreement
|
|
10043
|
-
* between two of them named. Off by default; `
|
|
10043
|
+
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
10044
10044
|
diagnose: external_exports.boolean().optional()
|
|
10045
10045
|
}).strict();
|
|
10046
10046
|
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Another account's Goals, and another agent's Goal that is not below one of yours, are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
@@ -10058,6 +10058,7 @@ var AGENT_TOOLS = [
|
|
|
10058
10058
|
];
|
|
10059
10059
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
10060
10060
|
var LIVE_MS = 3 * 6e4;
|
|
10061
|
+
var WORKING_MS = 30 * 6e4;
|
|
10061
10062
|
var ContextSchema = external_exports.object({
|
|
10062
10063
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
10063
10064
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -10128,7 +10129,7 @@ var AttentionSchema = external_exports.object({
|
|
|
10128
10129
|
points: external_exports.array(external_exports.string()).nullable(),
|
|
10129
10130
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
10130
10131
|
blocking: external_exports.boolean(),
|
|
10131
|
-
/** Reserved (
|
|
10132
|
+
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
10132
10133
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
10133
10134
|
deadline: external_exports.string().datetime().nullable().optional()
|
|
10134
10135
|
});
|
|
@@ -10179,7 +10180,7 @@ var NotifyRequestFields = external_exports.object({
|
|
|
10179
10180
|
* derive agent-side before sealing, so the server only ever shapes plaintext). */
|
|
10180
10181
|
// 10k, not a sentence budget. What the human hears is bounded by the BROKER — it splits
|
|
10181
10182
|
// the ask into topics and gives each one at most three sentences and one question
|
|
10182
|
-
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
10183
|
+
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
10183
10184
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
10184
10185
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
10185
10186
|
ask: external_exports.string().min(1).max(1e4).optional().describe(
|
|
@@ -10256,7 +10257,7 @@ var UserAnswerSchema = external_exports.discriminatedUnion("kind", [
|
|
|
10256
10257
|
external_exports.object({ kind: external_exports.literal("clarify"), chunks: external_exports.array(external_exports.string()).min(1) }),
|
|
10257
10258
|
external_exports.object({ kind: external_exports.literal("confirm"), approved: external_exports.boolean() }),
|
|
10258
10259
|
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema).min(1) }),
|
|
10259
|
-
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
10260
|
+
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
10260
10261
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
10261
10262
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
10262
10263
|
* card the user can reply to — the broker never overrides the user. */
|
|
@@ -10305,7 +10306,7 @@ var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
|
10305
10306
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
10306
10307
|
* a sentence with a hole in it is worse than no sentence. */
|
|
10307
10308
|
note: external_exports.string().optional(),
|
|
10308
|
-
/** The call record rendered for THIS agent (`voice/record-design.md`): the words the
|
|
10309
|
+
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
10309
10310
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
10310
10311
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
10311
10312
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
@@ -10401,10 +10402,11 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
10401
10402
|
agentId: external_exports.string().optional(),
|
|
10402
10403
|
select: SelectShapeSchema.optional(),
|
|
10403
10404
|
options: external_exports.array(OptionSchema.omit({ id: true })).optional(),
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10405
|
+
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
10406
|
+
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
10407
|
+
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
10408
|
+
spoken, not where the plan is written, so nothing between the brain and the walk decides
|
|
10409
|
+
anything. A caller's own `beat_s` tuning is what it used to override. */
|
|
10408
10410
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
10409
10411
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
10410
10412
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
@@ -10439,7 +10441,7 @@ var InboxItemSchema = external_exports.object({
|
|
|
10439
10441
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
10440
10442
|
* `createdAt`. */
|
|
10441
10443
|
agentStateAt: external_exports.string().datetime().optional(),
|
|
10442
|
-
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (walk/design.md §11, owner
|
|
10444
|
+
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
10443
10445
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
10444
10446
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
10445
10447
|
* Call's cards, and every card of that Call carries the same list: the call screen reads it
|
|
@@ -10500,7 +10502,7 @@ var InboxItemSchema = external_exports.object({
|
|
|
10500
10502
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
10501
10503
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
10502
10504
|
onCall: external_exports.literal(true).optional(),
|
|
10503
|
-
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
10505
|
+
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
10504
10506
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
10505
10507
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
10506
10508
|
* The ladder is ACCOUNT-WIDE (#2259): `anchorAt` and `step` are the account's position;
|
|
@@ -10531,7 +10533,7 @@ var InboxItemSchema = external_exports.object({
|
|
|
10531
10533
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
10532
10534
|
),
|
|
10533
10535
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
10534
|
-
* urgency (see the main README's "premier use case" + notify/states.md). Drives the
|
|
10536
|
+
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
10535
10537
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
10536
10538
|
blocking: external_exports.boolean().default(false),
|
|
10537
10539
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
@@ -10605,9 +10607,16 @@ var UserSettingsSchema = external_exports.object({
|
|
|
10605
10607
|
banner: external_exports.boolean(),
|
|
10606
10608
|
push: external_exports.boolean()
|
|
10607
10609
|
}),
|
|
10610
|
+
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
10611
|
+
* they are an enum on a live column across every account, and the rename is a rename of
|
|
10612
|
+
* what people read (owner, 2026-09-30). */
|
|
10608
10613
|
sessionMode: external_exports.enum(["default", "all_calls", "silent"]),
|
|
10609
|
-
|
|
10610
|
-
|
|
10614
|
+
/** DEAD, AND ACCEPTED ANYWAY (#2813). DateNight is inbox-only with no way back, so nothing
|
|
10615
|
+
* reads this. It stays OPTIONAL rather than deleted because a phone on the old bundle
|
|
10616
|
+
* PATCHes the whole settings object and would be refused for sending a key we stopped
|
|
10617
|
+
* wanting — the same trap `voiceMode` below already documents. #2813 drops the field and
|
|
10618
|
+
* its column once no installed build still sends it. */
|
|
10619
|
+
silentPush: external_exports.boolean().optional(),
|
|
10611
10620
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
10612
10621
|
improveConsent: external_exports.boolean(),
|
|
10613
10622
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
@@ -10618,12 +10627,16 @@ var UserSettingsSchema = external_exports.object({
|
|
|
10618
10627
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
10619
10628
|
* write, 'hosted' on read (see store.ts). */
|
|
10620
10629
|
voiceMode: external_exports.enum(["hosted", "on_device"]).optional(),
|
|
10621
|
-
/** Talk — after you answer, the next step is read aloud (walk/design.md §6). ALWAYS ON until
|
|
10630
|
+
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
10622
10631
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
10623
10632
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
10624
10633
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
10625
10634
|
* read (see store.ts). */
|
|
10626
10635
|
talk: external_exports.boolean().optional(),
|
|
10636
|
+
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
10637
|
+
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
10638
|
+
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
10639
|
+
callDiagnostics: external_exports.boolean().optional(),
|
|
10627
10640
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
10628
10641
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
10629
10642
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -10632,9 +10645,6 @@ var UserSettingsSchema = external_exports.object({
|
|
|
10632
10645
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
10633
10646
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
10634
10647
|
voiceTuning: external_exports.record(external_exports.string(), external_exports.union([external_exports.number(), external_exports.string()])).optional(),
|
|
10635
|
-
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
10636
|
-
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
10637
|
-
pstnCalls: external_exports.boolean().optional(),
|
|
10638
10648
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
10639
10649
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
10640
10650
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -10678,7 +10688,7 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
10678
10688
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
10679
10689
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
10680
10690
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
10681
|
-
*
|
|
10691
|
+
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
10682
10692
|
kind: external_exports.enum(["device", "agent"]).optional(),
|
|
10683
10693
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
10684
10694
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
@@ -10696,7 +10706,7 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
10696
10706
|
* user on the agent's own page. null = no ceiling (today's behaviour for every
|
|
10697
10707
|
* connection). Android binds importance to a relationship rather than to each message,
|
|
10698
10708
|
* and that is the thing our roster could not say: "Marlow may always call me; Otto
|
|
10699
|
-
* never may" (navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
|
|
10709
|
+
* never may" (docs/clients/app/party/navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
|
|
10700
10710
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
10701
10711
|
* set once must not overrule a rule they set about one agent. */
|
|
10702
10712
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
@@ -10707,7 +10717,14 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
10707
10717
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
10708
10718
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
10709
10719
|
lastSeenAt: external_exports.string().datetime().nullable().optional(),
|
|
10710
|
-
/**
|
|
10720
|
+
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
10721
|
+
* its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
|
|
10722
|
+
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
10723
|
+
lastWorkedAt: external_exports.string().datetime().nullable().optional(),
|
|
10724
|
+
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
10725
|
+
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
10726
|
+
oldestReadyAt: external_exports.string().datetime().nullable().optional(),
|
|
10727
|
+
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
10711
10728
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
10712
10729
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
10713
10730
|
runtime: external_exports.object({
|
|
@@ -10715,7 +10732,12 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
10715
10732
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
10716
10733
|
version: external_exports.string().optional(),
|
|
10717
10734
|
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
10718
|
-
workspaces: external_exports.array(external_exports.string()).optional()
|
|
10735
|
+
workspaces: external_exports.array(external_exports.string()).optional(),
|
|
10736
|
+
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
10737
|
+
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
10738
|
+
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
10739
|
+
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
10740
|
+
repos: external_exports.array(external_exports.object({ path: external_exports.string(), remote: external_exports.string() })).optional()
|
|
10719
10741
|
}).optional(),
|
|
10720
10742
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
10721
10743
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -10798,7 +10820,7 @@ var QueueQuestionSchema = external_exports.object({
|
|
|
10798
10820
|
* for a settled question whose reply carried nothing readable. */
|
|
10799
10821
|
answer: external_exports.string().nullable().default(null),
|
|
10800
10822
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
10801
|
-
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
10823
|
+
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
10802
10824
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
10803
10825
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
10804
10826
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
@@ -10890,7 +10912,19 @@ var QueueItemSchema = external_exports.object({
|
|
|
10890
10912
|
said: external_exports.string(),
|
|
10891
10913
|
at: external_exports.string(),
|
|
10892
10914
|
entryId: external_exports.string()
|
|
10893
|
-
}).nullable().optional()
|
|
10915
|
+
}).nullable().optional(),
|
|
10916
|
+
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
10917
|
+
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
10918
|
+
* answer to a question, a voice note filed as work. Null when the only hands on it have been its
|
|
10919
|
+
* agent's; optional, so a hand-built queue (fixtures, the demo) and a door older than
|
|
10920
|
+
* 20260930222356 need not spell it — `work/list.ts`'s `yoursAt` keeps its other sources, so the
|
|
10921
|
+
* Work tab orders as it did before the migration rather than throwing on the missing key.
|
|
10922
|
+
*
|
|
10923
|
+
* It is a FACT, not a reconstruction: `latest` holds one Entry, so an agent's progress note a
|
|
10924
|
+
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
10925
|
+
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
10926
|
+
* Entry with no `about_id` is in neither. */
|
|
10927
|
+
lastPersonAt: external_exports.string().nullable().optional()
|
|
10894
10928
|
});
|
|
10895
10929
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
10896
10930
|
var NoteSourceSchema = external_exports.enum(["app", "call"]);
|
|
@@ -10920,7 +10954,7 @@ var NoteSchema = external_exports.object({
|
|
|
10920
10954
|
assignee: external_exports.string().nullable(),
|
|
10921
10955
|
/** The request thread minted at assignment; null until assigned. */
|
|
10922
10956
|
parentId: external_exports.string().nullable(),
|
|
10923
|
-
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
10957
|
+
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
10924
10958
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
10925
10959
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
10926
10960
|
* a ring at that time); after that it rides like any other. Null = "the very next
|
|
@@ -11182,6 +11216,29 @@ var SnapshotSchema = external_exports.object({
|
|
|
11182
11216
|
computers: external_exports.array(ComputerRowSchema).nullable()
|
|
11183
11217
|
}).nullable()
|
|
11184
11218
|
});
|
|
11219
|
+
var CallRecapSchema = external_exports.object({
|
|
11220
|
+
call: external_exports.object({
|
|
11221
|
+
status: external_exports.string(),
|
|
11222
|
+
startedAt: external_exports.string(),
|
|
11223
|
+
durationMs: external_exports.number().nullable(),
|
|
11224
|
+
agents: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string().nullable() }))
|
|
11225
|
+
}),
|
|
11226
|
+
topics: external_exports.array(external_exports.object({
|
|
11227
|
+
goalId: external_exports.string().uuid(),
|
|
11228
|
+
title: external_exports.string(),
|
|
11229
|
+
owner: external_exports.string(),
|
|
11230
|
+
state: external_exports.string(),
|
|
11231
|
+
questions: external_exports.array(external_exports.object({ id: external_exports.string().uuid(), state: external_exports.string(), title: external_exports.string() })),
|
|
11232
|
+
/** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
|
|
11233
|
+
* their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
|
|
11234
|
+
* summarized like a pre-made option is"), so the row scans like a chosen option and their
|
|
11235
|
+
* own words stay under it. Absent on every line stored before the read wrote them, and on
|
|
11236
|
+
* anything that is not an answer. */
|
|
11237
|
+
lines: external_exports.array(external_exports.object({ entryId: external_exports.string().uuid(), words: external_exports.string(), headline: external_exports.string().optional() }))
|
|
11238
|
+
})),
|
|
11239
|
+
unfiled: external_exports.array(external_exports.object({ lineId: external_exports.string().uuid(), words: external_exports.string(), atMs: external_exports.number() })),
|
|
11240
|
+
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
11241
|
+
});
|
|
11185
11242
|
function sessionSlot(sessionId2) {
|
|
11186
11243
|
const id = sessionId2 ?? sessionId();
|
|
11187
11244
|
return `session:${id.slice(0, 8)}`;
|
|
@@ -12391,7 +12448,7 @@ async function brokerView(deps) {
|
|
|
12391
12448
|
}
|
|
12392
12449
|
|
|
12393
12450
|
// src/workspaces.ts
|
|
12394
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12451
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
12395
12452
|
import { dirname as dirname2, join as join5, resolve } from "path";
|
|
12396
12453
|
import { homedir as homedir5 } from "os";
|
|
12397
12454
|
function workspacesFile() {
|
|
@@ -12432,10 +12489,41 @@ function resolveWakeDir(pinned, deps) {
|
|
|
12432
12489
|
}
|
|
12433
12490
|
return listWorkspaces(deps)[0];
|
|
12434
12491
|
}
|
|
12492
|
+
function originOf(dir) {
|
|
12493
|
+
try {
|
|
12494
|
+
if (!statSync2(join5(dir, ".git")).isDirectory()) return null;
|
|
12495
|
+
const config = readFileSync3(join5(dir, ".git", "config"), "utf8");
|
|
12496
|
+
const section = config.split(/^\[/m).find((s) => /^remote\s+"origin"\]/.test(s));
|
|
12497
|
+
return section?.match(/^\s*url\s*=\s*(.+)$/m)?.[1]?.trim() ?? null;
|
|
12498
|
+
} catch {
|
|
12499
|
+
return null;
|
|
12500
|
+
}
|
|
12501
|
+
}
|
|
12502
|
+
var MAX_CHILDREN = 300;
|
|
12503
|
+
var MAX_REPOS = 100;
|
|
12504
|
+
function listRepos(deps) {
|
|
12505
|
+
const repos = [];
|
|
12506
|
+
for (const root of listWorkspaces(deps)) {
|
|
12507
|
+
const own = originOf(root);
|
|
12508
|
+
if (own) repos.push({ path: root, remote: own });
|
|
12509
|
+
let children = [];
|
|
12510
|
+
try {
|
|
12511
|
+
children = readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name).sort().slice(0, MAX_CHILDREN);
|
|
12512
|
+
} catch {
|
|
12513
|
+
}
|
|
12514
|
+
for (const name of children) {
|
|
12515
|
+
if (repos.length >= MAX_REPOS) return repos;
|
|
12516
|
+
const path = join5(root, name);
|
|
12517
|
+
const remote = originOf(path);
|
|
12518
|
+
if (remote) repos.push({ path, remote });
|
|
12519
|
+
}
|
|
12520
|
+
}
|
|
12521
|
+
return repos.slice(0, MAX_REPOS);
|
|
12522
|
+
}
|
|
12435
12523
|
|
|
12436
12524
|
// src/holder.ts
|
|
12437
12525
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
12438
|
-
import { readFileSync as readFileSync4, readdirSync } from "fs";
|
|
12526
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
12439
12527
|
import { homedir as homedir6 } from "os";
|
|
12440
12528
|
import { join as join6 } from "path";
|
|
12441
12529
|
function isAlive(pid) {
|
|
@@ -12468,7 +12556,7 @@ function holds(pid, procStart, deps) {
|
|
|
12468
12556
|
}
|
|
12469
12557
|
function localHolders(deps = {}) {
|
|
12470
12558
|
const dir = deps.dir ?? join6(process.env.CLAUDE_CONFIG_DIR?.trim() || join6(homedir6(), ".claude"), "sessions");
|
|
12471
|
-
const list = deps.list ?? ((d) =>
|
|
12559
|
+
const list = deps.list ?? ((d) => readdirSync2(d));
|
|
12472
12560
|
const read = deps.read ?? ((f) => readFileSync4(f, "utf8"));
|
|
12473
12561
|
const check = { alive: deps.alive ?? isAlive, started: deps.started ?? psStart };
|
|
12474
12562
|
const held = /* @__PURE__ */ new Map();
|
|
@@ -12496,7 +12584,7 @@ function localHolders(deps = {}) {
|
|
|
12496
12584
|
}
|
|
12497
12585
|
|
|
12498
12586
|
// src/owner.ts
|
|
12499
|
-
import { closeSync as closeSync2, openSync as openSync2, readSync, readdirSync as
|
|
12587
|
+
import { closeSync as closeSync2, openSync as openSync2, readSync, readdirSync as readdirSync3 } from "fs";
|
|
12500
12588
|
import { homedir as homedir7 } from "os";
|
|
12501
12589
|
import { join as join7 } from "path";
|
|
12502
12590
|
var HEAD_BYTES = 64e3;
|
|
@@ -12515,7 +12603,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
12515
12603
|
if (!m) return null;
|
|
12516
12604
|
const prefix = m[1];
|
|
12517
12605
|
const root = deps.projectsDir ?? join7(homedir7(), ".claude", "projects");
|
|
12518
|
-
const list = deps.list ?? ((dir) =>
|
|
12606
|
+
const list = deps.list ?? ((dir) => readdirSync3(dir));
|
|
12519
12607
|
const head = deps.head ?? readHead;
|
|
12520
12608
|
let folders;
|
|
12521
12609
|
try {
|
|
@@ -13956,7 +14044,7 @@ var GetGoalSchema2 = external_exports.object({
|
|
|
13956
14044
|
history: external_exports.boolean().optional(),
|
|
13957
14045
|
/** WHEN A READ HAS CONFUSED YOU. Adds `diagnosis`: every reader that already answers a question
|
|
13958
14046
|
* about this work, each answer attributed to the reader that gave it, and every disagreement
|
|
13959
|
-
* between two of them named. Off by default; `
|
|
14047
|
+
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
13960
14048
|
diagnose: external_exports.boolean().optional()
|
|
13961
14049
|
}).strict();
|
|
13962
14050
|
var GET_GOAL_DESCRIPTION2 = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Another account's Goals, and another agent's Goal that is not below one of yours, are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
@@ -13989,6 +14077,7 @@ function entryWords(entry) {
|
|
|
13989
14077
|
return entry.sources.map((source) => source.text).join("\n");
|
|
13990
14078
|
}
|
|
13991
14079
|
var LIVE_MS2 = 3 * 6e4;
|
|
14080
|
+
var WORKING_MS2 = 30 * 6e4;
|
|
13992
14081
|
var ContextSchema2 = external_exports.object({
|
|
13993
14082
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
13994
14083
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -14059,7 +14148,7 @@ var AttentionSchema2 = external_exports.object({
|
|
|
14059
14148
|
points: external_exports.array(external_exports.string()).nullable(),
|
|
14060
14149
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
14061
14150
|
blocking: external_exports.boolean(),
|
|
14062
|
-
/** Reserved (
|
|
14151
|
+
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
14063
14152
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
14064
14153
|
deadline: external_exports.string().datetime().nullable().optional()
|
|
14065
14154
|
});
|
|
@@ -14110,7 +14199,7 @@ var NotifyRequestFields2 = external_exports.object({
|
|
|
14110
14199
|
* derive agent-side before sealing, so the server only ever shapes plaintext). */
|
|
14111
14200
|
// 10k, not a sentence budget. What the human hears is bounded by the BROKER — it splits
|
|
14112
14201
|
// the ask into topics and gives each one at most three sentences and one question
|
|
14113
|
-
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
14202
|
+
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
14114
14203
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
14115
14204
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
14116
14205
|
ask: external_exports.string().min(1).max(1e4).optional().describe(
|
|
@@ -14187,7 +14276,7 @@ var UserAnswerSchema2 = external_exports.discriminatedUnion("kind", [
|
|
|
14187
14276
|
external_exports.object({ kind: external_exports.literal("clarify"), chunks: external_exports.array(external_exports.string()).min(1) }),
|
|
14188
14277
|
external_exports.object({ kind: external_exports.literal("confirm"), approved: external_exports.boolean() }),
|
|
14189
14278
|
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema2).min(1) }),
|
|
14190
|
-
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
14279
|
+
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
14191
14280
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
14192
14281
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
14193
14282
|
* card the user can reply to — the broker never overrides the user. */
|
|
@@ -14236,7 +14325,7 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
14236
14325
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
14237
14326
|
* a sentence with a hole in it is worse than no sentence. */
|
|
14238
14327
|
note: external_exports.string().optional(),
|
|
14239
|
-
/** The call record rendered for THIS agent (`voice/record-design.md`): the words the
|
|
14328
|
+
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
14240
14329
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
14241
14330
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
14242
14331
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
@@ -14332,10 +14421,11 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
14332
14421
|
agentId: external_exports.string().optional(),
|
|
14333
14422
|
select: SelectShapeSchema2.optional(),
|
|
14334
14423
|
options: external_exports.array(OptionSchema2.omit({ id: true })).optional(),
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14424
|
+
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
14425
|
+
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
14426
|
+
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
14427
|
+
spoken, not where the plan is written, so nothing between the brain and the walk decides
|
|
14428
|
+
anything. A caller's own `beat_s` tuning is what it used to override. */
|
|
14339
14429
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
14340
14430
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
14341
14431
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
@@ -14370,7 +14460,7 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
14370
14460
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
14371
14461
|
* `createdAt`. */
|
|
14372
14462
|
agentStateAt: external_exports.string().datetime().optional(),
|
|
14373
|
-
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (walk/design.md §11, owner
|
|
14463
|
+
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
14374
14464
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
14375
14465
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
14376
14466
|
* Call's cards, and every card of that Call carries the same list: the call screen reads it
|
|
@@ -14431,7 +14521,7 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
14431
14521
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
14432
14522
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
14433
14523
|
onCall: external_exports.literal(true).optional(),
|
|
14434
|
-
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
14524
|
+
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
14435
14525
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
14436
14526
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
14437
14527
|
* The ladder is ACCOUNT-WIDE (#2259): `anchorAt` and `step` are the account's position;
|
|
@@ -14462,7 +14552,7 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
14462
14552
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
14463
14553
|
),
|
|
14464
14554
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
14465
|
-
* urgency (see the main README's "premier use case" + notify/states.md). Drives the
|
|
14555
|
+
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
14466
14556
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
14467
14557
|
blocking: external_exports.boolean().default(false),
|
|
14468
14558
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
@@ -14536,9 +14626,16 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
14536
14626
|
banner: external_exports.boolean(),
|
|
14537
14627
|
push: external_exports.boolean()
|
|
14538
14628
|
}),
|
|
14629
|
+
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
14630
|
+
* they are an enum on a live column across every account, and the rename is a rename of
|
|
14631
|
+
* what people read (owner, 2026-09-30). */
|
|
14539
14632
|
sessionMode: external_exports.enum(["default", "all_calls", "silent"]),
|
|
14540
|
-
|
|
14541
|
-
|
|
14633
|
+
/** DEAD, AND ACCEPTED ANYWAY (#2813). DateNight is inbox-only with no way back, so nothing
|
|
14634
|
+
* reads this. It stays OPTIONAL rather than deleted because a phone on the old bundle
|
|
14635
|
+
* PATCHes the whole settings object and would be refused for sending a key we stopped
|
|
14636
|
+
* wanting — the same trap `voiceMode` below already documents. #2813 drops the field and
|
|
14637
|
+
* its column once no installed build still sends it. */
|
|
14638
|
+
silentPush: external_exports.boolean().optional(),
|
|
14542
14639
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
14543
14640
|
improveConsent: external_exports.boolean(),
|
|
14544
14641
|
missedCall: MissedCallSchema2.default("backoff_standard"),
|
|
@@ -14549,12 +14646,16 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
14549
14646
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
14550
14647
|
* write, 'hosted' on read (see store.ts). */
|
|
14551
14648
|
voiceMode: external_exports.enum(["hosted", "on_device"]).optional(),
|
|
14552
|
-
/** Talk — after you answer, the next step is read aloud (walk/design.md §6). ALWAYS ON until
|
|
14649
|
+
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
14553
14650
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
14554
14651
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
14555
14652
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
14556
14653
|
* read (see store.ts). */
|
|
14557
14654
|
talk: external_exports.boolean().optional(),
|
|
14655
|
+
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
14656
|
+
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
14657
|
+
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
14658
|
+
callDiagnostics: external_exports.boolean().optional(),
|
|
14558
14659
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
14559
14660
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
14560
14661
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -14563,9 +14664,6 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
14563
14664
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
14564
14665
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
14565
14666
|
voiceTuning: external_exports.record(external_exports.string(), external_exports.union([external_exports.number(), external_exports.string()])).optional(),
|
|
14566
|
-
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
14567
|
-
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
14568
|
-
pstnCalls: external_exports.boolean().optional(),
|
|
14569
14667
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
14570
14668
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
14571
14669
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -14609,7 +14707,7 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
14609
14707
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
14610
14708
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
14611
14709
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
14612
|
-
*
|
|
14710
|
+
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
14613
14711
|
kind: external_exports.enum(["device", "agent"]).optional(),
|
|
14614
14712
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
14615
14713
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
@@ -14627,7 +14725,7 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
14627
14725
|
* user on the agent's own page. null = no ceiling (today's behaviour for every
|
|
14628
14726
|
* connection). Android binds importance to a relationship rather than to each message,
|
|
14629
14727
|
* and that is the thing our roster could not say: "Marlow may always call me; Otto
|
|
14630
|
-
* never may" (navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
|
|
14728
|
+
* never may" (docs/clients/app/party/navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
|
|
14631
14729
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
14632
14730
|
* set once must not overrule a rule they set about one agent. */
|
|
14633
14731
|
reach: NotifyLevelSchema2.nullable().optional(),
|
|
@@ -14638,7 +14736,14 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
14638
14736
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
14639
14737
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
14640
14738
|
lastSeenAt: external_exports.string().datetime().nullable().optional(),
|
|
14641
|
-
/**
|
|
14739
|
+
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
14740
|
+
* its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
|
|
14741
|
+
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
14742
|
+
lastWorkedAt: external_exports.string().datetime().nullable().optional(),
|
|
14743
|
+
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
14744
|
+
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
14745
|
+
oldestReadyAt: external_exports.string().datetime().nullable().optional(),
|
|
14746
|
+
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
14642
14747
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
14643
14748
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
14644
14749
|
runtime: external_exports.object({
|
|
@@ -14646,7 +14751,12 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
14646
14751
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
14647
14752
|
version: external_exports.string().optional(),
|
|
14648
14753
|
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
14649
|
-
workspaces: external_exports.array(external_exports.string()).optional()
|
|
14754
|
+
workspaces: external_exports.array(external_exports.string()).optional(),
|
|
14755
|
+
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
14756
|
+
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
14757
|
+
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
14758
|
+
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
14759
|
+
repos: external_exports.array(external_exports.object({ path: external_exports.string(), remote: external_exports.string() })).optional()
|
|
14650
14760
|
}).optional(),
|
|
14651
14761
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
14652
14762
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -14729,7 +14839,7 @@ var QueueQuestionSchema2 = external_exports.object({
|
|
|
14729
14839
|
* for a settled question whose reply carried nothing readable. */
|
|
14730
14840
|
answer: external_exports.string().nullable().default(null),
|
|
14731
14841
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
14732
|
-
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
14842
|
+
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
14733
14843
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
14734
14844
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
14735
14845
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
@@ -14821,7 +14931,19 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
14821
14931
|
said: external_exports.string(),
|
|
14822
14932
|
at: external_exports.string(),
|
|
14823
14933
|
entryId: external_exports.string()
|
|
14824
|
-
}).nullable().optional()
|
|
14934
|
+
}).nullable().optional(),
|
|
14935
|
+
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
14936
|
+
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
14937
|
+
* answer to a question, a voice note filed as work. Null when the only hands on it have been its
|
|
14938
|
+
* agent's; optional, so a hand-built queue (fixtures, the demo) and a door older than
|
|
14939
|
+
* 20260930222356 need not spell it — `work/list.ts`'s `yoursAt` keeps its other sources, so the
|
|
14940
|
+
* Work tab orders as it did before the migration rather than throwing on the missing key.
|
|
14941
|
+
*
|
|
14942
|
+
* It is a FACT, not a reconstruction: `latest` holds one Entry, so an agent's progress note a
|
|
14943
|
+
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
14944
|
+
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
14945
|
+
* Entry with no `about_id` is in neither. */
|
|
14946
|
+
lastPersonAt: external_exports.string().nullable().optional()
|
|
14825
14947
|
});
|
|
14826
14948
|
var COLD_AFTER_MS2 = 3 * 24 * 60 * 60 * 1e3;
|
|
14827
14949
|
var NoteSourceSchema2 = external_exports.enum(["app", "call"]);
|
|
@@ -14851,7 +14973,7 @@ var NoteSchema2 = external_exports.object({
|
|
|
14851
14973
|
assignee: external_exports.string().nullable(),
|
|
14852
14974
|
/** The request thread minted at assignment; null until assigned. */
|
|
14853
14975
|
parentId: external_exports.string().nullable(),
|
|
14854
|
-
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
14976
|
+
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
14855
14977
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
14856
14978
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
14857
14979
|
* a ring at that time); after that it rides like any other. Null = "the very next
|
|
@@ -15111,6 +15233,29 @@ var SnapshotSchema2 = external_exports.object({
|
|
|
15111
15233
|
computers: external_exports.array(ComputerRowSchema2).nullable()
|
|
15112
15234
|
}).nullable()
|
|
15113
15235
|
});
|
|
15236
|
+
var CallRecapSchema2 = external_exports.object({
|
|
15237
|
+
call: external_exports.object({
|
|
15238
|
+
status: external_exports.string(),
|
|
15239
|
+
startedAt: external_exports.string(),
|
|
15240
|
+
durationMs: external_exports.number().nullable(),
|
|
15241
|
+
agents: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string().nullable() }))
|
|
15242
|
+
}),
|
|
15243
|
+
topics: external_exports.array(external_exports.object({
|
|
15244
|
+
goalId: external_exports.string().uuid(),
|
|
15245
|
+
title: external_exports.string(),
|
|
15246
|
+
owner: external_exports.string(),
|
|
15247
|
+
state: external_exports.string(),
|
|
15248
|
+
questions: external_exports.array(external_exports.object({ id: external_exports.string().uuid(), state: external_exports.string(), title: external_exports.string() })),
|
|
15249
|
+
/** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
|
|
15250
|
+
* their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
|
|
15251
|
+
* summarized like a pre-made option is"), so the row scans like a chosen option and their
|
|
15252
|
+
* own words stay under it. Absent on every line stored before the read wrote them, and on
|
|
15253
|
+
* anything that is not an answer. */
|
|
15254
|
+
lines: external_exports.array(external_exports.object({ entryId: external_exports.string().uuid(), words: external_exports.string(), headline: external_exports.string().optional() }))
|
|
15255
|
+
})),
|
|
15256
|
+
unfiled: external_exports.array(external_exports.object({ lineId: external_exports.string().uuid(), words: external_exports.string(), atMs: external_exports.number() })),
|
|
15257
|
+
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
15258
|
+
});
|
|
15114
15259
|
|
|
15115
15260
|
// src/paigy/activity.ts
|
|
15116
15261
|
function shortenPaths(s) {
|
|
@@ -15411,7 +15556,7 @@ var AcpDriver = class {
|
|
|
15411
15556
|
var frame2 = (body) => JSON.stringify({ jsonrpc: "2.0", ...body });
|
|
15412
15557
|
|
|
15413
15558
|
// src/paigy/inbox.ts
|
|
15414
|
-
import { mkdirSync as mkdirSync3, readdirSync as
|
|
15559
|
+
import { mkdirSync as mkdirSync3, readdirSync as readdirSync4, renameSync, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
15415
15560
|
import { join as join8 } from "path";
|
|
15416
15561
|
import { homedir as homedir8 } from "os";
|
|
15417
15562
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
@@ -15423,7 +15568,7 @@ function openInbox(dir) {
|
|
|
15423
15568
|
const receipt = (name) => join8(dir, "taken", `${name}.note`);
|
|
15424
15569
|
const taken = (name) => {
|
|
15425
15570
|
try {
|
|
15426
|
-
return
|
|
15571
|
+
return statSync3(receipt(name)).mtimeMs;
|
|
15427
15572
|
} catch (error) {
|
|
15428
15573
|
if (missing(error)) return null;
|
|
15429
15574
|
throw error;
|
|
@@ -15467,7 +15612,7 @@ var alive = (pid) => {
|
|
|
15467
15612
|
};
|
|
15468
15613
|
function inboxFor(root = INBOX_ROOT) {
|
|
15469
15614
|
try {
|
|
15470
|
-
for (const name of
|
|
15615
|
+
for (const name of readdirSync4(root)) {
|
|
15471
15616
|
const pid = Number(name.split("-")[0]);
|
|
15472
15617
|
if (!Number.isInteger(pid) || !alive(pid)) rmSync2(join8(root, name), { recursive: true, force: true });
|
|
15473
15618
|
}
|
|
@@ -16494,7 +16639,9 @@ function startHost(opts) {
|
|
|
16494
16639
|
void heartbeat({
|
|
16495
16640
|
version: VERSION,
|
|
16496
16641
|
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
16497
|
-
workspaces: listWorkspaces(opts.wsDeps)
|
|
16642
|
+
workspaces: listWorkspaces(opts.wsDeps),
|
|
16643
|
+
// The repos inside them, so a session started for work on one opens IN it (Goal 26982211).
|
|
16644
|
+
repos: listRepos(opts.wsDeps)
|
|
16498
16645
|
}, asHost).catch(() => {
|
|
16499
16646
|
});
|
|
16500
16647
|
void listenSlots().then(sweepSlots).catch(() => {
|
|
@@ -16849,7 +16996,7 @@ ipcMain.handle("paigy:status", () => ({
|
|
|
16849
16996
|
paired: Boolean(deviceToken()),
|
|
16850
16997
|
device: Boolean(readToken(DEVICE_SLOT)),
|
|
16851
16998
|
sessions: host?.sessions().map(([, label]) => label) ?? [],
|
|
16852
|
-
// The party (companion.md / party design.md): who lives on this machine and what
|
|
16999
|
+
// The party (docs/clients/desktop/companion.md / party design.md): who lives on this machine and what
|
|
16853
17000
|
// each one is doing THIS SECOND — the one thing the laptop knows better than the phone.
|
|
16854
17001
|
roster: host?.roster() ?? readHostState()?.roster ?? []
|
|
16855
17002
|
}));
|