@paigy/harness 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +142 -346
- package/dist/main.js +221 -454
- package/package.json +13 -12
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
|
|
4583
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
4584
4584
|
|
|
4585
4585
|
// ../../packages/sdk/dist/index.js
|
|
4586
4586
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
@@ -12234,9 +12234,9 @@ var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({
|
|
|
12234
12234
|
var ClaimGoalSchema = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
12235
12235
|
var GetGoalSchema = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
12236
12236
|
var 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.";
|
|
12237
|
-
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. Returns the new revision and a prose summary.";
|
|
12237
|
+
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. Returns the new revision and a prose summary.";
|
|
12238
12238
|
var 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.";
|
|
12239
|
-
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 each
|
|
12239
|
+
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, 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.";
|
|
12240
12240
|
var CheckRepliesSchema = external_exports.object({}).strict();
|
|
12241
12241
|
var GetThreadSchema = external_exports.object({
|
|
12242
12242
|
parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
@@ -12670,67 +12670,6 @@ var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
|
12670
12670
|
inFlight: external_exports.boolean().optional()
|
|
12671
12671
|
})
|
|
12672
12672
|
]);
|
|
12673
|
-
var CallbackTriggerSchema = external_exports.enum(["on_done", "on_blocked", "scheduled"]);
|
|
12674
|
-
var NotifyResponseSchema = external_exports.object({
|
|
12675
|
-
notificationId: external_exports.string(),
|
|
12676
|
-
workId: external_exports.string().uuid().optional(),
|
|
12677
|
-
decisionId: external_exports.string().uuid().optional(),
|
|
12678
|
-
status: NotifyStatusSchema,
|
|
12679
|
-
createdAt: external_exports.string().datetime(),
|
|
12680
|
-
answer: UserAnswerSchema.optional(),
|
|
12681
|
-
answeredAt: external_exports.string().datetime().optional(),
|
|
12682
|
-
/** Ride-alongs for THIS agent — pending work it should pick up when it's done with
|
|
12683
|
-
* what it came for. Present on any reply, because an unwakeable agent's only
|
|
12684
|
-
* reliable moment is one it initiated. Absent/empty = nothing owed. */
|
|
12685
|
-
also: external_exports.array(RideAlongSchema).optional()
|
|
12686
|
-
});
|
|
12687
|
-
var NotifyPlanUnitSchema = external_exports.object({
|
|
12688
|
-
notificationId: external_exports.string(),
|
|
12689
|
-
/** The unit's own heading, so the agent can tell which of its paragraphs this became. */
|
|
12690
|
-
title: external_exports.string(),
|
|
12691
|
-
/** How loudly this unit was arbitrated to arrive — per unit, which is the point of units. */
|
|
12692
|
-
level: NotifyLevelSchema,
|
|
12693
|
-
/** Answered from something the user already decided: nobody is interrupted, and a trail card
|
|
12694
|
-
* says so. The agent should not wait on this one. */
|
|
12695
|
-
settled: external_exports.literal(true).optional(),
|
|
12696
|
-
/** What this unit would need to be answerable and does not carry (#894). A PROPOSAL to the
|
|
12697
|
-
* agent — nothing here changed the ask, and ignoring it costs nothing. */
|
|
12698
|
-
needs: external_exports.array(external_exports.enum(["options", "visuals"])).optional(),
|
|
12699
|
-
/** The SHAPE the broker would give this unit, for the agent to ratify (#886/#894). The
|
|
12700
|
-
* split layer reads prose and can see that a paragraph is a yes/no or a pick-one — but a
|
|
12701
|
-
* broker that DECIDES that destroys the only fact separating a statement from a real ask
|
|
12702
|
-
* (#731), so it is offered, never applied: the unit is stored `text` until the agent
|
|
12703
|
-
* confirms the shape (POST /notify/:id/confirm). Ignoring it costs nothing. */
|
|
12704
|
-
proposal: external_exports.object({
|
|
12705
|
-
select: SelectShapeSchema,
|
|
12706
|
-
options: external_exports.array(external_exports.object({ label: external_exports.string().min(1) })).optional()
|
|
12707
|
-
}).optional(),
|
|
12708
|
-
/** What the broker READ this unit as wanting from the human (#952 layer 2): a `decision`
|
|
12709
|
-
* between alternatives, an `approval` the agent is blocked on, or `knowledge` it just
|
|
12710
|
-
* needs to know. Reported so the agent can correct a misread the same way it ratifies a
|
|
12711
|
-
* shape — the read RAISES (a decision always asks) and never silences a question the
|
|
12712
|
-
* agent declared (#731, #923). */
|
|
12713
|
-
wants: external_exports.enum(["decision", "approval", "knowledge"]).optional()
|
|
12714
|
-
});
|
|
12715
|
-
var NotifyPlanSchema = external_exports.object({
|
|
12716
|
-
units: external_exports.array(NotifyPlanUnitSchema),
|
|
12717
|
-
/** Which unit is this arrival's ONE interruption (units-design.md D23). Absent means nobody
|
|
12718
|
-
* was interrupted — every unit was either settled or quiet enough to sit in the inbox. */
|
|
12719
|
-
speaks: external_exports.string().optional()
|
|
12720
|
-
});
|
|
12721
|
-
var UserResponseSchema = external_exports.object({
|
|
12722
|
-
requestId: external_exports.string(),
|
|
12723
|
-
answer: UserAnswerSchema,
|
|
12724
|
-
answeredAt: external_exports.string().datetime(),
|
|
12725
|
-
transcript: external_exports.string().optional(),
|
|
12726
|
-
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the item was
|
|
12727
|
-
* E2EE-sealed. Present → the server persists it opaquely and branches status on `ignored`;
|
|
12728
|
-
* the plaintext `answer` is a placeholder (`{ kind: "ignored" }`) the server ignores for a
|
|
12729
|
-
* sealed row. Absent = today's plaintext answer, unchanged. */
|
|
12730
|
-
sealed: external_exports.lazy(() => SealedAnswerSchema).optional(),
|
|
12731
|
-
/** Coverage report (#396): which of the ask's declared `points` were addressed. */
|
|
12732
|
-
covered: external_exports.array(external_exports.string()).optional()
|
|
12733
|
-
});
|
|
12734
12673
|
var VoiceKeySchema = external_exports.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
12735
12674
|
var AgendaTurnSchema = external_exports.object({
|
|
12736
12675
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
@@ -12914,10 +12853,6 @@ var InboxItemSchema = external_exports.object({
|
|
|
12914
12853
|
goalState: external_exports.string().optional()
|
|
12915
12854
|
}).optional()
|
|
12916
12855
|
});
|
|
12917
|
-
var SnoozeRequestSchema = external_exports.object({
|
|
12918
|
-
requestId: external_exports.string(),
|
|
12919
|
-
until: external_exports.string().datetime()
|
|
12920
|
-
});
|
|
12921
12856
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
12922
12857
|
var PushTokenSchema = external_exports.object({
|
|
12923
12858
|
voipToken: external_exports.string().min(1).optional(),
|
|
@@ -13073,6 +13008,9 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
13073
13008
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
13074
13009
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
13075
13010
|
runtime: external_exports.object({
|
|
13011
|
+
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
13012
|
+
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
13013
|
+
version: external_exports.string().optional(),
|
|
13076
13014
|
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
13077
13015
|
workspaces: external_exports.array(external_exports.string()).optional()
|
|
13078
13016
|
}).optional(),
|
|
@@ -13135,19 +13073,6 @@ var MoveSchema = external_exports.object({
|
|
|
13135
13073
|
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
13136
13074
|
learnedFrom: external_exports.object({ id: external_exports.string(), name: external_exports.string() }).nullable()
|
|
13137
13075
|
});
|
|
13138
|
-
var CreateRequestSchema = external_exports.object({
|
|
13139
|
-
/** The connection (token id) to send to, from GET /api/tokens. */
|
|
13140
|
-
tokenId: external_exports.string(),
|
|
13141
|
-
/** The user's message to the agent. */
|
|
13142
|
-
text: external_exports.string().min(1),
|
|
13143
|
-
/** Land the request on an existing conversation thread (History → "Continue")
|
|
13144
|
-
* instead of minting a fresh one. Must belong to the requesting user. */
|
|
13145
|
-
parentId: external_exports.string().optional(),
|
|
13146
|
-
/** Point the agent at a past conversation (possibly with a different agent) as
|
|
13147
|
-
* starting context (History → "New session from this"). A reference, not a copy —
|
|
13148
|
-
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
13149
|
-
contextParentId: external_exports.string().optional()
|
|
13150
|
-
});
|
|
13151
13076
|
var QueueQuestionSchema = external_exports.object({
|
|
13152
13077
|
/** The decision need's id — what an answer is accepted against. */
|
|
13153
13078
|
id: external_exports.string(),
|
|
@@ -13239,38 +13164,6 @@ var NoteSchema = external_exports.object({
|
|
|
13239
13164
|
lastRideAt: external_exports.string().nullable().default(null),
|
|
13240
13165
|
createdAt: external_exports.string()
|
|
13241
13166
|
});
|
|
13242
|
-
var CreateNoteSchema = external_exports.object({
|
|
13243
|
-
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
13244
|
-
text: external_exports.string().min(1).max(4e3),
|
|
13245
|
-
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
13246
|
-
* their next call instead of being handed to an agent. Everything else about the
|
|
13247
|
-
* note is identical; this is the one parameter that separates the two. */
|
|
13248
|
-
forMe: external_exports.boolean().optional(),
|
|
13249
|
-
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
13250
|
-
dueAt: external_exports.string().datetime().optional(),
|
|
13251
|
-
repeat: NoteRepeatSchema.optional()
|
|
13252
|
-
});
|
|
13253
|
-
var RecordDecisionSchema = external_exports.object({
|
|
13254
|
-
/** An open decision (from /clarify) to answer. */
|
|
13255
|
-
decisionId: external_exports.string().optional(),
|
|
13256
|
-
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
13257
|
-
question: external_exports.string().min(1).max(500).optional(),
|
|
13258
|
-
/** The ruling. */
|
|
13259
|
-
answer: external_exports.string().min(1).max(2e3)
|
|
13260
|
-
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
13261
|
-
var AssignNoteSchema = external_exports.object({
|
|
13262
|
-
/** An EXISTING agent: token id or nickname. Omit when spawning fresh. */
|
|
13263
|
-
target: external_exports.string().min(1).optional(),
|
|
13264
|
-
/** Spawn a NEW session for this note (companion.md §2.2): the assignee doesn't
|
|
13265
|
-
* exist yet — mint it on a live desktop that advertises the harness+workspace,
|
|
13266
|
-
* named after the note. The brief arrives as its opening request. */
|
|
13267
|
-
spawn: external_exports.object({
|
|
13268
|
-
hostTokenId: external_exports.string().uuid(),
|
|
13269
|
-
harness: external_exports.string().min(1),
|
|
13270
|
-
workspace: external_exports.string().min(1)
|
|
13271
|
-
}).optional()
|
|
13272
|
-
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
13273
|
-
var TriageVerdictSchema = external_exports.enum(["keep", "stale", "close", "assign"]);
|
|
13274
13167
|
var TriageItemSchema = external_exports.object({
|
|
13275
13168
|
noteId: external_exports.string(),
|
|
13276
13169
|
/** The note's headline at run time. */
|
|
@@ -13406,20 +13299,6 @@ var PairingRevealSchema = external_exports.object({
|
|
|
13406
13299
|
ed25519: external_exports.string(),
|
|
13407
13300
|
nonce: external_exports.string()
|
|
13408
13301
|
});
|
|
13409
|
-
var DeviceCodeRequestSchema = external_exports.object({
|
|
13410
|
-
/** The agent's suggested name for the pairing — the human sees it pre-filled at
|
|
13411
|
-
* approval and can override. Optional; blank → a default silly name server-side. */
|
|
13412
|
-
suggestedName: external_exports.string().optional(),
|
|
13413
|
-
/** Legacy alias for suggestedName (older MCPs sent `agent`). Accepted for
|
|
13414
|
-
* back-compat; `suggestedName` wins when both are present. ponytail: drop once no
|
|
13415
|
-
* pre-`name` MCP is in the wild. */
|
|
13416
|
-
agent: external_exports.union([external_exports.string(), external_exports.object({ name: external_exports.string().optional() })]).optional(),
|
|
13417
|
-
device: external_exports.string().optional(),
|
|
13418
|
-
proto: external_exports.string().optional(),
|
|
13419
|
-
// compiled-in tag, e.g. "paigy-pair-v2|sas=24"
|
|
13420
|
-
commitment: external_exports.string().optional()
|
|
13421
|
-
// agent's Ca
|
|
13422
|
-
});
|
|
13423
13302
|
var DeviceCodeSchema = external_exports.object({
|
|
13424
13303
|
device_code: external_exports.string(),
|
|
13425
13304
|
user_code: external_exports.string(),
|
|
@@ -13453,11 +13332,6 @@ var DeviceInfoSchema = external_exports.object({
|
|
|
13453
13332
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
13454
13333
|
uik_pub: external_exports.string().nullable().optional()
|
|
13455
13334
|
});
|
|
13456
|
-
var DeviceTokenRequestSchema = external_exports.object({
|
|
13457
|
-
device_code: external_exports.string(),
|
|
13458
|
-
reveal: PairingRevealSchema.optional()
|
|
13459
|
-
// agent's reveal {x25519, ed25519, nonce}
|
|
13460
|
-
});
|
|
13461
13335
|
var DeviceTokenSchema = external_exports.object({
|
|
13462
13336
|
access_token: external_exports.string(),
|
|
13463
13337
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
@@ -13479,14 +13353,6 @@ var DeviceTokenSchema = external_exports.object({
|
|
|
13479
13353
|
// present once the phone reveals
|
|
13480
13354
|
uik_pub: external_exports.string().nullable().optional()
|
|
13481
13355
|
});
|
|
13482
|
-
var DeviceCommitRequestSchema = external_exports.object({
|
|
13483
|
-
code: external_exports.string(),
|
|
13484
|
-
commitment: external_exports.string()
|
|
13485
|
-
// Cb
|
|
13486
|
-
});
|
|
13487
|
-
var DevicePeerCommitSchema = external_exports.object({
|
|
13488
|
-
commitment: external_exports.string().nullable()
|
|
13489
|
-
});
|
|
13490
13356
|
var SupportRequestSchema = external_exports.object({
|
|
13491
13357
|
email: external_exports.string().email().max(320),
|
|
13492
13358
|
message: external_exports.string().trim().min(1).max(5e3),
|
|
@@ -13506,15 +13372,6 @@ var NotificationFeedbackKindSchema = external_exports.enum([
|
|
|
13506
13372
|
"other"
|
|
13507
13373
|
// anything else — the note carries it.
|
|
13508
13374
|
]);
|
|
13509
|
-
var NotificationFeedbackSchema = external_exports.object({
|
|
13510
|
-
notificationId: external_exports.string(),
|
|
13511
|
-
kind: NotificationFeedbackKindSchema,
|
|
13512
|
-
/** Optional free-text elaboration for a preset; required (non-empty) for 'other'. */
|
|
13513
|
-
note: external_exports.string().trim().max(2e3).optional()
|
|
13514
|
-
}).superRefine((r, ctx) => {
|
|
13515
|
-
if (r.kind === "other" && !r.note)
|
|
13516
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["note"], message: "note is required for 'other' feedback" });
|
|
13517
|
-
});
|
|
13518
13375
|
var FeedbackResolutionSchema = external_exports.enum(["broker_fixed", "sent_to_agent"]);
|
|
13519
13376
|
var FeedbackOutcomeSchema = external_exports.object({
|
|
13520
13377
|
resolution: FeedbackResolutionSchema,
|
|
@@ -13924,6 +13781,146 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
13924
13781
|
};
|
|
13925
13782
|
}
|
|
13926
13783
|
|
|
13784
|
+
// src/main.ts
|
|
13785
|
+
var import_qrcode = __toESM(require_lib(), 1);
|
|
13786
|
+
import { homedir as homedir7, hostname } from "os";
|
|
13787
|
+
|
|
13788
|
+
// src/harness/catalog.ts
|
|
13789
|
+
import { spawnSync } from "child_process";
|
|
13790
|
+
import { existsSync as existsSync2 } from "fs";
|
|
13791
|
+
import { homedir as homedir2 } from "os";
|
|
13792
|
+
import { delimiter, dirname, join as join2 } from "path";
|
|
13793
|
+
var CATALOG = [
|
|
13794
|
+
{
|
|
13795
|
+
name: "claude",
|
|
13796
|
+
label: "Claude Code",
|
|
13797
|
+
cli: "claude",
|
|
13798
|
+
adapter: "claude-agent-acp",
|
|
13799
|
+
authProbe: ["claude", "auth", "status"],
|
|
13800
|
+
loginHint: "Run `claude` once in a terminal and sign in.",
|
|
13801
|
+
installCli: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
13802
|
+
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
13803
|
+
},
|
|
13804
|
+
{
|
|
13805
|
+
name: "codex",
|
|
13806
|
+
label: "Codex",
|
|
13807
|
+
cli: "codex",
|
|
13808
|
+
adapter: "codex-acp",
|
|
13809
|
+
authProbe: ["codex", "login", "status"],
|
|
13810
|
+
loginHint: "Run `codex login` to authenticate.",
|
|
13811
|
+
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
13812
|
+
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
13813
|
+
},
|
|
13814
|
+
{
|
|
13815
|
+
name: "agy",
|
|
13816
|
+
label: "Antigravity",
|
|
13817
|
+
cli: "agy",
|
|
13818
|
+
authProbe: ["agy", "help"],
|
|
13819
|
+
loginHint: "Install and set up Antigravity (`agy`).",
|
|
13820
|
+
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
13821
|
+
}
|
|
13822
|
+
];
|
|
13823
|
+
function probeDirs(home = homedir2()) {
|
|
13824
|
+
return [
|
|
13825
|
+
dirname(process.execPath),
|
|
13826
|
+
join2(home, ".local", "bin"),
|
|
13827
|
+
"/usr/local/bin",
|
|
13828
|
+
"/opt/homebrew/bin",
|
|
13829
|
+
join2(home, ".npm-global", "bin")
|
|
13830
|
+
];
|
|
13831
|
+
}
|
|
13832
|
+
function which(bin) {
|
|
13833
|
+
const hit = spawnSync("which", [bin], { encoding: "utf8" });
|
|
13834
|
+
const fromPath = hit.status === 0 ? hit.stdout.trim() : "";
|
|
13835
|
+
if (fromPath) return fromPath;
|
|
13836
|
+
for (const dir of probeDirs()) {
|
|
13837
|
+
const candidate = join2(dir, bin);
|
|
13838
|
+
if (existsSync2(candidate)) return candidate;
|
|
13839
|
+
}
|
|
13840
|
+
return null;
|
|
13841
|
+
}
|
|
13842
|
+
function runProbe(argv) {
|
|
13843
|
+
const [cmd, ...args] = argv;
|
|
13844
|
+
if (!cmd) return false;
|
|
13845
|
+
const resolved = which(cmd);
|
|
13846
|
+
if (!resolved) return false;
|
|
13847
|
+
return spawnSync(resolved, args, {
|
|
13848
|
+
timeout: 1e4,
|
|
13849
|
+
stdio: "ignore",
|
|
13850
|
+
env: { ...process.env, PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter) }
|
|
13851
|
+
}).status === 0;
|
|
13852
|
+
}
|
|
13853
|
+
function detect(entry, deps = {}) {
|
|
13854
|
+
const find = deps.which ?? which;
|
|
13855
|
+
const probe = deps.probe ?? runProbe;
|
|
13856
|
+
const base = { name: entry.name, label: entry.label };
|
|
13857
|
+
const cliPath = find(entry.cli);
|
|
13858
|
+
if (!cliPath) return { ...base, status: "missing", hint: `${entry.label} is not installed.`, install: entry.installCli };
|
|
13859
|
+
const adapterPath = entry.adapter ? find(entry.adapter) : null;
|
|
13860
|
+
if (entry.adapter && !adapterPath) {
|
|
13861
|
+
return {
|
|
13862
|
+
...base,
|
|
13863
|
+
status: "adapter-missing",
|
|
13864
|
+
hint: `${entry.label} is installed, but its ACP adapter (${entry.adapter}) is not.`,
|
|
13865
|
+
...entry.installAdapter ? { install: entry.installAdapter } : {}
|
|
13866
|
+
};
|
|
13867
|
+
}
|
|
13868
|
+
const bin = adapterPath ?? cliPath;
|
|
13869
|
+
if (!probe(entry.authProbe)) return { ...base, status: "login", bin, hint: entry.loginHint };
|
|
13870
|
+
return { ...base, status: "ready", bin };
|
|
13871
|
+
}
|
|
13872
|
+
function detectAll(deps = {}) {
|
|
13873
|
+
return CATALOG.map((entry) => detect(entry, deps));
|
|
13874
|
+
}
|
|
13875
|
+
|
|
13876
|
+
// src/host.ts
|
|
13877
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
13878
|
+
import { homedir as homedir5 } from "os";
|
|
13879
|
+
import { join as join5 } from "path";
|
|
13880
|
+
|
|
13881
|
+
// src/workspaces.ts
|
|
13882
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
13883
|
+
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
13884
|
+
import { homedir as homedir3 } from "os";
|
|
13885
|
+
function workspacesFile() {
|
|
13886
|
+
return join3(homedir3(), ".paigy", "workspaces.json");
|
|
13887
|
+
}
|
|
13888
|
+
function listWorkspaces(deps) {
|
|
13889
|
+
try {
|
|
13890
|
+
const parsed = JSON.parse(readFileSync2(deps.file, "utf8"));
|
|
13891
|
+
return Array.isArray(parsed) ? parsed.filter((w) => typeof w === "string") : [];
|
|
13892
|
+
} catch {
|
|
13893
|
+
return [];
|
|
13894
|
+
}
|
|
13895
|
+
}
|
|
13896
|
+
function addWorkspace(dir, deps) {
|
|
13897
|
+
const next = resolve(dir.replace(/^~(?=$|\/)/, homedir3()));
|
|
13898
|
+
const all = listWorkspaces(deps);
|
|
13899
|
+
if (!all.includes(next)) {
|
|
13900
|
+
all.push(next);
|
|
13901
|
+
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
13902
|
+
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
13903
|
+
}
|
|
13904
|
+
return all;
|
|
13905
|
+
}
|
|
13906
|
+
function removeWorkspace(dir, deps) {
|
|
13907
|
+
const all = listWorkspaces(deps).filter((w) => w !== dir);
|
|
13908
|
+
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
13909
|
+
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
13910
|
+
return all;
|
|
13911
|
+
}
|
|
13912
|
+
function allowed(cwd, deps) {
|
|
13913
|
+
const target = resolve(cwd.replace(/^~(?=$|\/)/, homedir3()));
|
|
13914
|
+
return listWorkspaces(deps).some((w) => target === w || target.startsWith(`${w}/`));
|
|
13915
|
+
}
|
|
13916
|
+
function resolveWakeDir(pinned, deps) {
|
|
13917
|
+
if (pinned && allowed(pinned, deps)) {
|
|
13918
|
+
const dir = resolve(pinned.replace(/^~(?=$|\/)/, homedir3()));
|
|
13919
|
+
if ((deps.exists ?? existsSync3)(dir)) return dir;
|
|
13920
|
+
}
|
|
13921
|
+
return listWorkspaces(deps)[0];
|
|
13922
|
+
}
|
|
13923
|
+
|
|
13927
13924
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
13928
13925
|
var ignoreOverride2 = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
13929
13926
|
var defaultOptions2 = {
|
|
@@ -15307,9 +15304,9 @@ var UpdateGoalToolSchema2 = UpdateGoalSchema2.omit({ operationId: true }).extend
|
|
|
15307
15304
|
var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
15308
15305
|
var GetGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
15309
15306
|
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.";
|
|
15310
|
-
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. Returns the new revision and a prose summary.";
|
|
15307
|
+
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. Returns the new revision and a prose summary.";
|
|
15311
15308
|
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.";
|
|
15312
|
-
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 each
|
|
15309
|
+
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.";
|
|
15313
15310
|
var CheckRepliesSchema2 = external_exports.object({}).strict();
|
|
15314
15311
|
var GetThreadSchema2 = external_exports.object({
|
|
15315
15312
|
parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
@@ -15703,67 +15700,6 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
15703
15700
|
inFlight: external_exports.boolean().optional()
|
|
15704
15701
|
})
|
|
15705
15702
|
]);
|
|
15706
|
-
var CallbackTriggerSchema2 = external_exports.enum(["on_done", "on_blocked", "scheduled"]);
|
|
15707
|
-
var NotifyResponseSchema2 = external_exports.object({
|
|
15708
|
-
notificationId: external_exports.string(),
|
|
15709
|
-
workId: external_exports.string().uuid().optional(),
|
|
15710
|
-
decisionId: external_exports.string().uuid().optional(),
|
|
15711
|
-
status: NotifyStatusSchema2,
|
|
15712
|
-
createdAt: external_exports.string().datetime(),
|
|
15713
|
-
answer: UserAnswerSchema2.optional(),
|
|
15714
|
-
answeredAt: external_exports.string().datetime().optional(),
|
|
15715
|
-
/** Ride-alongs for THIS agent — pending work it should pick up when it's done with
|
|
15716
|
-
* what it came for. Present on any reply, because an unwakeable agent's only
|
|
15717
|
-
* reliable moment is one it initiated. Absent/empty = nothing owed. */
|
|
15718
|
-
also: external_exports.array(RideAlongSchema2).optional()
|
|
15719
|
-
});
|
|
15720
|
-
var NotifyPlanUnitSchema2 = external_exports.object({
|
|
15721
|
-
notificationId: external_exports.string(),
|
|
15722
|
-
/** The unit's own heading, so the agent can tell which of its paragraphs this became. */
|
|
15723
|
-
title: external_exports.string(),
|
|
15724
|
-
/** How loudly this unit was arbitrated to arrive — per unit, which is the point of units. */
|
|
15725
|
-
level: NotifyLevelSchema2,
|
|
15726
|
-
/** Answered from something the user already decided: nobody is interrupted, and a trail card
|
|
15727
|
-
* says so. The agent should not wait on this one. */
|
|
15728
|
-
settled: external_exports.literal(true).optional(),
|
|
15729
|
-
/** What this unit would need to be answerable and does not carry (#894). A PROPOSAL to the
|
|
15730
|
-
* agent — nothing here changed the ask, and ignoring it costs nothing. */
|
|
15731
|
-
needs: external_exports.array(external_exports.enum(["options", "visuals"])).optional(),
|
|
15732
|
-
/** The SHAPE the broker would give this unit, for the agent to ratify (#886/#894). The
|
|
15733
|
-
* split layer reads prose and can see that a paragraph is a yes/no or a pick-one — but a
|
|
15734
|
-
* broker that DECIDES that destroys the only fact separating a statement from a real ask
|
|
15735
|
-
* (#731), so it is offered, never applied: the unit is stored `text` until the agent
|
|
15736
|
-
* confirms the shape (POST /notify/:id/confirm). Ignoring it costs nothing. */
|
|
15737
|
-
proposal: external_exports.object({
|
|
15738
|
-
select: SelectShapeSchema2,
|
|
15739
|
-
options: external_exports.array(external_exports.object({ label: external_exports.string().min(1) })).optional()
|
|
15740
|
-
}).optional(),
|
|
15741
|
-
/** What the broker READ this unit as wanting from the human (#952 layer 2): a `decision`
|
|
15742
|
-
* between alternatives, an `approval` the agent is blocked on, or `knowledge` it just
|
|
15743
|
-
* needs to know. Reported so the agent can correct a misread the same way it ratifies a
|
|
15744
|
-
* shape — the read RAISES (a decision always asks) and never silences a question the
|
|
15745
|
-
* agent declared (#731, #923). */
|
|
15746
|
-
wants: external_exports.enum(["decision", "approval", "knowledge"]).optional()
|
|
15747
|
-
});
|
|
15748
|
-
var NotifyPlanSchema2 = external_exports.object({
|
|
15749
|
-
units: external_exports.array(NotifyPlanUnitSchema2),
|
|
15750
|
-
/** Which unit is this arrival's ONE interruption (units-design.md D23). Absent means nobody
|
|
15751
|
-
* was interrupted — every unit was either settled or quiet enough to sit in the inbox. */
|
|
15752
|
-
speaks: external_exports.string().optional()
|
|
15753
|
-
});
|
|
15754
|
-
var UserResponseSchema2 = external_exports.object({
|
|
15755
|
-
requestId: external_exports.string(),
|
|
15756
|
-
answer: UserAnswerSchema2,
|
|
15757
|
-
answeredAt: external_exports.string().datetime(),
|
|
15758
|
-
transcript: external_exports.string().optional(),
|
|
15759
|
-
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the item was
|
|
15760
|
-
* E2EE-sealed. Present → the server persists it opaquely and branches status on `ignored`;
|
|
15761
|
-
* the plaintext `answer` is a placeholder (`{ kind: "ignored" }`) the server ignores for a
|
|
15762
|
-
* sealed row. Absent = today's plaintext answer, unchanged. */
|
|
15763
|
-
sealed: external_exports.lazy(() => SealedAnswerSchema2).optional(),
|
|
15764
|
-
/** Coverage report (#396): which of the ask's declared `points` were addressed. */
|
|
15765
|
-
covered: external_exports.array(external_exports.string()).optional()
|
|
15766
|
-
});
|
|
15767
15703
|
var VoiceKeySchema2 = external_exports.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
15768
15704
|
var AgendaTurnSchema2 = external_exports.object({
|
|
15769
15705
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
@@ -15947,10 +15883,6 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
15947
15883
|
goalState: external_exports.string().optional()
|
|
15948
15884
|
}).optional()
|
|
15949
15885
|
});
|
|
15950
|
-
var SnoozeRequestSchema2 = external_exports.object({
|
|
15951
|
-
requestId: external_exports.string(),
|
|
15952
|
-
until: external_exports.string().datetime()
|
|
15953
|
-
});
|
|
15954
15886
|
var APNS_TOKEN_RE2 = /^[0-9a-fA-F]{64}$/;
|
|
15955
15887
|
var PushTokenSchema2 = external_exports.object({
|
|
15956
15888
|
voipToken: external_exports.string().min(1).optional(),
|
|
@@ -16106,6 +16038,9 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
16106
16038
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
16107
16039
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
16108
16040
|
runtime: external_exports.object({
|
|
16041
|
+
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
16042
|
+
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
16043
|
+
version: external_exports.string().optional(),
|
|
16109
16044
|
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
16110
16045
|
workspaces: external_exports.array(external_exports.string()).optional()
|
|
16111
16046
|
}).optional(),
|
|
@@ -16168,19 +16103,6 @@ var MoveSchema2 = external_exports.object({
|
|
|
16168
16103
|
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
16169
16104
|
learnedFrom: external_exports.object({ id: external_exports.string(), name: external_exports.string() }).nullable()
|
|
16170
16105
|
});
|
|
16171
|
-
var CreateRequestSchema2 = external_exports.object({
|
|
16172
|
-
/** The connection (token id) to send to, from GET /api/tokens. */
|
|
16173
|
-
tokenId: external_exports.string(),
|
|
16174
|
-
/** The user's message to the agent. */
|
|
16175
|
-
text: external_exports.string().min(1),
|
|
16176
|
-
/** Land the request on an existing conversation thread (History → "Continue")
|
|
16177
|
-
* instead of minting a fresh one. Must belong to the requesting user. */
|
|
16178
|
-
parentId: external_exports.string().optional(),
|
|
16179
|
-
/** Point the agent at a past conversation (possibly with a different agent) as
|
|
16180
|
-
* starting context (History → "New session from this"). A reference, not a copy —
|
|
16181
|
-
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
16182
|
-
contextParentId: external_exports.string().optional()
|
|
16183
|
-
});
|
|
16184
16106
|
var QueueQuestionSchema2 = external_exports.object({
|
|
16185
16107
|
/** The decision need's id — what an answer is accepted against. */
|
|
16186
16108
|
id: external_exports.string(),
|
|
@@ -16272,38 +16194,6 @@ var NoteSchema2 = external_exports.object({
|
|
|
16272
16194
|
lastRideAt: external_exports.string().nullable().default(null),
|
|
16273
16195
|
createdAt: external_exports.string()
|
|
16274
16196
|
});
|
|
16275
|
-
var CreateNoteSchema2 = external_exports.object({
|
|
16276
|
-
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
16277
|
-
text: external_exports.string().min(1).max(4e3),
|
|
16278
|
-
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
16279
|
-
* their next call instead of being handed to an agent. Everything else about the
|
|
16280
|
-
* note is identical; this is the one parameter that separates the two. */
|
|
16281
|
-
forMe: external_exports.boolean().optional(),
|
|
16282
|
-
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
16283
|
-
dueAt: external_exports.string().datetime().optional(),
|
|
16284
|
-
repeat: NoteRepeatSchema2.optional()
|
|
16285
|
-
});
|
|
16286
|
-
var RecordDecisionSchema2 = external_exports.object({
|
|
16287
|
-
/** An open decision (from /clarify) to answer. */
|
|
16288
|
-
decisionId: external_exports.string().optional(),
|
|
16289
|
-
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
16290
|
-
question: external_exports.string().min(1).max(500).optional(),
|
|
16291
|
-
/** The ruling. */
|
|
16292
|
-
answer: external_exports.string().min(1).max(2e3)
|
|
16293
|
-
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
16294
|
-
var AssignNoteSchema2 = external_exports.object({
|
|
16295
|
-
/** An EXISTING agent: token id or nickname. Omit when spawning fresh. */
|
|
16296
|
-
target: external_exports.string().min(1).optional(),
|
|
16297
|
-
/** Spawn a NEW session for this note (companion.md §2.2): the assignee doesn't
|
|
16298
|
-
* exist yet — mint it on a live desktop that advertises the harness+workspace,
|
|
16299
|
-
* named after the note. The brief arrives as its opening request. */
|
|
16300
|
-
spawn: external_exports.object({
|
|
16301
|
-
hostTokenId: external_exports.string().uuid(),
|
|
16302
|
-
harness: external_exports.string().min(1),
|
|
16303
|
-
workspace: external_exports.string().min(1)
|
|
16304
|
-
}).optional()
|
|
16305
|
-
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
16306
|
-
var TriageVerdictSchema2 = external_exports.enum(["keep", "stale", "close", "assign"]);
|
|
16307
16197
|
var TriageItemSchema2 = external_exports.object({
|
|
16308
16198
|
noteId: external_exports.string(),
|
|
16309
16199
|
/** The note's headline at run time. */
|
|
@@ -16437,20 +16327,6 @@ var PairingRevealSchema2 = external_exports.object({
|
|
|
16437
16327
|
ed25519: external_exports.string(),
|
|
16438
16328
|
nonce: external_exports.string()
|
|
16439
16329
|
});
|
|
16440
|
-
var DeviceCodeRequestSchema2 = external_exports.object({
|
|
16441
|
-
/** The agent's suggested name for the pairing — the human sees it pre-filled at
|
|
16442
|
-
* approval and can override. Optional; blank → a default silly name server-side. */
|
|
16443
|
-
suggestedName: external_exports.string().optional(),
|
|
16444
|
-
/** Legacy alias for suggestedName (older MCPs sent `agent`). Accepted for
|
|
16445
|
-
* back-compat; `suggestedName` wins when both are present. ponytail: drop once no
|
|
16446
|
-
* pre-`name` MCP is in the wild. */
|
|
16447
|
-
agent: external_exports.union([external_exports.string(), external_exports.object({ name: external_exports.string().optional() })]).optional(),
|
|
16448
|
-
device: external_exports.string().optional(),
|
|
16449
|
-
proto: external_exports.string().optional(),
|
|
16450
|
-
// compiled-in tag, e.g. "paigy-pair-v2|sas=24"
|
|
16451
|
-
commitment: external_exports.string().optional()
|
|
16452
|
-
// agent's Ca
|
|
16453
|
-
});
|
|
16454
16330
|
var DeviceCodeSchema2 = external_exports.object({
|
|
16455
16331
|
device_code: external_exports.string(),
|
|
16456
16332
|
user_code: external_exports.string(),
|
|
@@ -16484,11 +16360,6 @@ var DeviceInfoSchema2 = external_exports.object({
|
|
|
16484
16360
|
phone_reveal: PairingRevealSchema2.nullable().optional(),
|
|
16485
16361
|
uik_pub: external_exports.string().nullable().optional()
|
|
16486
16362
|
});
|
|
16487
|
-
var DeviceTokenRequestSchema2 = external_exports.object({
|
|
16488
|
-
device_code: external_exports.string(),
|
|
16489
|
-
reveal: PairingRevealSchema2.optional()
|
|
16490
|
-
// agent's reveal {x25519, ed25519, nonce}
|
|
16491
|
-
});
|
|
16492
16363
|
var DeviceTokenSchema2 = external_exports.object({
|
|
16493
16364
|
access_token: external_exports.string(),
|
|
16494
16365
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
@@ -16510,14 +16381,6 @@ var DeviceTokenSchema2 = external_exports.object({
|
|
|
16510
16381
|
// present once the phone reveals
|
|
16511
16382
|
uik_pub: external_exports.string().nullable().optional()
|
|
16512
16383
|
});
|
|
16513
|
-
var DeviceCommitRequestSchema2 = external_exports.object({
|
|
16514
|
-
code: external_exports.string(),
|
|
16515
|
-
commitment: external_exports.string()
|
|
16516
|
-
// Cb
|
|
16517
|
-
});
|
|
16518
|
-
var DevicePeerCommitSchema2 = external_exports.object({
|
|
16519
|
-
commitment: external_exports.string().nullable()
|
|
16520
|
-
});
|
|
16521
16384
|
var SupportRequestSchema2 = external_exports.object({
|
|
16522
16385
|
email: external_exports.string().email().max(320),
|
|
16523
16386
|
message: external_exports.string().trim().min(1).max(5e3),
|
|
@@ -16537,15 +16400,6 @@ var NotificationFeedbackKindSchema2 = external_exports.enum([
|
|
|
16537
16400
|
"other"
|
|
16538
16401
|
// anything else — the note carries it.
|
|
16539
16402
|
]);
|
|
16540
|
-
var NotificationFeedbackSchema2 = external_exports.object({
|
|
16541
|
-
notificationId: external_exports.string(),
|
|
16542
|
-
kind: NotificationFeedbackKindSchema2,
|
|
16543
|
-
/** Optional free-text elaboration for a preset; required (non-empty) for 'other'. */
|
|
16544
|
-
note: external_exports.string().trim().max(2e3).optional()
|
|
16545
|
-
}).superRefine((r, ctx) => {
|
|
16546
|
-
if (r.kind === "other" && !r.note)
|
|
16547
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["note"], message: "note is required for 'other' feedback" });
|
|
16548
|
-
});
|
|
16549
16403
|
var FeedbackResolutionSchema2 = external_exports.enum(["broker_fixed", "sent_to_agent"]);
|
|
16550
16404
|
var FeedbackOutcomeSchema2 = external_exports.object({
|
|
16551
16405
|
resolution: FeedbackResolutionSchema2,
|
|
@@ -16554,146 +16408,6 @@ var FeedbackOutcomeSchema2 = external_exports.object({
|
|
|
16554
16408
|
childIds: external_exports.array(external_exports.string()).optional()
|
|
16555
16409
|
});
|
|
16556
16410
|
|
|
16557
|
-
// src/main.ts
|
|
16558
|
-
var import_qrcode = __toESM(require_lib(), 1);
|
|
16559
|
-
import { homedir as homedir7, hostname } from "os";
|
|
16560
|
-
|
|
16561
|
-
// src/harness/catalog.ts
|
|
16562
|
-
import { spawnSync } from "child_process";
|
|
16563
|
-
import { existsSync as existsSync2 } from "fs";
|
|
16564
|
-
import { homedir as homedir2 } from "os";
|
|
16565
|
-
import { delimiter, dirname, join as join2 } from "path";
|
|
16566
|
-
var CATALOG = [
|
|
16567
|
-
{
|
|
16568
|
-
name: "claude",
|
|
16569
|
-
label: "Claude Code",
|
|
16570
|
-
cli: "claude",
|
|
16571
|
-
adapter: "claude-agent-acp",
|
|
16572
|
-
authProbe: ["claude", "auth", "status"],
|
|
16573
|
-
loginHint: "Run `claude` once in a terminal and sign in.",
|
|
16574
|
-
installCli: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
16575
|
-
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
16576
|
-
},
|
|
16577
|
-
{
|
|
16578
|
-
name: "codex",
|
|
16579
|
-
label: "Codex",
|
|
16580
|
-
cli: "codex",
|
|
16581
|
-
adapter: "codex-acp",
|
|
16582
|
-
authProbe: ["codex", "login", "status"],
|
|
16583
|
-
loginHint: "Run `codex login` to authenticate.",
|
|
16584
|
-
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
16585
|
-
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
16586
|
-
},
|
|
16587
|
-
{
|
|
16588
|
-
name: "agy",
|
|
16589
|
-
label: "Antigravity",
|
|
16590
|
-
cli: "agy",
|
|
16591
|
-
authProbe: ["agy", "help"],
|
|
16592
|
-
loginHint: "Install and set up Antigravity (`agy`).",
|
|
16593
|
-
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
16594
|
-
}
|
|
16595
|
-
];
|
|
16596
|
-
function probeDirs(home = homedir2()) {
|
|
16597
|
-
return [
|
|
16598
|
-
dirname(process.execPath),
|
|
16599
|
-
join2(home, ".local", "bin"),
|
|
16600
|
-
"/usr/local/bin",
|
|
16601
|
-
"/opt/homebrew/bin",
|
|
16602
|
-
join2(home, ".npm-global", "bin")
|
|
16603
|
-
];
|
|
16604
|
-
}
|
|
16605
|
-
function which(bin) {
|
|
16606
|
-
const hit = spawnSync("which", [bin], { encoding: "utf8" });
|
|
16607
|
-
const fromPath = hit.status === 0 ? hit.stdout.trim() : "";
|
|
16608
|
-
if (fromPath) return fromPath;
|
|
16609
|
-
for (const dir of probeDirs()) {
|
|
16610
|
-
const candidate = join2(dir, bin);
|
|
16611
|
-
if (existsSync2(candidate)) return candidate;
|
|
16612
|
-
}
|
|
16613
|
-
return null;
|
|
16614
|
-
}
|
|
16615
|
-
function runProbe(argv) {
|
|
16616
|
-
const [cmd, ...args] = argv;
|
|
16617
|
-
if (!cmd) return false;
|
|
16618
|
-
const resolved = which(cmd);
|
|
16619
|
-
if (!resolved) return false;
|
|
16620
|
-
return spawnSync(resolved, args, {
|
|
16621
|
-
timeout: 1e4,
|
|
16622
|
-
stdio: "ignore",
|
|
16623
|
-
env: { ...process.env, PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter) }
|
|
16624
|
-
}).status === 0;
|
|
16625
|
-
}
|
|
16626
|
-
function detect(entry, deps = {}) {
|
|
16627
|
-
const find = deps.which ?? which;
|
|
16628
|
-
const probe = deps.probe ?? runProbe;
|
|
16629
|
-
const base = { name: entry.name, label: entry.label };
|
|
16630
|
-
const cliPath = find(entry.cli);
|
|
16631
|
-
if (!cliPath) return { ...base, status: "missing", hint: `${entry.label} is not installed.`, install: entry.installCli };
|
|
16632
|
-
const adapterPath = entry.adapter ? find(entry.adapter) : null;
|
|
16633
|
-
if (entry.adapter && !adapterPath) {
|
|
16634
|
-
return {
|
|
16635
|
-
...base,
|
|
16636
|
-
status: "adapter-missing",
|
|
16637
|
-
hint: `${entry.label} is installed, but its ACP adapter (${entry.adapter}) is not.`,
|
|
16638
|
-
...entry.installAdapter ? { install: entry.installAdapter } : {}
|
|
16639
|
-
};
|
|
16640
|
-
}
|
|
16641
|
-
const bin = adapterPath ?? cliPath;
|
|
16642
|
-
if (!probe(entry.authProbe)) return { ...base, status: "login", bin, hint: entry.loginHint };
|
|
16643
|
-
return { ...base, status: "ready", bin };
|
|
16644
|
-
}
|
|
16645
|
-
function detectAll(deps = {}) {
|
|
16646
|
-
return CATALOG.map((entry) => detect(entry, deps));
|
|
16647
|
-
}
|
|
16648
|
-
|
|
16649
|
-
// src/host.ts
|
|
16650
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
16651
|
-
import { homedir as homedir5 } from "os";
|
|
16652
|
-
import { join as join4 } from "path";
|
|
16653
|
-
|
|
16654
|
-
// src/workspaces.ts
|
|
16655
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16656
|
-
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
16657
|
-
import { homedir as homedir3 } from "os";
|
|
16658
|
-
function workspacesFile() {
|
|
16659
|
-
return join3(homedir3(), ".paigy", "workspaces.json");
|
|
16660
|
-
}
|
|
16661
|
-
function listWorkspaces(deps) {
|
|
16662
|
-
try {
|
|
16663
|
-
const parsed = JSON.parse(readFileSync2(deps.file, "utf8"));
|
|
16664
|
-
return Array.isArray(parsed) ? parsed.filter((w) => typeof w === "string") : [];
|
|
16665
|
-
} catch {
|
|
16666
|
-
return [];
|
|
16667
|
-
}
|
|
16668
|
-
}
|
|
16669
|
-
function addWorkspace(dir, deps) {
|
|
16670
|
-
const next = resolve(dir.replace(/^~(?=$|\/)/, homedir3()));
|
|
16671
|
-
const all = listWorkspaces(deps);
|
|
16672
|
-
if (!all.includes(next)) {
|
|
16673
|
-
all.push(next);
|
|
16674
|
-
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
16675
|
-
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
16676
|
-
}
|
|
16677
|
-
return all;
|
|
16678
|
-
}
|
|
16679
|
-
function removeWorkspace(dir, deps) {
|
|
16680
|
-
const all = listWorkspaces(deps).filter((w) => w !== dir);
|
|
16681
|
-
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
16682
|
-
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
16683
|
-
return all;
|
|
16684
|
-
}
|
|
16685
|
-
function allowed(cwd, deps) {
|
|
16686
|
-
const target = resolve(cwd.replace(/^~(?=$|\/)/, homedir3()));
|
|
16687
|
-
return listWorkspaces(deps).some((w) => target === w || target.startsWith(`${w}/`));
|
|
16688
|
-
}
|
|
16689
|
-
function resolveWakeDir(pinned, deps) {
|
|
16690
|
-
if (pinned && allowed(pinned, deps)) {
|
|
16691
|
-
const dir = resolve(pinned.replace(/^~(?=$|\/)/, homedir3()));
|
|
16692
|
-
if ((deps.exists ?? existsSync3)(dir)) return dir;
|
|
16693
|
-
}
|
|
16694
|
-
return listWorkspaces(deps)[0];
|
|
16695
|
-
}
|
|
16696
|
-
|
|
16697
16411
|
// src/paigy/activity.ts
|
|
16698
16412
|
function shortenPaths(s) {
|
|
16699
16413
|
return s.replace(/(?<![\w.@+-])(?:\/[\w.@+-]+){3,}/g, (p) => {
|
|
@@ -17274,8 +16988,67 @@ function runHarness(opts) {
|
|
|
17274
16988
|
};
|
|
17275
16989
|
}
|
|
17276
16990
|
|
|
16991
|
+
// src/update.ts
|
|
16992
|
+
import { execFile } from "child_process";
|
|
16993
|
+
import { createRequire } from "module";
|
|
16994
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
16995
|
+
import { promisify } from "util";
|
|
16996
|
+
var SERVICE_LABEL = "ai.paigy.harness";
|
|
16997
|
+
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
16998
|
+
var REGISTRY = "https://registry.npmjs.org/@paigy/harness/latest";
|
|
16999
|
+
var CHECK_MS = 60 * 6e4;
|
|
17000
|
+
function newer(latest, current) {
|
|
17001
|
+
const parts = (v) => /^\d+\.\d+\.\d+$/.test(v) ? v.split(".").map(Number) : null;
|
|
17002
|
+
const [a, b] = [parts(latest), parts(current)];
|
|
17003
|
+
if (!a || !b) return false;
|
|
17004
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
|
|
17005
|
+
return false;
|
|
17006
|
+
}
|
|
17007
|
+
function updateAction(o) {
|
|
17008
|
+
if (!o.service || !o.latest || o.latest === o.failed || !newer(o.latest, o.current)) return "none";
|
|
17009
|
+
return o.sessions > 0 ? "wait" : "install";
|
|
17010
|
+
}
|
|
17011
|
+
async function latestVersion() {
|
|
17012
|
+
const res = await fetch(REGISTRY, { signal: AbortSignal.timeout(1e4) });
|
|
17013
|
+
return res.ok ? (await res.json()).version ?? null : null;
|
|
17014
|
+
}
|
|
17015
|
+
async function install(version) {
|
|
17016
|
+
const bin = dirname3(process.execPath);
|
|
17017
|
+
await promisify(execFile)(join4(bin, "npm"), ["install", "-g", `@paigy/harness@${version}`], {
|
|
17018
|
+
env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}`, npm_config_update_notifier: "false" },
|
|
17019
|
+
timeout: 5 * 6e4
|
|
17020
|
+
});
|
|
17021
|
+
}
|
|
17022
|
+
function selfUpdater(deps) {
|
|
17023
|
+
const service = deps.service ?? process.env.XPC_SERVICE_NAME === SERVICE_LABEL;
|
|
17024
|
+
let latest = null;
|
|
17025
|
+
let checkedAt = -Infinity;
|
|
17026
|
+
let failed;
|
|
17027
|
+
let waiting = false;
|
|
17028
|
+
return async () => {
|
|
17029
|
+
if (!service) return;
|
|
17030
|
+
const now = (deps.now ?? Date.now)();
|
|
17031
|
+
if (now - checkedAt >= CHECK_MS) {
|
|
17032
|
+
checkedAt = now;
|
|
17033
|
+
latest = await (deps.latest ?? latestVersion)().catch(() => latest);
|
|
17034
|
+
}
|
|
17035
|
+
const action = updateAction({ current: VERSION, latest, service, sessions: deps.sessions(), failed });
|
|
17036
|
+
if (action === "wait" && !waiting) deps.log(`\u21E1 @paigy/harness ${latest} is out \u2014 updating once no session is running`);
|
|
17037
|
+
waiting = action === "wait";
|
|
17038
|
+
if (action !== "install" || !latest) return;
|
|
17039
|
+
try {
|
|
17040
|
+
await (deps.install ?? install)(latest);
|
|
17041
|
+
deps.log(`\u21E1 updated @paigy/harness ${VERSION} \u2192 ${latest} \u2014 restarting`);
|
|
17042
|
+
(deps.exit ?? (() => process.exit(0)))();
|
|
17043
|
+
} catch (e) {
|
|
17044
|
+
failed = latest;
|
|
17045
|
+
deps.log(`\u26A0 could not update to @paigy/harness ${latest}: ${e.message.split("\n")[0]} \u2014 run: npm install -g @paigy/harness`);
|
|
17046
|
+
}
|
|
17047
|
+
};
|
|
17048
|
+
}
|
|
17049
|
+
|
|
17277
17050
|
// src/host.ts
|
|
17278
|
-
var HOST_FILE =
|
|
17051
|
+
var HOST_FILE = join5(homedir5(), ".paigy", "host.json");
|
|
17279
17052
|
function readHostState() {
|
|
17280
17053
|
try {
|
|
17281
17054
|
const s = JSON.parse(readFileSync3(HOST_FILE, "utf8"));
|
|
@@ -17323,11 +17096,15 @@ function startHost(opts) {
|
|
|
17323
17096
|
}
|
|
17324
17097
|
}
|
|
17325
17098
|
};
|
|
17099
|
+
const update = selfUpdater({ sessions: () => runs.size, log: opts.log });
|
|
17326
17100
|
const beat = () => {
|
|
17327
17101
|
void refreshIdentities();
|
|
17102
|
+
void update().catch(() => {
|
|
17103
|
+
});
|
|
17328
17104
|
for (const r of runs.values()) if (r.token) void heartbeat(void 0, { token: r.token }).catch(() => {
|
|
17329
17105
|
});
|
|
17330
17106
|
void heartbeat({
|
|
17107
|
+
version: VERSION,
|
|
17331
17108
|
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
17332
17109
|
workspaces: listWorkspaces(opts.wsDeps)
|
|
17333
17110
|
}, asHost).catch(() => {
|
|
@@ -18112,7 +17889,7 @@ async function brokerView(deps) {
|
|
|
18112
17889
|
}
|
|
18113
17890
|
|
|
18114
17891
|
// src/main.ts
|
|
18115
|
-
var here =
|
|
17892
|
+
var here = dirname4(fileURLToPath(import.meta.url));
|
|
18116
17893
|
var wsDeps = { file: workspacesFile() };
|
|
18117
17894
|
var DEVICE_SLOT = "Desktop";
|
|
18118
17895
|
var deviceToken = () => readToken(DEVICE_SLOT);
|
|
@@ -18120,7 +17897,7 @@ var win = null;
|
|
|
18120
17897
|
function log(message) {
|
|
18121
17898
|
win?.webContents.send("paigy:log", message);
|
|
18122
17899
|
}
|
|
18123
|
-
var ICON =
|
|
17900
|
+
var ICON = join6(here, "..", "assets", "icon.png");
|
|
18124
17901
|
function createWindow() {
|
|
18125
17902
|
win = new BrowserWindow({
|
|
18126
17903
|
// Sized for the working log, which is now the window's centerpiece; the layout is a
|
|
@@ -18132,9 +17909,9 @@ function createWindow() {
|
|
|
18132
17909
|
minHeight: 520,
|
|
18133
17910
|
icon: ICON,
|
|
18134
17911
|
// Windows/Linux window icon; macOS uses the dock icon below
|
|
18135
|
-
webPreferences: { preload:
|
|
17912
|
+
webPreferences: { preload: join6(here, "preload.cjs"), contextIsolation: true, nodeIntegration: false }
|
|
18136
17913
|
});
|
|
18137
|
-
void win.loadFile(
|
|
17914
|
+
void win.loadFile(join6(here, "..", "renderer", "index.html"));
|
|
18138
17915
|
}
|
|
18139
17916
|
var host = null;
|
|
18140
17917
|
app.whenReady().then(() => {
|
|
@@ -18187,13 +17964,6 @@ ipcMain.handle("paigy:beacon", async () => {
|
|
|
18187
17964
|
})();
|
|
18188
17965
|
return { qr, code: code.user_code };
|
|
18189
17966
|
});
|
|
18190
|
-
function waitingLine(d) {
|
|
18191
|
-
for (let i = d.entries.length - 1; i >= 0; i -= 1) {
|
|
18192
|
-
const words2 = entryWords(d.entries[i]).trim();
|
|
18193
|
-
if (words2) return words2.split("\n")[0];
|
|
18194
|
-
}
|
|
18195
|
-
return d.message.split("\n")[0] ?? "";
|
|
18196
|
-
}
|
|
18197
17967
|
ipcMain.handle("paigy:agent", async (_e, name) => {
|
|
18198
17968
|
const entry = (host?.roster() ?? readHostState()?.roster ?? []).find((a) => a.name === name);
|
|
18199
17969
|
if (!entry) return null;
|
|
@@ -18203,17 +17973,14 @@ ipcMain.handle("paigy:agent", async (_e, name) => {
|
|
|
18203
17973
|
...entry,
|
|
18204
17974
|
pending: open ? {
|
|
18205
17975
|
// The same two facts, from the one list: what is waiting, and how much.
|
|
18206
|
-
requests: open.slice(0, 5).map((d) => ({
|
|
18207
|
-
line: waitingLine(d).slice(0, 140),
|
|
18208
|
-
at: d.entries[d.entries.length - 1]?.createdAt ?? ""
|
|
18209
|
-
})),
|
|
17976
|
+
requests: open.slice(0, 5).map((d) => ({ line: (d.about || d.message.split("\n")[0] || "").slice(0, 140) })),
|
|
18210
17977
|
replies: open.length
|
|
18211
17978
|
} : null
|
|
18212
17979
|
};
|
|
18213
17980
|
});
|
|
18214
17981
|
ipcMain.handle("paigy:agent-log", (_e, name) => {
|
|
18215
17982
|
try {
|
|
18216
|
-
const all = readFileSync4(
|
|
17983
|
+
const all = readFileSync4(join6(homedir7(), ".paigy", "host.log"), "utf8").split("\n");
|
|
18217
17984
|
const mine = all.filter((l) => l.includes(`[${name}]`) || /[▶✖◉⚠]/.test(l) && l.includes(name));
|
|
18218
17985
|
return mine.slice(-500).join("\n");
|
|
18219
17986
|
} catch {
|