@paigy/harness 0.3.11 → 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 +156 -44
- package/dist/main.js +150 -41
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22759,11 +22759,17 @@ function sessionId() {
|
|
|
22759
22759
|
return SESSION_ID;
|
|
22760
22760
|
}
|
|
22761
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) {
|
|
22762
22768
|
try {
|
|
22763
22769
|
const headers = {
|
|
22764
22770
|
...init?.headers,
|
|
22765
22771
|
"x-paigy-instance": INSTANCE_ID,
|
|
22766
|
-
"x-paigy-session":
|
|
22772
|
+
"x-paigy-session": session
|
|
22767
22773
|
};
|
|
22768
22774
|
return await fetch(url, { ...init, headers, dispatcher: await proxy() });
|
|
22769
22775
|
} catch (e) {
|
|
@@ -23921,14 +23927,14 @@ async function acceptTriage(proposalId, body, opts = {}) {
|
|
|
23921
23927
|
async function contact(input, opts = {}) {
|
|
23922
23928
|
const parsed = ContactSchema.parse(input);
|
|
23923
23929
|
opts.signal?.throwIfAborted();
|
|
23924
|
-
const
|
|
23930
|
+
const send22 = opts.reach ?? reach;
|
|
23925
23931
|
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
23926
23932
|
let deliveryId;
|
|
23927
23933
|
if ("deliveryId" in parsed) deliveryId = parsed.deliveryId;
|
|
23928
23934
|
else {
|
|
23929
23935
|
if (!opts.reach && readKeyFile()?.e2ee) throw new Error("Goal contact does not support E2EE yet; refusing to send plaintext.");
|
|
23930
23936
|
const shaped = deriveAsk(NotifyRequestSchema.parse({ ask: parsed.ask, waiting: parsed.waiting, options: parsed.options }));
|
|
23931
|
-
const res = ensureAuthed(await
|
|
23937
|
+
const res = ensureAuthed(await send22(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
|
|
23932
23938
|
method: "POST",
|
|
23933
23939
|
headers,
|
|
23934
23940
|
signal: opts.signal,
|
|
@@ -23940,7 +23946,7 @@ async function contact(input, opts = {}) {
|
|
|
23940
23946
|
if (!deliveryId) throw new Error("Goal contact returned no Delivery identity");
|
|
23941
23947
|
}
|
|
23942
23948
|
const read = async (signal2) => {
|
|
23943
|
-
const res = ensureAuthed(await
|
|
23949
|
+
const res = ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
23944
23950
|
if (!res.ok) await fail("read_delivery", res);
|
|
23945
23951
|
return await res.json();
|
|
23946
23952
|
};
|
|
@@ -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, 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 =
|
|
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(),
|
|
@@ -26775,7 +26787,7 @@ var init_dist = __esm({
|
|
|
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
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.";
|
|
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 Goal as get_goal reads it, at its new revision.";
|
|
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.";
|
|
26779
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.";
|
|
26780
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();
|
|
@@ -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(),
|
|
@@ -32456,7 +32481,7 @@ var require_lib = __commonJS({
|
|
|
32456
32481
|
|
|
32457
32482
|
// src/triage/judge.ts
|
|
32458
32483
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
32459
|
-
import { homedir as
|
|
32484
|
+
import { homedir as homedir7 } from "os";
|
|
32460
32485
|
import { delimiter as delimiter3 } from "path";
|
|
32461
32486
|
function pickOllamaModel(tags) {
|
|
32462
32487
|
if (tags.length === 0) return null;
|
|
@@ -32539,7 +32564,7 @@ async function judgeFor(asked, deps = {}) {
|
|
|
32539
32564
|
const out = spawnSync2(bin, runner.argv(null), {
|
|
32540
32565
|
input: prompt,
|
|
32541
32566
|
encoding: "utf8",
|
|
32542
|
-
cwd:
|
|
32567
|
+
cwd: homedir7(),
|
|
32543
32568
|
timeout: RUN_TIMEOUT_MS,
|
|
32544
32569
|
maxBuffer: 32 * 1024 * 1024,
|
|
32545
32570
|
env: { ...process.env, PATH: [process.env["PATH"], ...probeDirs()].filter(Boolean).join(delimiter3) }
|
|
@@ -33129,8 +33154,11 @@ var import_qrcode = __toESM(require_lib(), 1);
|
|
|
33129
33154
|
import { hostname } from "os";
|
|
33130
33155
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
33131
33156
|
import { execFile as execFile2, execSync } from "child_process";
|
|
33132
|
-
import { homedir as
|
|
33133
|
-
import { join as
|
|
33157
|
+
import { homedir as homedir8 } from "os";
|
|
33158
|
+
import { join as join7 } from "path";
|
|
33159
|
+
|
|
33160
|
+
// src/run.ts
|
|
33161
|
+
init_dist();
|
|
33134
33162
|
|
|
33135
33163
|
// src/harness/session.ts
|
|
33136
33164
|
import { spawn } from "child_process";
|
|
@@ -33145,15 +33173,19 @@ function optionFor(options, decision) {
|
|
|
33145
33173
|
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
33146
33174
|
}
|
|
33147
33175
|
function createAcpDriver(opts) {
|
|
33148
|
-
return new AcpDriver(opts.cwd, opts.mode);
|
|
33176
|
+
return new AcpDriver(opts.cwd, opts.mode, opts.resume);
|
|
33149
33177
|
}
|
|
33150
33178
|
var AcpDriver = class {
|
|
33151
|
-
|
|
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) {
|
|
33152
33182
|
this.cwd = cwd;
|
|
33153
33183
|
this.mode = mode;
|
|
33184
|
+
this.resume = resume;
|
|
33154
33185
|
}
|
|
33155
33186
|
cwd;
|
|
33156
33187
|
mode;
|
|
33188
|
+
resume;
|
|
33157
33189
|
nextId = 1;
|
|
33158
33190
|
initId;
|
|
33159
33191
|
sessionNewId;
|
|
@@ -33237,15 +33269,15 @@ var AcpDriver = class {
|
|
|
33237
33269
|
this.sessionNewId = this.nextId++;
|
|
33238
33270
|
return {
|
|
33239
33271
|
events: [],
|
|
33240
|
-
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: [] } })]
|
|
33241
33273
|
};
|
|
33242
33274
|
}
|
|
33243
33275
|
if (msg.id === this.sessionNewId) {
|
|
33244
33276
|
this.sessionNewId = void 0;
|
|
33245
|
-
const sessionId2 = msg.result?.sessionId;
|
|
33277
|
+
const sessionId2 = msg.error ? void 0 : msg.result?.sessionId ?? this.resume;
|
|
33246
33278
|
if (!sessionId2) {
|
|
33247
33279
|
return {
|
|
33248
|
-
events: [{ kind: "error", message:
|
|
33280
|
+
events: [{ kind: "error", message: `${this.resume ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}` }],
|
|
33249
33281
|
writes: []
|
|
33250
33282
|
};
|
|
33251
33283
|
}
|
|
@@ -33382,7 +33414,8 @@ function startSession(opts) {
|
|
|
33382
33414
|
env: {
|
|
33383
33415
|
...process.env,
|
|
33384
33416
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
33385
|
-
...opts.token ? { PAIGY_TOKEN: opts.token } : {}
|
|
33417
|
+
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
33418
|
+
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {}
|
|
33386
33419
|
},
|
|
33387
33420
|
stdio: ["pipe", "pipe", "pipe"]
|
|
33388
33421
|
});
|
|
@@ -33402,7 +33435,7 @@ function startSession(opts) {
|
|
|
33402
33435
|
opts.onEvent({ kind: "error", message: e.message });
|
|
33403
33436
|
opts.onExit?.();
|
|
33404
33437
|
});
|
|
33405
|
-
const driver = createAcpDriver({ cwd, mode: opts.mode });
|
|
33438
|
+
const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.resume ? { resume: opts.resume } : {} });
|
|
33406
33439
|
let buffer = "";
|
|
33407
33440
|
child.stdout?.on("data", (chunk) => {
|
|
33408
33441
|
const { lines: lines2, rest } = splitLines(buffer, String(chunk));
|
|
@@ -34784,6 +34817,9 @@ var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
|
34784
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.";
|
|
34785
34818
|
var CreateGoalSchema2 = external_exports.object({
|
|
34786
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(),
|
|
34787
34823
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
34788
34824
|
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
34789
34825
|
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
@@ -34803,11 +34839,14 @@ var CreateGoalSchema2 = external_exports.object({
|
|
|
34803
34839
|
var CreateGoalToolSchema2 = CreateGoalSchema2.extend({
|
|
34804
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.")
|
|
34805
34841
|
}).strict();
|
|
34806
|
-
var CREATE_GOAL_DESCRIPTION2 =
|
|
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.';
|
|
34807
34843
|
var UpdateGoalSchema2 = external_exports.object({
|
|
34808
34844
|
revision: external_exports.number().int().positive(),
|
|
34809
34845
|
changes: external_exports.object({
|
|
34810
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(),
|
|
34811
34850
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
34812
34851
|
parentGoalId: external_exports.string().uuid().nullable().optional(),
|
|
34813
34852
|
dependencies: external_exports.array(external_exports.object({ goalId: external_exports.string().uuid(), gate: external_exports.enum(["start", "finish"]) }).strict()).optional(),
|
|
@@ -34824,7 +34863,7 @@ var UpdateGoalToolSchema2 = UpdateGoalSchema2.omit({ operationId: true }).extend
|
|
|
34824
34863
|
var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
34825
34864
|
var GetGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
34826
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.";
|
|
34827
|
-
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 Goal as get_goal reads it, at its new revision.";
|
|
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.";
|
|
34828
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.";
|
|
34829
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.";
|
|
34830
34869
|
var CheckRepliesSchema2 = external_exports.object({}).strict();
|
|
@@ -34862,6 +34901,7 @@ function entryWords(entry) {
|
|
|
34862
34901
|
}
|
|
34863
34902
|
return entry.sources.map((source) => source.text).join("\n");
|
|
34864
34903
|
}
|
|
34904
|
+
var LIVE_MS2 = 3 * 6e4;
|
|
34865
34905
|
var ContextSchema2 = external_exports.object({
|
|
34866
34906
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
34867
34907
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -35643,7 +35683,19 @@ var QueueQuestionSchema2 = external_exports.object({
|
|
|
35643
35683
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
35644
35684
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
35645
35685
|
* for a settled question whose reply carried nothing readable. */
|
|
35646
|
-
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)
|
|
35647
35699
|
});
|
|
35648
35700
|
var QueueItemSchema2 = external_exports.object({
|
|
35649
35701
|
id: external_exports.string(),
|
|
@@ -35968,7 +36020,7 @@ async function step(session, state, rail) {
|
|
|
35968
36020
|
else state.goal = { id: brief2.goalId, revision: brief2.revision };
|
|
35969
36021
|
const outcome = brief2.outcome?.trim() ?? "";
|
|
35970
36022
|
const said = (brief2.entries ?? []).map((e) => entryWords(e).trim()).filter((w) => w && w.toLowerCase() !== outcome.toLowerCase());
|
|
35971
|
-
|
|
36023
|
+
send2(session, state, [outcome, ...said].filter(Boolean).join("\n\n"));
|
|
35972
36024
|
return;
|
|
35973
36025
|
}
|
|
35974
36026
|
const brief = await rail.read(state.goal.id);
|
|
@@ -35987,9 +36039,9 @@ async function step(session, state, rail) {
|
|
|
35987
36039
|
}
|
|
35988
36040
|
if (state.asks?.size || !state.resting) return;
|
|
35989
36041
|
const words2 = unheard(brief, state);
|
|
35990
|
-
if (words2.length)
|
|
36042
|
+
if (words2.length) send2(session, state, words2.join("\n\n"));
|
|
35991
36043
|
}
|
|
35992
|
-
function
|
|
36044
|
+
function send2(session, state, text) {
|
|
35993
36045
|
state.resting = false;
|
|
35994
36046
|
session.send(text);
|
|
35995
36047
|
}
|
|
@@ -36122,7 +36174,7 @@ function runHarness(opts) {
|
|
|
36122
36174
|
let tail = [];
|
|
36123
36175
|
const state = { resting: true };
|
|
36124
36176
|
let session = null;
|
|
36125
|
-
const rail = railFor({ token: opts.token });
|
|
36177
|
+
const rail = railFor({ token: opts.token, ...opts.resume ? { reach: reachAs(opts.resume) } : {} });
|
|
36126
36178
|
async function handle(event) {
|
|
36127
36179
|
if (event.kind === "error") {
|
|
36128
36180
|
opts.log(`\u26A0 ${event.message}`);
|
|
@@ -36170,6 +36222,7 @@ function runHarness(opts) {
|
|
|
36170
36222
|
cwd: opts.cwd,
|
|
36171
36223
|
mode: opts.mode,
|
|
36172
36224
|
...opts.token ? { token: opts.token } : {},
|
|
36225
|
+
...opts.resume ? { resume: opts.resume } : {},
|
|
36173
36226
|
...opts.bin ? { bin: opts.bin } : {},
|
|
36174
36227
|
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
36175
36228
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
@@ -36203,8 +36256,8 @@ function runHarness(opts) {
|
|
|
36203
36256
|
init_dist();
|
|
36204
36257
|
init_catalog();
|
|
36205
36258
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
36206
|
-
import { homedir as
|
|
36207
|
-
import { join as
|
|
36259
|
+
import { homedir as homedir6 } from "os";
|
|
36260
|
+
import { join as join6 } from "path";
|
|
36208
36261
|
|
|
36209
36262
|
// src/workspaces.ts
|
|
36210
36263
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -36243,10 +36296,66 @@ function resolveWakeDir(pinned, deps) {
|
|
|
36243
36296
|
return listWorkspaces(deps)[0];
|
|
36244
36297
|
}
|
|
36245
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
|
+
|
|
36246
36355
|
// src/update.ts
|
|
36247
36356
|
import { execFile } from "child_process";
|
|
36248
36357
|
import { createRequire } from "module";
|
|
36249
|
-
import { dirname as dirname3, join as
|
|
36358
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
36250
36359
|
import { promisify } from "util";
|
|
36251
36360
|
var SERVICE_LABEL = "ai.paigy.harness";
|
|
36252
36361
|
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
@@ -36269,7 +36378,7 @@ async function latestVersion() {
|
|
|
36269
36378
|
}
|
|
36270
36379
|
async function install(version) {
|
|
36271
36380
|
const bin = dirname3(process.execPath);
|
|
36272
|
-
await promisify(execFile)(
|
|
36381
|
+
await promisify(execFile)(join5(bin, "npm"), ["install", "-g", `@paigy/harness@${version}`], {
|
|
36273
36382
|
env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}`, npm_config_update_notifier: "false" },
|
|
36274
36383
|
timeout: 5 * 6e4
|
|
36275
36384
|
});
|
|
@@ -36303,14 +36412,14 @@ function selfUpdater(deps) {
|
|
|
36303
36412
|
}
|
|
36304
36413
|
|
|
36305
36414
|
// src/host.ts
|
|
36306
|
-
var HOST_FILE =
|
|
36415
|
+
var HOST_FILE = join6(homedir6(), ".paigy", "host.json");
|
|
36307
36416
|
var runKey = (slot) => `slot:${slot}`;
|
|
36308
36417
|
var claimedRun = (sessionId2) => {
|
|
36309
36418
|
const slot = sessionSlot(sessionId2);
|
|
36310
36419
|
return { slot, key: runKey(slot) };
|
|
36311
36420
|
};
|
|
36312
|
-
async function slotHasWaitingWork(token) {
|
|
36313
|
-
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;
|
|
36314
36423
|
}
|
|
36315
36424
|
var PRESENCE_FRESH_MS = 3 * 6e4;
|
|
36316
36425
|
function wakeAction(live, now, serverSeenAt) {
|
|
@@ -36403,9 +36512,11 @@ function startHost(opts) {
|
|
|
36403
36512
|
if (wakeAction(!!live, Date.now(), seenByServer) === "skip") return;
|
|
36404
36513
|
const token = readToken(slot);
|
|
36405
36514
|
if (!token) return;
|
|
36406
|
-
const
|
|
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);
|
|
36407
36518
|
if (!workspace) return;
|
|
36408
|
-
if (!await slotHasWaitingWork(token).catch(() => false)) return;
|
|
36519
|
+
if (!await slotHasWaitingWork(token, owner?.sessionId).catch(() => false)) return;
|
|
36409
36520
|
const label = slotName(slot) ?? slot;
|
|
36410
36521
|
const log = (line) => opts.log(`[${label}] ${line}`);
|
|
36411
36522
|
const run = runHarness({
|
|
@@ -36414,6 +36525,7 @@ function startHost(opts) {
|
|
|
36414
36525
|
mode: "bypass",
|
|
36415
36526
|
prompt: "",
|
|
36416
36527
|
token,
|
|
36528
|
+
...owner ? { resume: owner.sessionId } : {},
|
|
36417
36529
|
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
36418
36530
|
onExit: () => {
|
|
36419
36531
|
runs.delete(key);
|
|
@@ -36424,7 +36536,7 @@ function startHost(opts) {
|
|
|
36424
36536
|
runs.set(key, { run, label, token });
|
|
36425
36537
|
void heartbeat(void 0, { token }).catch(() => {
|
|
36426
36538
|
});
|
|
36427
|
-
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}`);
|
|
36428
36540
|
}
|
|
36429
36541
|
}
|
|
36430
36542
|
const listening = /* @__PURE__ */ new Map();
|
|
@@ -36656,7 +36768,7 @@ async function pairThisMachine() {
|
|
|
36656
36768
|
}
|
|
36657
36769
|
}
|
|
36658
36770
|
function forwardCodexThreadId() {
|
|
36659
|
-
const cfg =
|
|
36771
|
+
const cfg = join7(process.env.CODEX_HOME || join7(homedir8(), ".codex"), "config.toml");
|
|
36660
36772
|
if (!existsSync5(cfg)) return;
|
|
36661
36773
|
const text = readFileSync4(cfg, "utf8");
|
|
36662
36774
|
const head = "[mcp_servers.paigy]\n";
|
|
@@ -36758,7 +36870,7 @@ async function main() {
|
|
|
36758
36870
|
const wsDeps = { file: workspacesFile() };
|
|
36759
36871
|
for (const dir of parsed.grant) addWorkspace(dir, wsDeps);
|
|
36760
36872
|
if (!listWorkspaces(wsDeps).length) {
|
|
36761
|
-
const guess =
|
|
36873
|
+
const guess = join7(homedir8(), "projects");
|
|
36762
36874
|
if (existsSync5(guess)) {
|
|
36763
36875
|
addWorkspace(guess, wsDeps);
|
|
36764
36876
|
console.log(`\u2713 workspace granted: ${guess} (change with --grant DIR)`);
|
|
@@ -36799,7 +36911,7 @@ async function main() {
|
|
|
36799
36911
|
}
|
|
36800
36912
|
}
|
|
36801
36913
|
try {
|
|
36802
|
-
const settings =
|
|
36914
|
+
const settings = join7(homedir8(), ".claude", "settings.json");
|
|
36803
36915
|
if (which("claude") && existsSync5(settings)) {
|
|
36804
36916
|
const cfg = JSON.parse(readFileSync4(settings, "utf8"));
|
|
36805
36917
|
if (!cfg["statusLine"]) {
|
|
@@ -36857,13 +36969,13 @@ async function main() {
|
|
|
36857
36969
|
<key>SoftResourceLimits</key><dict>
|
|
36858
36970
|
<key>NumberOfFiles</key><integer>65536</integer>
|
|
36859
36971
|
</dict>
|
|
36860
|
-
<key>StandardOutPath</key><string>${
|
|
36861
|
-
<key>StandardErrorPath</key><string>${
|
|
36972
|
+
<key>StandardOutPath</key><string>${join7(homedir8(), ".paigy", "host.log")}</string>
|
|
36973
|
+
<key>StandardErrorPath</key><string>${join7(homedir8(), ".paigy", "host.log")}</string>
|
|
36862
36974
|
</dict></plist>
|
|
36863
36975
|
`;
|
|
36864
|
-
const dir =
|
|
36976
|
+
const dir = join7(homedir8(), "Library", "LaunchAgents");
|
|
36865
36977
|
mkdirSync3(dir, { recursive: true });
|
|
36866
|
-
const path =
|
|
36978
|
+
const path = join7(dir, `${SERVICE_LABEL}.plist`);
|
|
36867
36979
|
writeFileSync4(path, plist);
|
|
36868
36980
|
execSync(`launchctl unload ${path} 2>/dev/null; launchctl load ${path}`, { shell: "/bin/sh" });
|
|
36869
36981
|
console.log(`\u2713 host installed as a login service (${path}) \u2014 logs at ~/.paigy/host.log`);
|
|
@@ -36886,7 +36998,7 @@ async function main() {
|
|
|
36886
36998
|
process.exit(1);
|
|
36887
36999
|
}
|
|
36888
37000
|
const name = slotName(slot) ?? slot;
|
|
36889
|
-
console.log(`\u2713 ${name} is handed off \u2014 wakes in ${cwd.replace(
|
|
37001
|
+
console.log(`\u2713 ${name} is handed off \u2014 wakes in ${cwd.replace(homedir8(), "~")}`);
|
|
36890
37002
|
if (!allowed(cwd, { file: workspacesFile() })) {
|
|
36891
37003
|
console.log(` \u26A0 that folder isn't on the allow-list, so wakes will land in the first granted`);
|
|
36892
37004
|
console.log(` workspace instead \u2014 add it in the desktop app, or: paigy-harness host --grant ${cwd}`);
|
package/dist/main.js
CHANGED
|
@@ -4580,7 +4580,7 @@ import { app, BrowserWindow, dialog, ipcMain } from "electron";
|
|
|
4580
4580
|
import { spawn as spawn2 } from "child_process";
|
|
4581
4581
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4582
4582
|
import { fileURLToPath } from "url";
|
|
4583
|
-
import { dirname as dirname4, join as
|
|
4583
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
4584
4584
|
|
|
4585
4585
|
// ../../packages/sdk/dist/index.js
|
|
4586
4586
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
@@ -10915,11 +10915,17 @@ function sessionId() {
|
|
|
10915
10915
|
return SESSION_ID;
|
|
10916
10916
|
}
|
|
10917
10917
|
async function reach(url, init) {
|
|
10918
|
+
return send(url, init, SESSION_ID);
|
|
10919
|
+
}
|
|
10920
|
+
function reachAs(session) {
|
|
10921
|
+
return (url, init) => send(url, init, session);
|
|
10922
|
+
}
|
|
10923
|
+
async function send(url, init, session) {
|
|
10918
10924
|
try {
|
|
10919
10925
|
const headers = {
|
|
10920
10926
|
...init?.headers,
|
|
10921
10927
|
"x-paigy-instance": INSTANCE_ID,
|
|
10922
|
-
"x-paigy-session":
|
|
10928
|
+
"x-paigy-session": session
|
|
10923
10929
|
};
|
|
10924
10930
|
return await fetch(url, { ...init, headers, dispatcher: await proxy() });
|
|
10925
10931
|
} catch (e) {
|
|
@@ -12195,6 +12201,9 @@ var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
|
12195
12201
|
var 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.";
|
|
12196
12202
|
var CreateGoalSchema = external_exports.object({
|
|
12197
12203
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
12204
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
12205
|
+
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
12206
|
+
title: external_exports.string().trim().min(1).max(80).optional(),
|
|
12198
12207
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
12199
12208
|
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
12200
12209
|
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
@@ -12214,11 +12223,14 @@ var CreateGoalSchema = external_exports.object({
|
|
|
12214
12223
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
12215
12224
|
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
12216
12225
|
}).strict();
|
|
12217
|
-
var CREATE_GOAL_DESCRIPTION =
|
|
12226
|
+
var 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.';
|
|
12218
12227
|
var UpdateGoalSchema = external_exports.object({
|
|
12219
12228
|
revision: external_exports.number().int().positive(),
|
|
12220
12229
|
changes: external_exports.object({
|
|
12221
12230
|
outcome: external_exports.string().trim().min(1).max(1e4).optional(),
|
|
12231
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
12232
|
+
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
12233
|
+
title: external_exports.string().trim().min(1).max(80).nullable().optional(),
|
|
12222
12234
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
12223
12235
|
parentGoalId: external_exports.string().uuid().nullable().optional(),
|
|
12224
12236
|
dependencies: external_exports.array(external_exports.object({ goalId: external_exports.string().uuid(), gate: external_exports.enum(["start", "finish"]) }).strict()).optional(),
|
|
@@ -12235,7 +12247,7 @@ var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({
|
|
|
12235
12247
|
var ClaimGoalSchema = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
12236
12248
|
var GetGoalSchema = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
12237
12249
|
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), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
|
|
12238
|
-
var 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 Goal as get_goal reads it, at its new revision.";
|
|
12250
|
+
var 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.";
|
|
12239
12251
|
var 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.";
|
|
12240
12252
|
var 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.";
|
|
12241
12253
|
var CheckRepliesSchema = external_exports.object({}).strict();
|
|
@@ -12258,6 +12270,7 @@ var AGENT_TOOLS = [
|
|
|
12258
12270
|
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
12259
12271
|
];
|
|
12260
12272
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
12273
|
+
var LIVE_MS = 3 * 6e4;
|
|
12261
12274
|
var ContextSchema = external_exports.object({
|
|
12262
12275
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
12263
12276
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -13094,7 +13107,19 @@ var QueueQuestionSchema = external_exports.object({
|
|
|
13094
13107
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
13095
13108
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
13096
13109
|
* for a settled question whose reply carried nothing readable. */
|
|
13097
|
-
answer: external_exports.string().nullable().default(null)
|
|
13110
|
+
answer: external_exports.string().nullable().default(null),
|
|
13111
|
+
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
13112
|
+
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
13113
|
+
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
13114
|
+
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
13115
|
+
* this is the field they agree through rather than each re-deriving it from the row it
|
|
13116
|
+
* arrived under. Required because the API projects it on every need it sends. */
|
|
13117
|
+
goalId: external_exports.string(),
|
|
13118
|
+
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
13119
|
+
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
13120
|
+
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
13121
|
+
* gate at all. */
|
|
13122
|
+
blocked: external_exports.boolean().default(false)
|
|
13098
13123
|
});
|
|
13099
13124
|
var QueueItemSchema = external_exports.object({
|
|
13100
13125
|
id: external_exports.string(),
|
|
@@ -13615,14 +13640,14 @@ async function dismissTriage(proposalId, opts = {}) {
|
|
|
13615
13640
|
async function contact(input, opts = {}) {
|
|
13616
13641
|
const parsed = ContactSchema.parse(input);
|
|
13617
13642
|
opts.signal?.throwIfAborted();
|
|
13618
|
-
const
|
|
13643
|
+
const send22 = opts.reach ?? reach;
|
|
13619
13644
|
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
13620
13645
|
let deliveryId;
|
|
13621
13646
|
if ("deliveryId" in parsed) deliveryId = parsed.deliveryId;
|
|
13622
13647
|
else {
|
|
13623
13648
|
if (!opts.reach && readKeyFile()?.e2ee) throw new Error("Goal contact does not support E2EE yet; refusing to send plaintext.");
|
|
13624
13649
|
const shaped = deriveAsk(NotifyRequestSchema.parse({ ask: parsed.ask, waiting: parsed.waiting, options: parsed.options }));
|
|
13625
|
-
const res = ensureAuthed(await
|
|
13650
|
+
const res = ensureAuthed(await send22(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
|
|
13626
13651
|
method: "POST",
|
|
13627
13652
|
headers,
|
|
13628
13653
|
signal: opts.signal,
|
|
@@ -13634,7 +13659,7 @@ async function contact(input, opts = {}) {
|
|
|
13634
13659
|
if (!deliveryId) throw new Error("Goal contact returned no Delivery identity");
|
|
13635
13660
|
}
|
|
13636
13661
|
const read = async (signal2) => {
|
|
13637
|
-
const res = ensureAuthed(await
|
|
13662
|
+
const res = ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
13638
13663
|
if (!res.ok) await fail("read_delivery", res);
|
|
13639
13664
|
return await res.json();
|
|
13640
13665
|
};
|
|
@@ -13763,7 +13788,7 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
13763
13788
|
|
|
13764
13789
|
// src/main.ts
|
|
13765
13790
|
var import_qrcode = __toESM(require_lib(), 1);
|
|
13766
|
-
import { homedir as
|
|
13791
|
+
import { homedir as homedir8, hostname } from "os";
|
|
13767
13792
|
|
|
13768
13793
|
// src/harness/catalog.ts
|
|
13769
13794
|
import { spawnSync } from "child_process";
|
|
@@ -13857,8 +13882,8 @@ function detectAll(deps = {}) {
|
|
|
13857
13882
|
|
|
13858
13883
|
// src/host.ts
|
|
13859
13884
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
13860
|
-
import { homedir as
|
|
13861
|
-
import { join as
|
|
13885
|
+
import { homedir as homedir6 } from "os";
|
|
13886
|
+
import { join as join6 } from "path";
|
|
13862
13887
|
|
|
13863
13888
|
// src/workspaces.ts
|
|
13864
13889
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -13903,6 +13928,62 @@ function resolveWakeDir(pinned, deps) {
|
|
|
13903
13928
|
return listWorkspaces(deps)[0];
|
|
13904
13929
|
}
|
|
13905
13930
|
|
|
13931
|
+
// src/owner.ts
|
|
13932
|
+
import { closeSync as closeSync2, openSync as openSync2, readSync, readdirSync } from "fs";
|
|
13933
|
+
import { homedir as homedir4 } from "os";
|
|
13934
|
+
import { join as join4 } from "path";
|
|
13935
|
+
var HEAD_BYTES = 64e3;
|
|
13936
|
+
function readHead(file) {
|
|
13937
|
+
const fd = openSync2(file, "r");
|
|
13938
|
+
try {
|
|
13939
|
+
const buf = Buffer.alloc(HEAD_BYTES);
|
|
13940
|
+
const n = readSync(fd, buf, 0, HEAD_BYTES, 0);
|
|
13941
|
+
return buf.subarray(0, n).toString("utf8");
|
|
13942
|
+
} finally {
|
|
13943
|
+
closeSync2(fd);
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13946
|
+
function ownerSession(slot, deps = {}) {
|
|
13947
|
+
const m = /^session:([0-9a-f]{8})$/.exec(slot);
|
|
13948
|
+
if (!m) return null;
|
|
13949
|
+
const prefix = m[1];
|
|
13950
|
+
const root = deps.projectsDir ?? join4(homedir4(), ".claude", "projects");
|
|
13951
|
+
const list = deps.list ?? ((dir) => readdirSync(dir));
|
|
13952
|
+
const head = deps.head ?? readHead;
|
|
13953
|
+
let folders;
|
|
13954
|
+
try {
|
|
13955
|
+
folders = list(root);
|
|
13956
|
+
} catch {
|
|
13957
|
+
return null;
|
|
13958
|
+
}
|
|
13959
|
+
for (const folder of folders) {
|
|
13960
|
+
let files;
|
|
13961
|
+
try {
|
|
13962
|
+
files = list(join4(root, folder));
|
|
13963
|
+
} catch {
|
|
13964
|
+
continue;
|
|
13965
|
+
}
|
|
13966
|
+
const file = files.find((f) => f.startsWith(prefix) && f.endsWith(".jsonl"));
|
|
13967
|
+
if (!file) continue;
|
|
13968
|
+
let text;
|
|
13969
|
+
try {
|
|
13970
|
+
text = head(join4(root, folder, file));
|
|
13971
|
+
} catch {
|
|
13972
|
+
continue;
|
|
13973
|
+
}
|
|
13974
|
+
for (const line of text.split("\n")) {
|
|
13975
|
+
try {
|
|
13976
|
+
const row = JSON.parse(line);
|
|
13977
|
+
if (typeof row.cwd === "string" && typeof row.sessionId === "string" && row.sessionId.startsWith(prefix)) {
|
|
13978
|
+
return { sessionId: row.sessionId, cwd: row.cwd };
|
|
13979
|
+
}
|
|
13980
|
+
} catch {
|
|
13981
|
+
}
|
|
13982
|
+
}
|
|
13983
|
+
}
|
|
13984
|
+
return null;
|
|
13985
|
+
}
|
|
13986
|
+
|
|
13906
13987
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
13907
13988
|
var ignoreOverride2 = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
13908
13989
|
var defaultOptions2 = {
|
|
@@ -15247,6 +15328,9 @@ var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
|
15247
15328
|
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.";
|
|
15248
15329
|
var CreateGoalSchema2 = external_exports.object({
|
|
15249
15330
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
15331
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
15332
|
+
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
15333
|
+
title: external_exports.string().trim().min(1).max(80).optional(),
|
|
15250
15334
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
15251
15335
|
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
15252
15336
|
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
@@ -15266,11 +15350,14 @@ var CreateGoalSchema2 = external_exports.object({
|
|
|
15266
15350
|
var CreateGoalToolSchema2 = CreateGoalSchema2.extend({
|
|
15267
15351
|
idempotencyKey: CreateGoalSchema2.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
15268
15352
|
}).strict();
|
|
15269
|
-
var CREATE_GOAL_DESCRIPTION2 =
|
|
15353
|
+
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.';
|
|
15270
15354
|
var UpdateGoalSchema2 = external_exports.object({
|
|
15271
15355
|
revision: external_exports.number().int().positive(),
|
|
15272
15356
|
changes: external_exports.object({
|
|
15273
15357
|
outcome: external_exports.string().trim().min(1).max(1e4).optional(),
|
|
15358
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
15359
|
+
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
15360
|
+
title: external_exports.string().trim().min(1).max(80).nullable().optional(),
|
|
15274
15361
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
15275
15362
|
parentGoalId: external_exports.string().uuid().nullable().optional(),
|
|
15276
15363
|
dependencies: external_exports.array(external_exports.object({ goalId: external_exports.string().uuid(), gate: external_exports.enum(["start", "finish"]) }).strict()).optional(),
|
|
@@ -15287,7 +15374,7 @@ var UpdateGoalToolSchema2 = UpdateGoalSchema2.omit({ operationId: true }).extend
|
|
|
15287
15374
|
var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
15288
15375
|
var GetGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
15289
15376
|
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.";
|
|
15290
|
-
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 Goal as get_goal reads it, at its new revision.";
|
|
15377
|
+
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.";
|
|
15291
15378
|
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.";
|
|
15292
15379
|
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.";
|
|
15293
15380
|
var CheckRepliesSchema2 = external_exports.object({}).strict();
|
|
@@ -15325,6 +15412,7 @@ function entryWords(entry) {
|
|
|
15325
15412
|
}
|
|
15326
15413
|
return entry.sources.map((source) => source.text).join("\n");
|
|
15327
15414
|
}
|
|
15415
|
+
var LIVE_MS2 = 3 * 6e4;
|
|
15328
15416
|
var ContextSchema2 = external_exports.object({
|
|
15329
15417
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
15330
15418
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -16106,7 +16194,19 @@ var QueueQuestionSchema2 = external_exports.object({
|
|
|
16106
16194
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
16107
16195
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
16108
16196
|
* for a settled question whose reply carried nothing readable. */
|
|
16109
|
-
answer: external_exports.string().nullable().default(null)
|
|
16197
|
+
answer: external_exports.string().nullable().default(null),
|
|
16198
|
+
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
16199
|
+
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
16200
|
+
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
16201
|
+
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
16202
|
+
* this is the field they agree through rather than each re-deriving it from the row it
|
|
16203
|
+
* arrived under. Required because the API projects it on every need it sends. */
|
|
16204
|
+
goalId: external_exports.string(),
|
|
16205
|
+
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
16206
|
+
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
16207
|
+
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
16208
|
+
* gate at all. */
|
|
16209
|
+
blocked: external_exports.boolean().default(false)
|
|
16110
16210
|
});
|
|
16111
16211
|
var QueueItemSchema2 = external_exports.object({
|
|
16112
16212
|
id: external_exports.string(),
|
|
@@ -16395,7 +16495,7 @@ function sameTail(a, b) {
|
|
|
16395
16495
|
// src/harness/session.ts
|
|
16396
16496
|
import { spawn } from "child_process";
|
|
16397
16497
|
import { existsSync as existsSync4 } from "fs";
|
|
16398
|
-
import { homedir as
|
|
16498
|
+
import { homedir as homedir5 } from "os";
|
|
16399
16499
|
import { resolve as resolve2, delimiter as delimiter2 } from "path";
|
|
16400
16500
|
|
|
16401
16501
|
// src/harness/acp.ts
|
|
@@ -16405,15 +16505,19 @@ function optionFor(options, decision) {
|
|
|
16405
16505
|
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
16406
16506
|
}
|
|
16407
16507
|
function createAcpDriver(opts) {
|
|
16408
|
-
return new AcpDriver(opts.cwd, opts.mode);
|
|
16508
|
+
return new AcpDriver(opts.cwd, opts.mode, opts.resume);
|
|
16409
16509
|
}
|
|
16410
16510
|
var AcpDriver = class {
|
|
16411
|
-
|
|
16511
|
+
/** `resume`: the id of an existing session to continue rather than open a new one (#1637) —
|
|
16512
|
+
* the session a bound identity belongs to, so the process this drives IS its holder. */
|
|
16513
|
+
constructor(cwd, mode, resume) {
|
|
16412
16514
|
this.cwd = cwd;
|
|
16413
16515
|
this.mode = mode;
|
|
16516
|
+
this.resume = resume;
|
|
16414
16517
|
}
|
|
16415
16518
|
cwd;
|
|
16416
16519
|
mode;
|
|
16520
|
+
resume;
|
|
16417
16521
|
nextId = 1;
|
|
16418
16522
|
initId;
|
|
16419
16523
|
sessionNewId;
|
|
@@ -16497,15 +16601,15 @@ var AcpDriver = class {
|
|
|
16497
16601
|
this.sessionNewId = this.nextId++;
|
|
16498
16602
|
return {
|
|
16499
16603
|
events: [],
|
|
16500
|
-
writes: [frame2({ id: this.sessionNewId, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } })]
|
|
16604
|
+
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: [] } })]
|
|
16501
16605
|
};
|
|
16502
16606
|
}
|
|
16503
16607
|
if (msg.id === this.sessionNewId) {
|
|
16504
16608
|
this.sessionNewId = void 0;
|
|
16505
|
-
const sessionId2 = msg.result?.sessionId;
|
|
16609
|
+
const sessionId2 = msg.error ? void 0 : msg.result?.sessionId ?? this.resume;
|
|
16506
16610
|
if (!sessionId2) {
|
|
16507
16611
|
return {
|
|
16508
|
-
events: [{ kind: "error", message:
|
|
16612
|
+
events: [{ kind: "error", message: `${this.resume ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}` }],
|
|
16509
16613
|
writes: []
|
|
16510
16614
|
};
|
|
16511
16615
|
}
|
|
@@ -16628,7 +16732,7 @@ function splitLines(buffer, chunk) {
|
|
|
16628
16732
|
return { lines: parts.filter((l) => l.trim()), rest };
|
|
16629
16733
|
}
|
|
16630
16734
|
function startSession(opts) {
|
|
16631
|
-
const cwd = resolve2(opts.cwd.replace(/^~(?=$|\/)/,
|
|
16735
|
+
const cwd = resolve2(opts.cwd.replace(/^~(?=$|\/)/, homedir5()));
|
|
16632
16736
|
if (!existsSync4(cwd)) {
|
|
16633
16737
|
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
16634
16738
|
}
|
|
@@ -16641,7 +16745,8 @@ function startSession(opts) {
|
|
|
16641
16745
|
env: {
|
|
16642
16746
|
...process.env,
|
|
16643
16747
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
16644
|
-
...opts.token ? { PAIGY_TOKEN: opts.token } : {}
|
|
16748
|
+
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
16749
|
+
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {}
|
|
16645
16750
|
},
|
|
16646
16751
|
stdio: ["pipe", "pipe", "pipe"]
|
|
16647
16752
|
});
|
|
@@ -16661,7 +16766,7 @@ function startSession(opts) {
|
|
|
16661
16766
|
opts.onEvent({ kind: "error", message: e.message });
|
|
16662
16767
|
opts.onExit?.();
|
|
16663
16768
|
});
|
|
16664
|
-
const driver = createAcpDriver({ cwd, mode: opts.mode });
|
|
16769
|
+
const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.resume ? { resume: opts.resume } : {} });
|
|
16665
16770
|
let buffer = "";
|
|
16666
16771
|
child.stdout?.on("data", (chunk) => {
|
|
16667
16772
|
const { lines: lines2, rest } = splitLines(buffer, String(chunk));
|
|
@@ -16723,7 +16828,7 @@ async function step(session, state, rail) {
|
|
|
16723
16828
|
else state.goal = { id: brief2.goalId, revision: brief2.revision };
|
|
16724
16829
|
const outcome = brief2.outcome?.trim() ?? "";
|
|
16725
16830
|
const said = (brief2.entries ?? []).map((e) => entryWords(e).trim()).filter((w) => w && w.toLowerCase() !== outcome.toLowerCase());
|
|
16726
|
-
|
|
16831
|
+
send2(session, state, [outcome, ...said].filter(Boolean).join("\n\n"));
|
|
16727
16832
|
return;
|
|
16728
16833
|
}
|
|
16729
16834
|
const brief = await rail.read(state.goal.id);
|
|
@@ -16742,9 +16847,9 @@ async function step(session, state, rail) {
|
|
|
16742
16847
|
}
|
|
16743
16848
|
if (state.asks?.size || !state.resting) return;
|
|
16744
16849
|
const words2 = unheard(brief, state);
|
|
16745
|
-
if (words2.length)
|
|
16850
|
+
if (words2.length) send2(session, state, words2.join("\n\n"));
|
|
16746
16851
|
}
|
|
16747
|
-
function
|
|
16852
|
+
function send2(session, state, text) {
|
|
16748
16853
|
state.resting = false;
|
|
16749
16854
|
session.send(text);
|
|
16750
16855
|
}
|
|
@@ -16877,7 +16982,7 @@ function runHarness(opts) {
|
|
|
16877
16982
|
let tail = [];
|
|
16878
16983
|
const state = { resting: true };
|
|
16879
16984
|
let session = null;
|
|
16880
|
-
const rail = railFor({ token: opts.token });
|
|
16985
|
+
const rail = railFor({ token: opts.token, ...opts.resume ? { reach: reachAs(opts.resume) } : {} });
|
|
16881
16986
|
async function handle(event) {
|
|
16882
16987
|
if (event.kind === "error") {
|
|
16883
16988
|
opts.log(`\u26A0 ${event.message}`);
|
|
@@ -16925,6 +17030,7 @@ function runHarness(opts) {
|
|
|
16925
17030
|
cwd: opts.cwd,
|
|
16926
17031
|
mode: opts.mode,
|
|
16927
17032
|
...opts.token ? { token: opts.token } : {},
|
|
17033
|
+
...opts.resume ? { resume: opts.resume } : {},
|
|
16928
17034
|
...opts.bin ? { bin: opts.bin } : {},
|
|
16929
17035
|
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
16930
17036
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
@@ -16957,7 +17063,7 @@ function runHarness(opts) {
|
|
|
16957
17063
|
// src/update.ts
|
|
16958
17064
|
import { execFile } from "child_process";
|
|
16959
17065
|
import { createRequire } from "module";
|
|
16960
|
-
import { dirname as dirname3, join as
|
|
17066
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
16961
17067
|
import { promisify } from "util";
|
|
16962
17068
|
var SERVICE_LABEL = "ai.paigy.harness";
|
|
16963
17069
|
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
@@ -16980,7 +17086,7 @@ async function latestVersion() {
|
|
|
16980
17086
|
}
|
|
16981
17087
|
async function install(version) {
|
|
16982
17088
|
const bin = dirname3(process.execPath);
|
|
16983
|
-
await promisify(execFile)(
|
|
17089
|
+
await promisify(execFile)(join5(bin, "npm"), ["install", "-g", `@paigy/harness@${version}`], {
|
|
16984
17090
|
env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}`, npm_config_update_notifier: "false" },
|
|
16985
17091
|
timeout: 5 * 6e4
|
|
16986
17092
|
});
|
|
@@ -17014,7 +17120,7 @@ function selfUpdater(deps) {
|
|
|
17014
17120
|
}
|
|
17015
17121
|
|
|
17016
17122
|
// src/host.ts
|
|
17017
|
-
var HOST_FILE =
|
|
17123
|
+
var HOST_FILE = join6(homedir6(), ".paigy", "host.json");
|
|
17018
17124
|
function readHostState() {
|
|
17019
17125
|
try {
|
|
17020
17126
|
const s = JSON.parse(readFileSync3(HOST_FILE, "utf8"));
|
|
@@ -17038,8 +17144,8 @@ var claimedRun = (sessionId2) => {
|
|
|
17038
17144
|
const slot = sessionSlot(sessionId2);
|
|
17039
17145
|
return { slot, key: runKey(slot) };
|
|
17040
17146
|
};
|
|
17041
|
-
async function slotHasWaitingWork(token) {
|
|
17042
|
-
return (await checkReplies({ token })).deliveries.length > 0;
|
|
17147
|
+
async function slotHasWaitingWork(token, session) {
|
|
17148
|
+
return (await checkReplies({ token, ...session ? { reach: reachAs(session) } : {} })).deliveries.length > 0;
|
|
17043
17149
|
}
|
|
17044
17150
|
var PRESENCE_FRESH_MS = 3 * 6e4;
|
|
17045
17151
|
function wakeAction(live, now, serverSeenAt) {
|
|
@@ -17132,9 +17238,11 @@ function startHost(opts) {
|
|
|
17132
17238
|
if (wakeAction(!!live, Date.now(), seenByServer) === "skip") return;
|
|
17133
17239
|
const token = readToken(slot);
|
|
17134
17240
|
if (!token) return;
|
|
17135
|
-
const
|
|
17241
|
+
const owner = ownerSession(slot);
|
|
17242
|
+
if (owner && !allowed(owner.cwd, opts.wsDeps)) return;
|
|
17243
|
+
const workspace = owner?.cwd ?? resolveWakeDir(slotIdentity(slot).workspace, opts.wsDeps);
|
|
17136
17244
|
if (!workspace) return;
|
|
17137
|
-
if (!await slotHasWaitingWork(token).catch(() => false)) return;
|
|
17245
|
+
if (!await slotHasWaitingWork(token, owner?.sessionId).catch(() => false)) return;
|
|
17138
17246
|
const label = slotName(slot) ?? slot;
|
|
17139
17247
|
const log2 = (line) => opts.log(`[${label}] ${line}`);
|
|
17140
17248
|
const run = runHarness({
|
|
@@ -17143,6 +17251,7 @@ function startHost(opts) {
|
|
|
17143
17251
|
mode: "bypass",
|
|
17144
17252
|
prompt: "",
|
|
17145
17253
|
token,
|
|
17254
|
+
...owner ? { resume: owner.sessionId } : {},
|
|
17146
17255
|
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
17147
17256
|
onExit: () => {
|
|
17148
17257
|
runs.delete(key);
|
|
@@ -17153,7 +17262,7 @@ function startHost(opts) {
|
|
|
17153
17262
|
runs.set(key, { run, label, token });
|
|
17154
17263
|
void heartbeat(void 0, { token }).catch(() => {
|
|
17155
17264
|
});
|
|
17156
|
-
opts.log(`\u25B6 woke ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
17265
|
+
opts.log(`\u25B6 ${owner ? "resumed" : "woke"} ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
17157
17266
|
}
|
|
17158
17267
|
}
|
|
17159
17268
|
const listening = /* @__PURE__ */ new Map();
|
|
@@ -17462,7 +17571,7 @@ function staleWhy(note) {
|
|
|
17462
17571
|
|
|
17463
17572
|
// src/triage/judge.ts
|
|
17464
17573
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
17465
|
-
import { homedir as
|
|
17574
|
+
import { homedir as homedir7 } from "os";
|
|
17466
17575
|
import { delimiter as delimiter3 } from "path";
|
|
17467
17576
|
var PRIVACY = {
|
|
17468
17577
|
ollama: "never leaves this machine",
|
|
@@ -17566,7 +17675,7 @@ async function judgeFor(asked, deps = {}) {
|
|
|
17566
17675
|
const out = spawnSync2(bin, runner.argv(null), {
|
|
17567
17676
|
input: prompt,
|
|
17568
17677
|
encoding: "utf8",
|
|
17569
|
-
cwd:
|
|
17678
|
+
cwd: homedir7(),
|
|
17570
17679
|
timeout: RUN_TIMEOUT_MS,
|
|
17571
17680
|
maxBuffer: 32 * 1024 * 1024,
|
|
17572
17681
|
env: { ...process.env, PATH: [process.env["PATH"], ...probeDirs()].filter(Boolean).join(delimiter3) }
|
|
@@ -17864,7 +17973,7 @@ var win = null;
|
|
|
17864
17973
|
function log(message) {
|
|
17865
17974
|
win?.webContents.send("paigy:log", message);
|
|
17866
17975
|
}
|
|
17867
|
-
var ICON =
|
|
17976
|
+
var ICON = join7(here, "..", "assets", "icon.png");
|
|
17868
17977
|
function createWindow() {
|
|
17869
17978
|
win = new BrowserWindow({
|
|
17870
17979
|
// Sized for the working log, which is now the window's centerpiece; the layout is a
|
|
@@ -17876,9 +17985,9 @@ function createWindow() {
|
|
|
17876
17985
|
minHeight: 520,
|
|
17877
17986
|
icon: ICON,
|
|
17878
17987
|
// Windows/Linux window icon; macOS uses the dock icon below
|
|
17879
|
-
webPreferences: { preload:
|
|
17988
|
+
webPreferences: { preload: join7(here, "preload.cjs"), contextIsolation: true, nodeIntegration: false }
|
|
17880
17989
|
});
|
|
17881
|
-
void win.loadFile(
|
|
17990
|
+
void win.loadFile(join7(here, "..", "renderer", "index.html"));
|
|
17882
17991
|
}
|
|
17883
17992
|
var host = null;
|
|
17884
17993
|
app.whenReady().then(() => {
|
|
@@ -17947,7 +18056,7 @@ ipcMain.handle("paigy:agent", async (_e, name) => {
|
|
|
17947
18056
|
});
|
|
17948
18057
|
ipcMain.handle("paigy:agent-log", (_e, name) => {
|
|
17949
18058
|
try {
|
|
17950
|
-
const all = readFileSync4(
|
|
18059
|
+
const all = readFileSync4(join7(homedir8(), ".paigy", "host.log"), "utf8").split("\n");
|
|
17951
18060
|
const mine = all.filter((l) => l.includes(`[${name}]`) || /[▶✖◉⚠]/.test(l) && l.includes(name));
|
|
17952
18061
|
return mine.slice(-500).join("\n");
|
|
17953
18062
|
} catch {
|