@paigy/harness 0.3.10 → 0.3.12

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 CHANGED
@@ -125,6 +125,8 @@ var init_catalog = __esm({
125
125
  name: "agy",
126
126
  label: "Antigravity",
127
127
  cli: "agy",
128
+ // `agy` has no ACP mode; its adapter ships inside the harness (`harness/agy-acp.ts`).
129
+ adapter: "paigy-agy-acp",
128
130
  authProbe: ["agy", "help"],
129
131
  loginHint: "Install and set up Antigravity (`agy`).",
130
132
  installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
@@ -22757,11 +22759,17 @@ function sessionId() {
22757
22759
  return SESSION_ID;
22758
22760
  }
22759
22761
  async function reach(url, init) {
22762
+ return send(url, init, SESSION_ID);
22763
+ }
22764
+ function reachAs(session) {
22765
+ return (url, init) => send(url, init, session);
22766
+ }
22767
+ async function send(url, init, session) {
22760
22768
  try {
22761
22769
  const headers = {
22762
22770
  ...init?.headers,
22763
22771
  "x-paigy-instance": INSTANCE_ID,
22764
- "x-paigy-session": SESSION_ID
22772
+ "x-paigy-session": session
22765
22773
  };
22766
22774
  return await fetch(url, { ...init, headers, dispatcher: await proxy() });
22767
22775
  } catch (e) {
@@ -23893,7 +23901,7 @@ async function listNotes(opts = {}) {
23893
23901
  }
23894
23902
  async function listConnections(opts = {}) {
23895
23903
  const res = ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
23896
- headers: { authorization: `Bearer ${authToken(opts.token)}` }
23904
+ headers: { authorization: `Bearer ${authToken(opts.token)}`, "x-paigy-model": "goal-entry-v1" }
23897
23905
  }));
23898
23906
  if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
23899
23907
  return await res.json();
@@ -23919,14 +23927,14 @@ async function acceptTriage(proposalId, body, opts = {}) {
23919
23927
  async function contact(input, opts = {}) {
23920
23928
  const parsed = ContactSchema.parse(input);
23921
23929
  opts.signal?.throwIfAborted();
23922
- const send2 = opts.reach ?? reach;
23930
+ const send22 = opts.reach ?? reach;
23923
23931
  const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
23924
23932
  let deliveryId;
23925
23933
  if ("deliveryId" in parsed) deliveryId = parsed.deliveryId;
23926
23934
  else {
23927
23935
  if (!opts.reach && readKeyFile()?.e2ee) throw new Error("Goal contact does not support E2EE yet; refusing to send plaintext.");
23928
23936
  const shaped = deriveAsk(NotifyRequestSchema.parse({ ask: parsed.ask, waiting: parsed.waiting, options: parsed.options }));
23929
- const res = ensureAuthed(await send2(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
23937
+ const res = ensureAuthed(await send22(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
23930
23938
  method: "POST",
23931
23939
  headers,
23932
23940
  signal: opts.signal,
@@ -23938,11 +23946,9 @@ async function contact(input, opts = {}) {
23938
23946
  if (!deliveryId) throw new Error("Goal contact returned no Delivery identity");
23939
23947
  }
23940
23948
  const read = async (signal2) => {
23941
- const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
23949
+ const res = ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
23942
23950
  if (!res.ok) await fail("read_delivery", res);
23943
- const delivery = await res.json();
23944
- return delivery.kind === "notification" ? { ...delivery, message: `${delivery.message}
23945
- Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answers. Do not poll this Notification.` } : delivery;
23951
+ return await res.json();
23946
23952
  };
23947
23953
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
23948
23954
  if (opts.waits === false) return read(opts.signal);
@@ -24062,7 +24068,7 @@ async function subscribeWake(onNudge, opts = {}) {
24062
24068
  }
24063
24069
  };
24064
24070
  }
24065
- var import_undici, require2, __create2, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2, __copyProps2, __toESM2, require_nacl_fast, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, SESSION_ID, ignoreOverride, defaultOptions, getDefaultOptions, getRefs, getRelativePath, parseCatchDef, integerDateParser, isJsonSchema7AllOfType, emojiRegex2, zodPatterns, ALPHA_NUMERIC, primitiveMappings, asAnyOf, parseOptionalDef, parsePipelineDef, parseReadonlyDef, selectParser, get$ref, addMeta, zodToJsonSchema, OPTIONS_MIN, OPTIONS_MAX, StartContactSchema, ContactSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, CreateGoalToolSchema, CREATE_GOAL_DESCRIPTION, UpdateGoalSchema, UpdateGoalToolSchema, ClaimGoalSchema, GetGoalSchema, GET_GOAL_DESCRIPTION, UPDATE_GOAL_DESCRIPTION, CLAIM_GOAL_DESCRIPTION, CHECK_REPLIES_DESCRIPTION, CheckRepliesSchema, GetThreadSchema, GET_THREAD_DESCRIPTION, SearchThreadsSchema, SEARCH_THREADS_DESCRIPTION, AGENT_TOOLS, AGENT_TOOL_NAMES, ContextSchema, ParticipantSchema, TransformSchema, OptionSchema, VisualSchema, NotifyLevelSchema, SelectShapeSchema, ReceiptEventSchema, AttentionSchema, NotifyRequestFields, NotifyRequestSchema, NotifyStatusSchema, AgentStateSchema, TurnSchema, UserAnswerSchema, IntentSchema, RideAlongSchema, AwaitItemSchema, VoiceKeySchema, AgendaTurnSchema, CLAIM_STALE_MS, InboxItemSchema, APNS_TOKEN_RE, PushTokenSchema, MissedCallSchema, BrokerTuningSchema, UserSettingsSchema, HistoryItemSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, MoveRingSchema, MoveSchema, QueueQuestionSchema, QueueItemSchema, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, StatusSchema, EnvelopeRecipientSchema, EnvelopeHeaderSchema, EnvelopeSchema, SealedAnswerSchema, DeviceCredentialSchema, DeviceRosterSchema, WakeNudgeSchema, PairingStatusSchema, PairingRevealSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenSchema, SupportRequestSchema, NotificationFeedbackKindSchema, FeedbackResolutionSchema, FeedbackOutcomeSchema, import_tweetnacl, import_tweetnacl2, import_tweetnacl3, import_tweetnacl4, import_tweetnacl5, AGENT_NAME, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, ApiError, AWAIT_WINDOW_MS, tokenOverride, authToken, HEARTBEAT_MS, RETRY_MS, JOIN, lines;
24071
+ var import_undici, require2, __create2, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2, __copyProps2, __toESM2, require_nacl_fast, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, SESSION_ID, ignoreOverride, defaultOptions, getDefaultOptions, getRefs, getRelativePath, parseCatchDef, integerDateParser, isJsonSchema7AllOfType, emojiRegex2, zodPatterns, ALPHA_NUMERIC, primitiveMappings, asAnyOf, parseOptionalDef, parsePipelineDef, parseReadonlyDef, selectParser, get$ref, addMeta, zodToJsonSchema, OPTIONS_MIN, OPTIONS_MAX, StartContactSchema, ContactSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, CreateGoalToolSchema, CREATE_GOAL_DESCRIPTION, UpdateGoalSchema, UpdateGoalToolSchema, ClaimGoalSchema, GetGoalSchema, GET_GOAL_DESCRIPTION, UPDATE_GOAL_DESCRIPTION, CLAIM_GOAL_DESCRIPTION, CHECK_REPLIES_DESCRIPTION, CheckRepliesSchema, GetThreadSchema, GET_THREAD_DESCRIPTION, SearchThreadsSchema, SEARCH_THREADS_DESCRIPTION, AGENT_TOOLS, AGENT_TOOL_NAMES, LIVE_MS, ContextSchema, ParticipantSchema, TransformSchema, OptionSchema, VisualSchema, NotifyLevelSchema, SelectShapeSchema, ReceiptEventSchema, AttentionSchema, NotifyRequestFields, NotifyRequestSchema, NotifyStatusSchema, AgentStateSchema, TurnSchema, UserAnswerSchema, IntentSchema, RideAlongSchema, AwaitItemSchema, VoiceKeySchema, AgendaTurnSchema, CLAIM_STALE_MS, InboxItemSchema, APNS_TOKEN_RE, PushTokenSchema, MissedCallSchema, BrokerTuningSchema, UserSettingsSchema, HistoryItemSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, MoveRingSchema, MoveSchema, QueueQuestionSchema, QueueItemSchema, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, EnvelopeRecipientSchema, EnvelopeHeaderSchema, EnvelopeSchema, SealedAnswerSchema, DeviceCredentialSchema, DeviceRosterSchema, WakeNudgeSchema, PairingStatusSchema, PairingRevealSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenSchema, SupportRequestSchema, NotificationFeedbackKindSchema, FeedbackResolutionSchema, FeedbackOutcomeSchema, import_tweetnacl, import_tweetnacl2, import_tweetnacl3, import_tweetnacl4, import_tweetnacl5, AGENT_NAME, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, ApiError, AWAIT_WINDOW_MS, tokenOverride, authToken, HEARTBEAT_MS, RETRY_MS, JOIN, lines;
24066
24072
  var init_dist = __esm({
24067
24073
  "../../packages/sdk/dist/index.js"() {
24068
24074
  "use strict";
@@ -26735,6 +26741,9 @@ var init_dist = __esm({
26735
26741
  CONTACT_DESCRIPTION = "Contact the user about exactly one existing Goal: pass goalIds:[goalId], ask, channel:'notification'|'call', and waiting:'none'|'hard'. Options supply choices. Notification returns immediately; collect durable answers with claim_goal/get_goal. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Continuation sends nothing and rereads the same durable evidence, including previously read answers. Entries retain authorship and provenance; accepted decisions are separate from quoted speech. Call state open does not mean ringing. Unsupported: soft waiting, multiple Goals/questions, outcome admission, and re-presentation of an existing request. Create a Goal explicitly first; never resend a pending ask to continue waiting.";
26736
26742
  CreateGoalSchema = external_exports.object({
26737
26743
  outcome: external_exports.string().trim().min(1).max(1e4),
26744
+ /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
26745
+ * rings"). Omit it and the brain writes one at admission from the outcome. */
26746
+ title: external_exports.string().trim().min(1).max(80).optional(),
26738
26747
  ownerParticipant: external_exports.string().trim().min(1).optional(),
26739
26748
  idempotencyKey: external_exports.string().trim().min(1).max(200),
26740
26749
  /** A past conversation this Goal should be read against — History's "new session from this"
@@ -26754,11 +26763,14 @@ var init_dist = __esm({
26754
26763
  CreateGoalToolSchema = CreateGoalSchema.extend({
26755
26764
  idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
26756
26765
  }).strict();
26757
- CREATE_GOAL_DESCRIPTION = "Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.";
26766
+ CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
26758
26767
  UpdateGoalSchema = external_exports.object({
26759
26768
  revision: external_exports.number().int().positive(),
26760
26769
  changes: external_exports.object({
26761
26770
  outcome: external_exports.string().trim().min(1).max(1e4).optional(),
26771
+ /** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
26772
+ * brain writes one at admission; this is the owner saying it better. Null clears it. */
26773
+ title: external_exports.string().trim().min(1).max(80).nullable().optional(),
26762
26774
  ownerParticipant: external_exports.string().trim().min(1).optional(),
26763
26775
  parentGoalId: external_exports.string().uuid().nullable().optional(),
26764
26776
  dependencies: external_exports.array(external_exports.object({ goalId: external_exports.string().uuid(), gate: external_exports.enum(["start", "finish"]) }).strict()).optional(),
@@ -26774,10 +26786,10 @@ var init_dist = __esm({
26774
26786
  UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: external_exports.string().uuid() }).strict();
26775
26787
  ClaimGoalSchema = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
26776
26788
  GetGoalSchema = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
26777
- GET_GOAL_DESCRIPTION = "Read the current authorized Goal brief: state, owner, blockers, open decisions, progress, and the next operation. Foreign or sibling-owned Goals are not disclosed.";
26778
- UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, and review acknowledgement are explicit; stale revisions are rejected. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the new revision and a prose summary.";
26779
- CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns a Goal-scoped brief, current revision, blockers, and the next valid operation. Claiming creates or renews the execution lease.";
26780
- CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, the newest words in brief, accepted decisions and open decision needs, without the Entries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
26789
+ 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), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
26790
+ UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the Goal as get_goal reads it, at its new revision.";
26791
+ CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
26792
+ CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, how many decisions are still open, and the newest words in brief. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
26781
26793
  CheckRepliesSchema = external_exports.object({}).strict();
26782
26794
  GetThreadSchema = external_exports.object({
26783
26795
  parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
@@ -26798,6 +26810,7 @@ var init_dist = __esm({
26798
26810
  { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
26799
26811
  ];
26800
26812
  AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
26813
+ LIVE_MS = 3 * 6e4;
26801
26814
  ContextSchema = external_exports.object({
26802
26815
  title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
26803
26816
  description: external_exports.array(external_exports.string().min(1)).describe(
@@ -27579,7 +27592,19 @@ var init_dist = __esm({
27579
27592
  /** The ruling in the person's own words, from the contribution that replied — not the
27580
27593
  * option id, which is not something anyone reads back. Null while it is open, and null
27581
27594
  * for a settled question whose reply carried nothing readable. */
27582
- answer: external_exports.string().nullable().default(null)
27595
+ answer: external_exports.string().nullable().default(null),
27596
+ /** The Goal this question belongs to — a step knows its Goal on its own, not only through
27597
+ * an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
27598
+ * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
27599
+ * order, its route, home's trees and the list of steps all take a step's Goal from here, so
27600
+ * this is the field they agree through rather than each re-deriving it from the row it
27601
+ * arrived under. Required because the API projects it on every need it sends. */
27602
+ goalId: external_exports.string(),
27603
+ /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
27604
+ * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
27605
+ * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
27606
+ * gate at all. */
27607
+ blocked: external_exports.boolean().default(false)
27583
27608
  });
27584
27609
  QueueItemSchema = external_exports.object({
27585
27610
  id: external_exports.string(),
@@ -27718,25 +27743,6 @@ var init_dist = __esm({
27718
27743
  * carries credentials; only a `poll` registration can come back with null here. */
27719
27744
  realtime: external_exports.object({ url: external_exports.string(), anonKey: external_exports.string() }).nullable()
27720
27745
  });
27721
- StatusSchema = external_exports.object({
27722
- name: external_exports.string(),
27723
- sessionMode: external_exports.enum(["default", "all_calls", "silent"]),
27724
- /** A phone is registered for push/ring (any push token on the account). */
27725
- phone: external_exports.boolean(),
27726
- /** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
27727
- * it never picked up. THE SAME NUMBER the harness's wake gate reads
27728
- * (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
27729
- * zero while the sweep sees one is two ideas of "waiting".
27730
- *
27731
- * Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
27732
- * that nobody spawned, so a terminal session only learns of work by asking. The harness
27733
- * used to paper over that by spawning a SECOND process on the identity; now it stands
27734
- * back, correctly, and the person sitting at the terminal is the one who can act. A
27735
- * coffee-beans request sat unread for three days.
27736
- *
27737
- * Optional: an older API sends no field, and the statusline then renders exactly as before. */
27738
- waiting: external_exports.number().int().nonnegative().optional()
27739
- });
27740
27746
  EnvelopeRecipientSchema = external_exports.object({
27741
27747
  keyId: external_exports.string(),
27742
27748
  epk: external_exports.string(),
@@ -32475,7 +32481,7 @@ var require_lib = __commonJS({
32475
32481
 
32476
32482
  // src/triage/judge.ts
32477
32483
  import { spawnSync as spawnSync2 } from "child_process";
32478
- import { homedir as homedir6 } from "os";
32484
+ import { homedir as homedir7 } from "os";
32479
32485
  import { delimiter as delimiter3 } from "path";
32480
32486
  function pickOllamaModel(tags) {
32481
32487
  if (tags.length === 0) return null;
@@ -32558,7 +32564,7 @@ async function judgeFor(asked, deps = {}) {
32558
32564
  const out = spawnSync2(bin, runner.argv(null), {
32559
32565
  input: prompt,
32560
32566
  encoding: "utf8",
32561
- cwd: homedir6(),
32567
+ cwd: homedir7(),
32562
32568
  timeout: RUN_TIMEOUT_MS,
32563
32569
  maxBuffer: 32 * 1024 * 1024,
32564
32570
  env: { ...process.env, PATH: [process.env["PATH"], ...probeDirs()].filter(Boolean).join(delimiter3) }
@@ -33148,8 +33154,11 @@ var import_qrcode = __toESM(require_lib(), 1);
33148
33154
  import { hostname } from "os";
33149
33155
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
33150
33156
  import { execFile as execFile2, execSync } from "child_process";
33151
- import { homedir as homedir7 } from "os";
33152
- import { join as join6 } from "path";
33157
+ import { homedir as homedir8 } from "os";
33158
+ import { join as join7 } from "path";
33159
+
33160
+ // src/run.ts
33161
+ init_dist();
33153
33162
 
33154
33163
  // src/harness/session.ts
33155
33164
  import { spawn } from "child_process";
@@ -33164,15 +33173,19 @@ function optionFor(options, decision) {
33164
33173
  return options.find((o) => o.kind === want)?.optionId ?? null;
33165
33174
  }
33166
33175
  function createAcpDriver(opts) {
33167
- return new AcpDriver(opts.cwd, opts.mode);
33176
+ return new AcpDriver(opts.cwd, opts.mode, opts.resume);
33168
33177
  }
33169
33178
  var AcpDriver = class {
33170
- constructor(cwd, mode) {
33179
+ /** `resume`: the id of an existing session to continue rather than open a new one (#1637)
33180
+ * the session a bound identity belongs to, so the process this drives IS its holder. */
33181
+ constructor(cwd, mode, resume) {
33171
33182
  this.cwd = cwd;
33172
33183
  this.mode = mode;
33184
+ this.resume = resume;
33173
33185
  }
33174
33186
  cwd;
33175
33187
  mode;
33188
+ resume;
33176
33189
  nextId = 1;
33177
33190
  initId;
33178
33191
  sessionNewId;
@@ -33256,15 +33269,15 @@ var AcpDriver = class {
33256
33269
  this.sessionNewId = this.nextId++;
33257
33270
  return {
33258
33271
  events: [],
33259
- writes: [frame2({ id: this.sessionNewId, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } })]
33272
+ writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } : { id: this.sessionNewId, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } })]
33260
33273
  };
33261
33274
  }
33262
33275
  if (msg.id === this.sessionNewId) {
33263
33276
  this.sessionNewId = void 0;
33264
- const sessionId2 = msg.result?.sessionId;
33277
+ const sessionId2 = msg.error ? void 0 : msg.result?.sessionId ?? this.resume;
33265
33278
  if (!sessionId2) {
33266
33279
  return {
33267
- events: [{ kind: "error", message: `session/new failed: ${msg.error?.message ?? "no sessionId"}` }],
33280
+ events: [{ kind: "error", message: `${this.resume ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}` }],
33268
33281
  writes: []
33269
33282
  };
33270
33283
  }
@@ -33379,7 +33392,7 @@ init_catalog();
33379
33392
  var ADAPTER_BIN = {
33380
33393
  claude: "claude-agent-acp",
33381
33394
  codex: "codex-acp",
33382
- agy: "agy"
33395
+ agy: "paigy-agy-acp"
33383
33396
  };
33384
33397
  function splitLines(buffer, chunk) {
33385
33398
  const combined = buffer + chunk;
@@ -33401,7 +33414,8 @@ function startSession(opts) {
33401
33414
  env: {
33402
33415
  ...process.env,
33403
33416
  PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
33404
- ...opts.token ? { PAIGY_TOKEN: opts.token } : {}
33417
+ ...opts.token ? { PAIGY_TOKEN: opts.token } : {},
33418
+ ...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {}
33405
33419
  },
33406
33420
  stdio: ["pipe", "pipe", "pipe"]
33407
33421
  });
@@ -33421,7 +33435,7 @@ function startSession(opts) {
33421
33435
  opts.onEvent({ kind: "error", message: e.message });
33422
33436
  opts.onExit?.();
33423
33437
  });
33424
- const driver = createAcpDriver({ cwd, mode: opts.mode });
33438
+ const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.resume ? { resume: opts.resume } : {} });
33425
33439
  let buffer = "";
33426
33440
  child.stdout?.on("data", (chunk) => {
33427
33441
  const { lines: lines2, rest } = splitLines(buffer, String(chunk));
@@ -34803,6 +34817,9 @@ var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
34803
34817
  var CONTACT_DESCRIPTION2 = "Contact the user about exactly one existing Goal: pass goalIds:[goalId], ask, channel:'notification'|'call', and waiting:'none'|'hard'. Options supply choices. Notification returns immediately; collect durable answers with claim_goal/get_goal. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Continuation sends nothing and rereads the same durable evidence, including previously read answers. Entries retain authorship and provenance; accepted decisions are separate from quoted speech. Call state open does not mean ringing. Unsupported: soft waiting, multiple Goals/questions, outcome admission, and re-presentation of an existing request. Create a Goal explicitly first; never resend a pending ask to continue waiting.";
34804
34818
  var CreateGoalSchema2 = external_exports.object({
34805
34819
  outcome: external_exports.string().trim().min(1).max(1e4),
34820
+ /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
34821
+ * rings"). Omit it and the brain writes one at admission from the outcome. */
34822
+ title: external_exports.string().trim().min(1).max(80).optional(),
34806
34823
  ownerParticipant: external_exports.string().trim().min(1).optional(),
34807
34824
  idempotencyKey: external_exports.string().trim().min(1).max(200),
34808
34825
  /** A past conversation this Goal should be read against — History's "new session from this"
@@ -34822,11 +34839,14 @@ var CreateGoalSchema2 = external_exports.object({
34822
34839
  var CreateGoalToolSchema2 = CreateGoalSchema2.extend({
34823
34840
  idempotencyKey: CreateGoalSchema2.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
34824
34841
  }).strict();
34825
- var CREATE_GOAL_DESCRIPTION2 = "Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.";
34842
+ var CREATE_GOAL_DESCRIPTION2 = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
34826
34843
  var UpdateGoalSchema2 = external_exports.object({
34827
34844
  revision: external_exports.number().int().positive(),
34828
34845
  changes: external_exports.object({
34829
34846
  outcome: external_exports.string().trim().min(1).max(1e4).optional(),
34847
+ /** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
34848
+ * brain writes one at admission; this is the owner saying it better. Null clears it. */
34849
+ title: external_exports.string().trim().min(1).max(80).nullable().optional(),
34830
34850
  ownerParticipant: external_exports.string().trim().min(1).optional(),
34831
34851
  parentGoalId: external_exports.string().uuid().nullable().optional(),
34832
34852
  dependencies: external_exports.array(external_exports.object({ goalId: external_exports.string().uuid(), gate: external_exports.enum(["start", "finish"]) }).strict()).optional(),
@@ -34842,10 +34862,10 @@ var UpdateGoalSchema2 = external_exports.object({
34842
34862
  var UpdateGoalToolSchema2 = UpdateGoalSchema2.omit({ operationId: true }).extend({ goalId: external_exports.string().uuid() }).strict();
34843
34863
  var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
34844
34864
  var GetGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
34845
- var GET_GOAL_DESCRIPTION2 = "Read the current authorized Goal brief: state, owner, blockers, open decisions, progress, and the next operation. Foreign or sibling-owned Goals are not disclosed.";
34846
- var UPDATE_GOAL_DESCRIPTION2 = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, and review acknowledgement are explicit; stale revisions are rejected. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the new revision and a prose summary.";
34847
- var CLAIM_GOAL_DESCRIPTION2 = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns a Goal-scoped brief, current revision, blockers, and the next valid operation. Claiming creates or renews the execution lease.";
34848
- var CHECK_REPLIES_DESCRIPTION2 = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, the newest words in brief, accepted decisions and open decision needs, without the Entries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
34865
+ 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), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
34866
+ var UPDATE_GOAL_DESCRIPTION2 = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the Goal as get_goal reads it, at its new revision.";
34867
+ var CLAIM_GOAL_DESCRIPTION2 = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
34868
+ var CHECK_REPLIES_DESCRIPTION2 = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, how many decisions are still open, and the newest words in brief. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
34849
34869
  var CheckRepliesSchema2 = external_exports.object({}).strict();
34850
34870
  var GetThreadSchema2 = external_exports.object({
34851
34871
  parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
@@ -34881,6 +34901,7 @@ function entryWords(entry) {
34881
34901
  }
34882
34902
  return entry.sources.map((source) => source.text).join("\n");
34883
34903
  }
34904
+ var LIVE_MS2 = 3 * 6e4;
34884
34905
  var ContextSchema2 = external_exports.object({
34885
34906
  title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
34886
34907
  description: external_exports.array(external_exports.string().min(1)).describe(
@@ -35662,7 +35683,19 @@ var QueueQuestionSchema2 = external_exports.object({
35662
35683
  /** The ruling in the person's own words, from the contribution that replied — not the
35663
35684
  * option id, which is not something anyone reads back. Null while it is open, and null
35664
35685
  * for a settled question whose reply carried nothing readable. */
35665
- answer: external_exports.string().nullable().default(null)
35686
+ answer: external_exports.string().nullable().default(null),
35687
+ /** The Goal this question belongs to — a step knows its Goal on its own, not only through
35688
+ * an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
35689
+ * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
35690
+ * order, its route, home's trees and the list of steps all take a step's Goal from here, so
35691
+ * this is the field they agree through rather than each re-deriving it from the row it
35692
+ * arrived under. Required because the API projects it on every need it sends. */
35693
+ goalId: external_exports.string(),
35694
+ /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
35695
+ * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
35696
+ * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
35697
+ * gate at all. */
35698
+ blocked: external_exports.boolean().default(false)
35666
35699
  });
35667
35700
  var QueueItemSchema2 = external_exports.object({
35668
35701
  id: external_exports.string(),
@@ -35799,25 +35832,6 @@ var DeliveryConfigSchema2 = external_exports.object({
35799
35832
  * carries credentials; only a `poll` registration can come back with null here. */
35800
35833
  realtime: external_exports.object({ url: external_exports.string(), anonKey: external_exports.string() }).nullable()
35801
35834
  });
35802
- var StatusSchema2 = external_exports.object({
35803
- name: external_exports.string(),
35804
- sessionMode: external_exports.enum(["default", "all_calls", "silent"]),
35805
- /** A phone is registered for push/ring (any push token on the account). */
35806
- phone: external_exports.boolean(),
35807
- /** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
35808
- * it never picked up. THE SAME NUMBER the harness's wake gate reads
35809
- * (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
35810
- * zero while the sweep sees one is two ideas of "waiting".
35811
- *
35812
- * Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
35813
- * that nobody spawned, so a terminal session only learns of work by asking. The harness
35814
- * used to paper over that by spawning a SECOND process on the identity; now it stands
35815
- * back, correctly, and the person sitting at the terminal is the one who can act. A
35816
- * coffee-beans request sat unread for three days.
35817
- *
35818
- * Optional: an older API sends no field, and the statusline then renders exactly as before. */
35819
- waiting: external_exports.number().int().nonnegative().optional()
35820
- });
35821
35835
  var EnvelopeRecipientSchema2 = external_exports.object({
35822
35836
  keyId: external_exports.string(),
35823
35837
  epk: external_exports.string(),
@@ -36004,7 +36018,9 @@ async function step(session, state, rail) {
36004
36018
  unheard(brief2, state);
36005
36019
  if (ended(brief2.state)) await rail.update(brief2.goalId, { revision: brief2.revision, changes: { reviewed: true }, reason: "Typed into the session." });
36006
36020
  else state.goal = { id: brief2.goalId, revision: brief2.revision };
36007
- send(session, state, brief2.message);
36021
+ const outcome = brief2.outcome?.trim() ?? "";
36022
+ const said = (brief2.entries ?? []).map((e) => entryWords(e).trim()).filter((w) => w && w.toLowerCase() !== outcome.toLowerCase());
36023
+ send2(session, state, [outcome, ...said].filter(Boolean).join("\n\n"));
36008
36024
  return;
36009
36025
  }
36010
36026
  const brief = await rail.read(state.goal.id);
@@ -36023,9 +36039,9 @@ async function step(session, state, rail) {
36023
36039
  }
36024
36040
  if (state.asks?.size || !state.resting) return;
36025
36041
  const words2 = unheard(brief, state);
36026
- if (words2.length) send(session, state, words2.join("\n\n"));
36042
+ if (words2.length) send2(session, state, words2.join("\n\n"));
36027
36043
  }
36028
- function send(session, state, text) {
36044
+ function send2(session, state, text) {
36029
36045
  state.resting = false;
36030
36046
  session.send(text);
36031
36047
  }
@@ -36158,7 +36174,7 @@ function runHarness(opts) {
36158
36174
  let tail = [];
36159
36175
  const state = { resting: true };
36160
36176
  let session = null;
36161
- const rail = railFor({ token: opts.token });
36177
+ const rail = railFor({ token: opts.token, ...opts.resume ? { reach: reachAs(opts.resume) } : {} });
36162
36178
  async function handle(event) {
36163
36179
  if (event.kind === "error") {
36164
36180
  opts.log(`\u26A0 ${event.message}`);
@@ -36206,6 +36222,7 @@ function runHarness(opts) {
36206
36222
  cwd: opts.cwd,
36207
36223
  mode: opts.mode,
36208
36224
  ...opts.token ? { token: opts.token } : {},
36225
+ ...opts.resume ? { resume: opts.resume } : {},
36209
36226
  ...opts.bin ? { bin: opts.bin } : {},
36210
36227
  ...opts.onExit ? { onExit: opts.onExit } : {},
36211
36228
  onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
@@ -36239,8 +36256,8 @@ function runHarness(opts) {
36239
36256
  init_dist();
36240
36257
  init_catalog();
36241
36258
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
36242
- import { homedir as homedir5 } from "os";
36243
- import { join as join5 } from "path";
36259
+ import { homedir as homedir6 } from "os";
36260
+ import { join as join6 } from "path";
36244
36261
 
36245
36262
  // src/workspaces.ts
36246
36263
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
@@ -36279,10 +36296,66 @@ function resolveWakeDir(pinned, deps) {
36279
36296
  return listWorkspaces(deps)[0];
36280
36297
  }
36281
36298
 
36299
+ // src/owner.ts
36300
+ import { closeSync as closeSync2, openSync as openSync2, readSync, readdirSync } from "fs";
36301
+ import { homedir as homedir5 } from "os";
36302
+ import { join as join4 } from "path";
36303
+ var HEAD_BYTES = 64e3;
36304
+ function readHead(file) {
36305
+ const fd = openSync2(file, "r");
36306
+ try {
36307
+ const buf = Buffer.alloc(HEAD_BYTES);
36308
+ const n = readSync(fd, buf, 0, HEAD_BYTES, 0);
36309
+ return buf.subarray(0, n).toString("utf8");
36310
+ } finally {
36311
+ closeSync2(fd);
36312
+ }
36313
+ }
36314
+ function ownerSession(slot, deps = {}) {
36315
+ const m = /^session:([0-9a-f]{8})$/.exec(slot);
36316
+ if (!m) return null;
36317
+ const prefix = m[1];
36318
+ const root = deps.projectsDir ?? join4(homedir5(), ".claude", "projects");
36319
+ const list = deps.list ?? ((dir) => readdirSync(dir));
36320
+ const head = deps.head ?? readHead;
36321
+ let folders;
36322
+ try {
36323
+ folders = list(root);
36324
+ } catch {
36325
+ return null;
36326
+ }
36327
+ for (const folder of folders) {
36328
+ let files;
36329
+ try {
36330
+ files = list(join4(root, folder));
36331
+ } catch {
36332
+ continue;
36333
+ }
36334
+ const file = files.find((f) => f.startsWith(prefix) && f.endsWith(".jsonl"));
36335
+ if (!file) continue;
36336
+ let text;
36337
+ try {
36338
+ text = head(join4(root, folder, file));
36339
+ } catch {
36340
+ continue;
36341
+ }
36342
+ for (const line of text.split("\n")) {
36343
+ try {
36344
+ const row = JSON.parse(line);
36345
+ if (typeof row.cwd === "string" && typeof row.sessionId === "string" && row.sessionId.startsWith(prefix)) {
36346
+ return { sessionId: row.sessionId, cwd: row.cwd };
36347
+ }
36348
+ } catch {
36349
+ }
36350
+ }
36351
+ }
36352
+ return null;
36353
+ }
36354
+
36282
36355
  // src/update.ts
36283
36356
  import { execFile } from "child_process";
36284
36357
  import { createRequire } from "module";
36285
- import { dirname as dirname3, join as join4 } from "path";
36358
+ import { dirname as dirname3, join as join5 } from "path";
36286
36359
  import { promisify } from "util";
36287
36360
  var SERVICE_LABEL = "ai.paigy.harness";
36288
36361
  var VERSION = createRequire(import.meta.url)("../package.json").version;
@@ -36305,7 +36378,7 @@ async function latestVersion() {
36305
36378
  }
36306
36379
  async function install(version) {
36307
36380
  const bin = dirname3(process.execPath);
36308
- await promisify(execFile)(join4(bin, "npm"), ["install", "-g", `@paigy/harness@${version}`], {
36381
+ await promisify(execFile)(join5(bin, "npm"), ["install", "-g", `@paigy/harness@${version}`], {
36309
36382
  env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}`, npm_config_update_notifier: "false" },
36310
36383
  timeout: 5 * 6e4
36311
36384
  });
@@ -36339,14 +36412,14 @@ function selfUpdater(deps) {
36339
36412
  }
36340
36413
 
36341
36414
  // src/host.ts
36342
- var HOST_FILE = join5(homedir5(), ".paigy", "host.json");
36415
+ var HOST_FILE = join6(homedir6(), ".paigy", "host.json");
36343
36416
  var runKey = (slot) => `slot:${slot}`;
36344
36417
  var claimedRun = (sessionId2) => {
36345
36418
  const slot = sessionSlot(sessionId2);
36346
36419
  return { slot, key: runKey(slot) };
36347
36420
  };
36348
- async function slotHasWaitingWork(token) {
36349
- return (await checkReplies({ token })).deliveries.length > 0;
36421
+ async function slotHasWaitingWork(token, session) {
36422
+ return (await checkReplies({ token, ...session ? { reach: reachAs(session) } : {} })).deliveries.length > 0;
36350
36423
  }
36351
36424
  var PRESENCE_FRESH_MS = 3 * 6e4;
36352
36425
  function wakeAction(live, now, serverSeenAt) {
@@ -36359,6 +36432,7 @@ function startHost(opts) {
36359
36432
  const asHost = { token: opts.token };
36360
36433
  const refreshIdentities = async () => {
36361
36434
  for (const slot of listSlots()) {
36435
+ if (slot === "Desktop") continue;
36362
36436
  const token = readToken(slot);
36363
36437
  if (!token) continue;
36364
36438
  const me = await whoAmI({ token }).catch(() => null);
@@ -36438,9 +36512,11 @@ function startHost(opts) {
36438
36512
  if (wakeAction(!!live, Date.now(), seenByServer) === "skip") return;
36439
36513
  const token = readToken(slot);
36440
36514
  if (!token) return;
36441
- const workspace = resolveWakeDir(slotIdentity(slot).workspace, opts.wsDeps);
36515
+ const owner = ownerSession(slot);
36516
+ if (owner && !allowed(owner.cwd, opts.wsDeps)) return;
36517
+ const workspace = owner?.cwd ?? resolveWakeDir(slotIdentity(slot).workspace, opts.wsDeps);
36442
36518
  if (!workspace) return;
36443
- if (!await slotHasWaitingWork(token).catch(() => false)) return;
36519
+ if (!await slotHasWaitingWork(token, owner?.sessionId).catch(() => false)) return;
36444
36520
  const label = slotName(slot) ?? slot;
36445
36521
  const log = (line) => opts.log(`[${label}] ${line}`);
36446
36522
  const run = runHarness({
@@ -36449,6 +36525,7 @@ function startHost(opts) {
36449
36525
  mode: "bypass",
36450
36526
  prompt: "",
36451
36527
  token,
36528
+ ...owner ? { resume: owner.sessionId } : {},
36452
36529
  // A dead run must not squat the slot — evict so the next wake can respawn.
36453
36530
  onExit: () => {
36454
36531
  runs.delete(key);
@@ -36459,7 +36536,7 @@ function startHost(opts) {
36459
36536
  runs.set(key, { run, label, token });
36460
36537
  void heartbeat(void 0, { token }).catch(() => {
36461
36538
  });
36462
- opts.log(`\u25B6 woke ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
36539
+ opts.log(`\u25B6 ${owner ? "resumed" : "woke"} ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
36463
36540
  }
36464
36541
  }
36465
36542
  const listening = /* @__PURE__ */ new Map();
@@ -36691,7 +36768,7 @@ async function pairThisMachine() {
36691
36768
  }
36692
36769
  }
36693
36770
  function forwardCodexThreadId() {
36694
- const cfg = join6(process.env.CODEX_HOME || join6(homedir7(), ".codex"), "config.toml");
36771
+ const cfg = join7(process.env.CODEX_HOME || join7(homedir8(), ".codex"), "config.toml");
36695
36772
  if (!existsSync5(cfg)) return;
36696
36773
  const text = readFileSync4(cfg, "utf8");
36697
36774
  const head = "[mcp_servers.paigy]\n";
@@ -36793,7 +36870,7 @@ async function main() {
36793
36870
  const wsDeps = { file: workspacesFile() };
36794
36871
  for (const dir of parsed.grant) addWorkspace(dir, wsDeps);
36795
36872
  if (!listWorkspaces(wsDeps).length) {
36796
- const guess = join6(homedir7(), "projects");
36873
+ const guess = join7(homedir8(), "projects");
36797
36874
  if (existsSync5(guess)) {
36798
36875
  addWorkspace(guess, wsDeps);
36799
36876
  console.log(`\u2713 workspace granted: ${guess} (change with --grant DIR)`);
@@ -36834,7 +36911,7 @@ async function main() {
36834
36911
  }
36835
36912
  }
36836
36913
  try {
36837
- const settings = join6(homedir7(), ".claude", "settings.json");
36914
+ const settings = join7(homedir8(), ".claude", "settings.json");
36838
36915
  if (which("claude") && existsSync5(settings)) {
36839
36916
  const cfg = JSON.parse(readFileSync4(settings, "utf8"));
36840
36917
  if (!cfg["statusLine"]) {
@@ -36892,13 +36969,13 @@ async function main() {
36892
36969
  <key>SoftResourceLimits</key><dict>
36893
36970
  <key>NumberOfFiles</key><integer>65536</integer>
36894
36971
  </dict>
36895
- <key>StandardOutPath</key><string>${join6(homedir7(), ".paigy", "host.log")}</string>
36896
- <key>StandardErrorPath</key><string>${join6(homedir7(), ".paigy", "host.log")}</string>
36972
+ <key>StandardOutPath</key><string>${join7(homedir8(), ".paigy", "host.log")}</string>
36973
+ <key>StandardErrorPath</key><string>${join7(homedir8(), ".paigy", "host.log")}</string>
36897
36974
  </dict></plist>
36898
36975
  `;
36899
- const dir = join6(homedir7(), "Library", "LaunchAgents");
36976
+ const dir = join7(homedir8(), "Library", "LaunchAgents");
36900
36977
  mkdirSync3(dir, { recursive: true });
36901
- const path = join6(dir, `${SERVICE_LABEL}.plist`);
36978
+ const path = join7(dir, `${SERVICE_LABEL}.plist`);
36902
36979
  writeFileSync4(path, plist);
36903
36980
  execSync(`launchctl unload ${path} 2>/dev/null; launchctl load ${path}`, { shell: "/bin/sh" });
36904
36981
  console.log(`\u2713 host installed as a login service (${path}) \u2014 logs at ~/.paigy/host.log`);
@@ -36921,7 +36998,7 @@ async function main() {
36921
36998
  process.exit(1);
36922
36999
  }
36923
37000
  const name = slotName(slot) ?? slot;
36924
- console.log(`\u2713 ${name} is handed off \u2014 wakes in ${cwd.replace(homedir7(), "~")}`);
37001
+ console.log(`\u2713 ${name} is handed off \u2014 wakes in ${cwd.replace(homedir8(), "~")}`);
36925
37002
  if (!allowed(cwd, { file: workspacesFile() })) {
36926
37003
  console.log(` \u26A0 that folder isn't on the allow-list, so wakes will land in the first granted`);
36927
37004
  console.log(` workspace instead \u2014 add it in the desktop app, or: paigy-harness host --grant ${cwd}`);