@paigy/harness 0.3.0 → 0.3.2
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 +454 -86
- package/dist/main.js +833 -507
- package/package.json +13 -12
package/dist/cli.js
CHANGED
|
@@ -23175,6 +23175,79 @@ function detectAll(deps = {}) {
|
|
|
23175
23175
|
return CATALOG.map((entry) => detect(entry, deps));
|
|
23176
23176
|
}
|
|
23177
23177
|
|
|
23178
|
+
// src/cli-args.ts
|
|
23179
|
+
var USAGE = "usage: paigy-harness setup | pair | host [--grant DIR]\u2026 | service | enable-tools [--scope user|project] | hatch NAME [--voice KEY] [--slot SLOT] | handoff | [--harness claude|codex|agy] [--mode bypass|ask] [--cwd DIR] [--identity NAME] [--grace SECONDS] [--doctor] PROMPT\u2026";
|
|
23180
|
+
function parseArgs(argv) {
|
|
23181
|
+
const args = { harness: "claude", mode: "bypass", cwd: process.cwd(), doctor: false, help: false, host: false, pair: false, service: false, setup: false, enableTools: false, scope: "user", grant: [], hatch: null, voice: null, slot: null, identity: null, graceSeconds: 90, prompt: "", handoff: false };
|
|
23182
|
+
const words = [];
|
|
23183
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23184
|
+
const arg = argv[i];
|
|
23185
|
+
const next = () => argv[++i];
|
|
23186
|
+
if (arg === "--harness") {
|
|
23187
|
+
const v = next();
|
|
23188
|
+
if (v !== "claude" && v !== "codex" && v !== "agy") return { error: `--harness must be claude, codex, or agy, got ${v ?? "nothing"}` };
|
|
23189
|
+
args.harness = v;
|
|
23190
|
+
} else if (arg === "--mode") {
|
|
23191
|
+
const v = next();
|
|
23192
|
+
if (v !== "bypass" && v !== "ask") return { error: `--mode must be bypass or ask, got ${v ?? "nothing"}` };
|
|
23193
|
+
args.mode = v;
|
|
23194
|
+
} else if (arg === "--cwd") {
|
|
23195
|
+
const v = next();
|
|
23196
|
+
if (!v) return { error: "--cwd needs a directory" };
|
|
23197
|
+
args.cwd = v;
|
|
23198
|
+
} else if (arg === "--doctor") {
|
|
23199
|
+
args.doctor = true;
|
|
23200
|
+
} else if (arg === "-h" || arg === "--help" || arg === "help") {
|
|
23201
|
+
args.help = true;
|
|
23202
|
+
} else if (arg === "--scope") {
|
|
23203
|
+
const v = next();
|
|
23204
|
+
if (v !== "user" && v !== "project") return { error: `--scope must be user or project, got ${v ?? "nothing"}` };
|
|
23205
|
+
args.scope = v;
|
|
23206
|
+
} else if (arg === "--grace") {
|
|
23207
|
+
const v = Number(next());
|
|
23208
|
+
if (!Number.isFinite(v) || v < 0) return { error: "--grace needs seconds (0 = straight to phone)" };
|
|
23209
|
+
args.graceSeconds = v;
|
|
23210
|
+
} else if (arg === "host") {
|
|
23211
|
+
args.host = true;
|
|
23212
|
+
} else if (arg === "pair") {
|
|
23213
|
+
args.pair = true;
|
|
23214
|
+
} else if (arg === "setup") {
|
|
23215
|
+
args.setup = true;
|
|
23216
|
+
} else if (arg === "service") {
|
|
23217
|
+
args.service = true;
|
|
23218
|
+
} else if (arg === "--grant") {
|
|
23219
|
+
const v = next();
|
|
23220
|
+
if (!v) return { error: "--grant needs a folder to allow" };
|
|
23221
|
+
args.grant.push(v);
|
|
23222
|
+
} else if (arg === "handoff") {
|
|
23223
|
+
args.handoff = true;
|
|
23224
|
+
} else if (arg === "enable-tools") {
|
|
23225
|
+
args.enableTools = true;
|
|
23226
|
+
} else if (arg === "hatch") {
|
|
23227
|
+
const v = next();
|
|
23228
|
+
if (!v) return { error: 'hatch needs a name: paigy-harness hatch "Voice bug hunter"' };
|
|
23229
|
+
args.hatch = v;
|
|
23230
|
+
} else if (arg === "--voice") {
|
|
23231
|
+
args.voice = next() ?? null;
|
|
23232
|
+
} else if (arg === "--slot") {
|
|
23233
|
+
args.slot = next() ?? null;
|
|
23234
|
+
} else if (arg === "--identity") {
|
|
23235
|
+
const v = next();
|
|
23236
|
+
if (!v) return { error: "--identity needs a hatched agent's name" };
|
|
23237
|
+
args.identity = v;
|
|
23238
|
+
} else if (arg?.startsWith("--")) {
|
|
23239
|
+
return { error: `unknown flag ${arg}` };
|
|
23240
|
+
} else if (arg) {
|
|
23241
|
+
words.push(arg);
|
|
23242
|
+
}
|
|
23243
|
+
}
|
|
23244
|
+
args.prompt = words.join(" ");
|
|
23245
|
+
if (args.help) return args;
|
|
23246
|
+
if (!args.doctor && !args.handoff && !args.hatch && !args.host && !args.pair && !args.service && !args.setup && !args.enableTools && !args.prompt) return { error: "a prompt is required (or setup / --doctor / pair / host / service / enable-tools / hatch NAME / handoff)" };
|
|
23247
|
+
return args;
|
|
23248
|
+
}
|
|
23249
|
+
var ENABLE_TOOLS_COMMAND = "npx -y -p @paigy/mcp@latest paigy-enable-tools";
|
|
23250
|
+
|
|
23178
23251
|
// ../../packages/sdk/dist/index.js
|
|
23179
23252
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
23180
23253
|
import { randomUUID } from "crypto";
|
|
@@ -29553,6 +29626,12 @@ var ReceiptEventSchema = external_exports.enum([
|
|
|
29553
29626
|
// the recipient opened it
|
|
29554
29627
|
"answered",
|
|
29555
29628
|
// the recipient replied
|
|
29629
|
+
// The recipient TURNED THE RING DOWN — CallKit ended it and no answer was ever tapped.
|
|
29630
|
+
// Written by the phone, on the same door that reports the ring itself, so it exists only
|
|
29631
|
+
// when a ring reached a running app and a person did not take it. That is what separates
|
|
29632
|
+
// it from "a ring with no answer", which our own crashes wrote just as readily and which
|
|
29633
|
+
// is why the responsiveness back-off had to be removed (#1144).
|
|
29634
|
+
"declined",
|
|
29556
29635
|
"escalated",
|
|
29557
29636
|
// re-reached at a higher level (re-ring / promote)
|
|
29558
29637
|
"coalesced",
|
|
@@ -29561,8 +29640,14 @@ var ReceiptEventSchema = external_exports.enum([
|
|
|
29561
29640
|
// deadline passed unanswered
|
|
29562
29641
|
"woke",
|
|
29563
29642
|
// the agent was woken for an owed obligation (callback)
|
|
29564
|
-
"gave_up"
|
|
29643
|
+
"gave_up",
|
|
29565
29644
|
// the budget was spent — stopped re-engaging
|
|
29645
|
+
// The ladder starts over — a silent pickup (the owner's fresh-miss rule, 2026-07-28), a
|
|
29646
|
+
// promote to call, a re-delivery. APPENDED, never a rewind: `ring_step` was a cache of
|
|
29647
|
+
// the escalated-count and a writer rewound it to 0 on every unanswered call (2026-08-24,
|
|
29648
|
+
// nine rings in an hour, `gaveUp` unreachable). A count since the last `restarted` cannot
|
|
29649
|
+
// be rewound by a writer that forgot to advance it.
|
|
29650
|
+
"restarted"
|
|
29566
29651
|
]);
|
|
29567
29652
|
var AttentionSchema = external_exports.object({
|
|
29568
29653
|
urgency: NotifyLevelSchema,
|
|
@@ -29694,7 +29779,7 @@ var NotifyRequestSchema = external_exports.object({
|
|
|
29694
29779
|
if (r.ask !== void 0) {
|
|
29695
29780
|
for (const f of ["context", "select", "points"]) {
|
|
29696
29781
|
if (r[f] !== void 0)
|
|
29697
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives it
|
|
29782
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
|
|
29698
29783
|
}
|
|
29699
29784
|
return;
|
|
29700
29785
|
}
|
|
@@ -29807,6 +29892,14 @@ var IntentSchema = external_exports.object({
|
|
|
29807
29892
|
* about what cannot answer "is the bot looping less this week?". */
|
|
29808
29893
|
fault: external_exports.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
29809
29894
|
});
|
|
29895
|
+
var RideAlongSchema = external_exports.object({
|
|
29896
|
+
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
29897
|
+
noteId: external_exports.string(),
|
|
29898
|
+
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
29899
|
+
text: external_exports.string(),
|
|
29900
|
+
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
29901
|
+
parentId: external_exports.string().nullable()
|
|
29902
|
+
});
|
|
29810
29903
|
var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
29811
29904
|
external_exports.object({
|
|
29812
29905
|
type: external_exports.literal("reply"),
|
|
@@ -29827,7 +29920,13 @@ var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
|
29827
29920
|
transcript: external_exports.string().optional(),
|
|
29828
29921
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
29829
29922
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
29830
|
-
covered: external_exports.array(external_exports.string()).optional()
|
|
29923
|
+
covered: external_exports.array(external_exports.string()).optional(),
|
|
29924
|
+
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
29925
|
+
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
29926
|
+
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
29927
|
+
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
29928
|
+
* dropped. Absent/empty = nothing owed. */
|
|
29929
|
+
also: external_exports.array(RideAlongSchema).optional()
|
|
29831
29930
|
}),
|
|
29832
29931
|
external_exports.object({
|
|
29833
29932
|
type: external_exports.literal("remind"),
|
|
@@ -29862,7 +29961,7 @@ var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
|
29862
29961
|
acts: external_exports.array(IntentSchema).nullable().optional()
|
|
29863
29962
|
})
|
|
29864
29963
|
}),
|
|
29865
|
-
external_exports.object({ type: external_exports.literal("idle") })
|
|
29964
|
+
external_exports.object({ type: external_exports.literal("idle"), also: external_exports.array(RideAlongSchema).optional() })
|
|
29866
29965
|
]);
|
|
29867
29966
|
var CallbackTriggerSchema = external_exports.enum(["on_done", "on_blocked", "scheduled"]);
|
|
29868
29967
|
var ScheduleCallbackSchema = external_exports.object({
|
|
@@ -29893,6 +29992,12 @@ var PendingRepliesSchema = external_exports.object({
|
|
|
29893
29992
|
pending: external_exports.array(
|
|
29894
29993
|
external_exports.object({ parentId: external_exports.string(), notificationId: external_exports.string(), createdAt: external_exports.string() })
|
|
29895
29994
|
),
|
|
29995
|
+
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
29996
|
+
* sees for this session's identity. From inside a session there was no way to find out —
|
|
29997
|
+
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
29998
|
+
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
29999
|
+
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
30000
|
+
you: external_exports.object({ name: external_exports.string(), device: external_exports.string().nullable(), tokenId: external_exports.string() }).optional(),
|
|
29896
30001
|
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
29897
30002
|
* contact on the same parentId. Keeps reappearing until you call
|
|
29898
30003
|
* set_task_state on its notificationId. */
|
|
@@ -29904,12 +30009,21 @@ var PendingRepliesSchema = external_exports.object({
|
|
|
29904
30009
|
createdAt: external_exports.string(),
|
|
29905
30010
|
/** The user seeded this request with a past conversation — call get_thread on it
|
|
29906
30011
|
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
29907
|
-
contextParentId: external_exports.string().optional()
|
|
30012
|
+
contextParentId: external_exports.string().optional(),
|
|
30013
|
+
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
30014
|
+
* the account — the name here — which has not been seen since it landed, so nobody
|
|
30015
|
+
* came for it. Handed to you because you are the session that is here. Take it like
|
|
30016
|
+
* any request (set_task_state claims it, reply with contact on its parentId), and say
|
|
30017
|
+
* whose it was, because the user chose that agent on purpose. */
|
|
30018
|
+
stranded: external_exports.string().optional()
|
|
29908
30019
|
})
|
|
29909
30020
|
),
|
|
29910
30021
|
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
29911
30022
|
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
29912
30023
|
* fulfill one by calling contact on its parentId. */
|
|
30024
|
+
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
30025
|
+
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
30026
|
+
also: external_exports.array(RideAlongSchema).optional(),
|
|
29913
30027
|
owedCallbacks: external_exports.array(
|
|
29914
30028
|
external_exports.object({ parentId: external_exports.string(), trigger: CallbackTriggerSchema, note: external_exports.string() })
|
|
29915
30029
|
),
|
|
@@ -29945,7 +30059,11 @@ var NotifyResponseSchema = external_exports.object({
|
|
|
29945
30059
|
status: NotifyStatusSchema,
|
|
29946
30060
|
createdAt: external_exports.string().datetime(),
|
|
29947
30061
|
answer: UserAnswerSchema.optional(),
|
|
29948
|
-
answeredAt: external_exports.string().datetime().optional()
|
|
30062
|
+
answeredAt: external_exports.string().datetime().optional(),
|
|
30063
|
+
/** Ride-alongs for THIS agent — pending work it should pick up when it's done with
|
|
30064
|
+
* what it came for. Present on any reply, because an unwakeable agent's only
|
|
30065
|
+
* reliable moment is one it initiated. Absent/empty = nothing owed. */
|
|
30066
|
+
also: external_exports.array(RideAlongSchema).optional()
|
|
29949
30067
|
});
|
|
29950
30068
|
var NotifyPlanUnitSchema = external_exports.object({
|
|
29951
30069
|
notificationId: external_exports.string(),
|
|
@@ -29967,7 +30085,13 @@ var NotifyPlanUnitSchema = external_exports.object({
|
|
|
29967
30085
|
proposal: external_exports.object({
|
|
29968
30086
|
select: SelectShapeSchema,
|
|
29969
30087
|
options: external_exports.array(external_exports.object({ label: external_exports.string().min(1) })).optional()
|
|
29970
|
-
}).optional()
|
|
30088
|
+
}).optional(),
|
|
30089
|
+
/** What the broker READ this unit as wanting from the human (#952 layer 2): a `decision`
|
|
30090
|
+
* between alternatives, an `approval` the agent is blocked on, or `knowledge` it just
|
|
30091
|
+
* needs to know. Reported so the agent can correct a misread the same way it ratifies a
|
|
30092
|
+
* shape — the read RAISES (a decision always asks) and never silences a question the
|
|
30093
|
+
* agent declared (#731, #923). */
|
|
30094
|
+
wants: external_exports.enum(["decision", "approval", "knowledge"]).optional()
|
|
29971
30095
|
});
|
|
29972
30096
|
var NotifyPlanSchema = external_exports.object({
|
|
29973
30097
|
units: external_exports.array(NotifyPlanUnitSchema),
|
|
@@ -29991,6 +30115,9 @@ var UserResponseSchema = external_exports.object({
|
|
|
29991
30115
|
});
|
|
29992
30116
|
var VoiceKeySchema = external_exports.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
29993
30117
|
var AgendaTurnSchema = external_exports.object({
|
|
30118
|
+
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
30119
|
+
* the planner declares duplicates instead of asking them twice. */
|
|
30120
|
+
coveredIds: external_exports.array(external_exports.string()).optional(),
|
|
29994
30121
|
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
29995
30122
|
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
29996
30123
|
info: external_exports.array(external_exports.string().min(1)).max(3).default([]),
|
|
@@ -30024,6 +30151,7 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
30024
30151
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
30025
30152
|
blocking: external_exports.boolean().optional()
|
|
30026
30153
|
});
|
|
30154
|
+
var CLAIM_STALE_MS = 30 * 6e4;
|
|
30027
30155
|
var InboxItemSchema = external_exports.object({
|
|
30028
30156
|
id: external_exports.string(),
|
|
30029
30157
|
/** The conversation thread + connection this item lives on. Present on the replied
|
|
@@ -30043,6 +30171,15 @@ var InboxItemSchema = external_exports.object({
|
|
|
30043
30171
|
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
30044
30172
|
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
30045
30173
|
seq: external_exports.number().int().optional(),
|
|
30174
|
+
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
30175
|
+
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
30176
|
+
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
30177
|
+
* screen read "1 of 3", then "1 of 2", then no chip at all, each answer having removed the
|
|
30178
|
+
* only evidence of itself. How big an arrival is, is a fact about the arrival, so the
|
|
30179
|
+
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
30180
|
+
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
30181
|
+
* the one part of its own arrival a single row cannot answer. */
|
|
30182
|
+
units: external_exports.number().int().positive().optional(),
|
|
30046
30183
|
tokenId: external_exports.string().optional(),
|
|
30047
30184
|
status: NotifyStatusSchema,
|
|
30048
30185
|
context: ContextSchema,
|
|
@@ -30063,6 +30200,16 @@ var InboxItemSchema = external_exports.object({
|
|
|
30063
30200
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
30064
30201
|
* which must never CLAIM stalled. */
|
|
30065
30202
|
lastSeenAt: external_exports.string().optional(),
|
|
30203
|
+
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
30204
|
+
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
30205
|
+
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
30206
|
+
* than `createdAt`: a claim is very often picked up long after the row was born — the
|
|
30207
|
+
* inbox keeps an ANSWERED row visible while the agent works the follow-up, so a question
|
|
30208
|
+
* asked this morning and claimed a minute ago is eight hours old and one minute into its
|
|
30209
|
+
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
30210
|
+
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
30211
|
+
* `createdAt`. */
|
|
30212
|
+
agentStateAt: external_exports.string().datetime().optional(),
|
|
30066
30213
|
agenda: external_exports.array(AgendaTurnSchema).optional(),
|
|
30067
30214
|
/** On a replied detail (#397): the next steps the user attached to the answer
|
|
30068
30215
|
* ("call back after lunch") — shown so they can see the commitment was captured. */
|
|
@@ -30134,11 +30281,23 @@ var SnoozeRequestSchema = external_exports.object({
|
|
|
30134
30281
|
requestId: external_exports.string(),
|
|
30135
30282
|
until: external_exports.string().datetime()
|
|
30136
30283
|
});
|
|
30284
|
+
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
30137
30285
|
var PushTokenSchema = external_exports.object({
|
|
30138
30286
|
voipToken: external_exports.string().min(1).optional(),
|
|
30139
30287
|
alertToken: external_exports.string().min(1).optional(),
|
|
30140
30288
|
fcmToken: external_exports.string().min(1).optional(),
|
|
30141
30289
|
platform: external_exports.enum(["ios", "android"])
|
|
30290
|
+
}).superRefine((v, ctx) => {
|
|
30291
|
+
if (v.platform !== "ios") return;
|
|
30292
|
+
for (const field of ["voipToken", "alertToken"]) {
|
|
30293
|
+
const token = v[field];
|
|
30294
|
+
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
30295
|
+
ctx.addIssue({
|
|
30296
|
+
code: external_exports.ZodIssueCode.custom,
|
|
30297
|
+
path: [field],
|
|
30298
|
+
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
30299
|
+
});
|
|
30300
|
+
}
|
|
30142
30301
|
});
|
|
30143
30302
|
var MissedCallSchema = external_exports.enum([
|
|
30144
30303
|
"retry_10m",
|
|
@@ -30189,6 +30348,15 @@ var UserSettingsSchema = external_exports.object({
|
|
|
30189
30348
|
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
30190
30349
|
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
30191
30350
|
pstnCalls: external_exports.boolean().optional(),
|
|
30351
|
+
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
30352
|
+
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
30353
|
+
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
30354
|
+
* the same reason `voiceMode` is (a stale client PATCHing the whole object must not
|
|
30355
|
+
* clobber it) and one more: a GUESSED timezone schedules reminders hours off, and
|
|
30356
|
+
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
30357
|
+
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
30358
|
+
* call — honest about what we know. */
|
|
30359
|
+
timezone: external_exports.string().min(1).max(64).optional(),
|
|
30192
30360
|
/** Account E2EE state (text lane): 'off' (default) = today's plaintext; 'on' =
|
|
30193
30361
|
* content is sealed end-to-end between the local agent and the phone. Like
|
|
30194
30362
|
* voiceMode, OPTIONAL and NOT defaulted so a stale client PATCHing the full
|
|
@@ -30214,6 +30382,15 @@ var HistoryItemSchema = external_exports.object({
|
|
|
30214
30382
|
/** When you answered the agent's notification (agent→user only). */
|
|
30215
30383
|
humanAckedAt: external_exports.string().nullable()
|
|
30216
30384
|
});
|
|
30385
|
+
var ACTIVITY_LINES = 2;
|
|
30386
|
+
var ACTIVITY_LINE_MAX = 80;
|
|
30387
|
+
var AgentActivitySchema = external_exports.object({
|
|
30388
|
+
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
30389
|
+
lines: external_exports.array(external_exports.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
30390
|
+
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
30391
|
+
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
30392
|
+
at: external_exports.string().datetime()
|
|
30393
|
+
});
|
|
30217
30394
|
var ConnectionSummarySchema = external_exports.object({
|
|
30218
30395
|
/** The connection = the agent's token id (used to address a request). */
|
|
30219
30396
|
id: external_exports.string(),
|
|
@@ -30247,6 +30424,11 @@ var ConnectionSummarySchema = external_exports.object({
|
|
|
30247
30424
|
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
30248
30425
|
workspaces: external_exports.array(external_exports.string()).optional()
|
|
30249
30426
|
}).optional(),
|
|
30427
|
+
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
30428
|
+
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
30429
|
+
* used straight from a terminal emits no work events; the page says so rather than
|
|
30430
|
+
* drawing an empty box). */
|
|
30431
|
+
activity: AgentActivitySchema.optional(),
|
|
30250
30432
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
30251
30433
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
30252
30434
|
managed: external_exports.boolean()
|
|
@@ -30315,7 +30497,8 @@ var HandoffSchema = external_exports.object({
|
|
|
30315
30497
|
recap: external_exports.boolean().optional()
|
|
30316
30498
|
});
|
|
30317
30499
|
var NoteSourceSchema = external_exports.enum(["app", "call"]);
|
|
30318
|
-
var NoteStatusSchema = external_exports.enum(["open", "assigned", "done"]);
|
|
30500
|
+
var NoteStatusSchema = external_exports.enum(["open", "assigned", "in_progress", "done"]);
|
|
30501
|
+
var NoteRepeatSchema = external_exports.enum(["once", "until_done"]);
|
|
30319
30502
|
var DecisionSchema = external_exports.object({
|
|
30320
30503
|
id: external_exports.string(),
|
|
30321
30504
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
@@ -30340,11 +30523,29 @@ var NoteSchema = external_exports.object({
|
|
|
30340
30523
|
assignee: external_exports.string().nullable(),
|
|
30341
30524
|
/** The request thread minted at assignment; null until assigned. */
|
|
30342
30525
|
parentId: external_exports.string().nullable(),
|
|
30526
|
+
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
30527
|
+
* call — never a deadline, and nothing rings when it passes. Null = "the very next
|
|
30528
|
+
* call", the right reading of "remind me to…" with no time attached. */
|
|
30529
|
+
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
30530
|
+
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
30531
|
+
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
30532
|
+
dueAt: external_exports.string().nullable().default(null),
|
|
30533
|
+
repeat: NoteRepeatSchema.default("once"),
|
|
30534
|
+
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
30535
|
+
rides: external_exports.number().int().default(0),
|
|
30536
|
+
lastRideAt: external_exports.string().nullable().default(null),
|
|
30343
30537
|
createdAt: external_exports.string()
|
|
30344
30538
|
});
|
|
30345
30539
|
var CreateNoteSchema = external_exports.object({
|
|
30346
30540
|
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
30347
|
-
text: external_exports.string().min(1).max(4e3)
|
|
30541
|
+
text: external_exports.string().min(1).max(4e3),
|
|
30542
|
+
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
30543
|
+
* their next call instead of being handed to an agent. Everything else about the
|
|
30544
|
+
* note is identical; this is the one parameter that separates the two. */
|
|
30545
|
+
forMe: external_exports.boolean().optional(),
|
|
30546
|
+
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
30547
|
+
dueAt: external_exports.string().datetime().optional(),
|
|
30548
|
+
repeat: NoteRepeatSchema.optional()
|
|
30348
30549
|
});
|
|
30349
30550
|
var RecordDecisionSchema = external_exports.object({
|
|
30350
30551
|
/** An open decision (from /clarify) to answer. */
|
|
@@ -31081,10 +31282,14 @@ async function claimSessions(opts = {}) {
|
|
|
31081
31282
|
return (await res.json()).sessions;
|
|
31082
31283
|
}
|
|
31083
31284
|
async function heartbeat(runtime, opts = {}) {
|
|
31285
|
+
const body = {
|
|
31286
|
+
...runtime !== void 0 ? { runtime } : {},
|
|
31287
|
+
...opts.activity !== void 0 ? { activity: opts.activity } : {}
|
|
31288
|
+
};
|
|
31084
31289
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/presence`, {
|
|
31085
31290
|
method: "POST",
|
|
31086
31291
|
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
31087
|
-
...
|
|
31292
|
+
...Object.keys(body).length > 0 ? { body: JSON.stringify(body) } : {}
|
|
31088
31293
|
}));
|
|
31089
31294
|
if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
|
|
31090
31295
|
}
|
|
@@ -31158,6 +31363,12 @@ var ReceiptEventSchema2 = external_exports.enum([
|
|
|
31158
31363
|
// the recipient opened it
|
|
31159
31364
|
"answered",
|
|
31160
31365
|
// the recipient replied
|
|
31366
|
+
// The recipient TURNED THE RING DOWN — CallKit ended it and no answer was ever tapped.
|
|
31367
|
+
// Written by the phone, on the same door that reports the ring itself, so it exists only
|
|
31368
|
+
// when a ring reached a running app and a person did not take it. That is what separates
|
|
31369
|
+
// it from "a ring with no answer", which our own crashes wrote just as readily and which
|
|
31370
|
+
// is why the responsiveness back-off had to be removed (#1144).
|
|
31371
|
+
"declined",
|
|
31161
31372
|
"escalated",
|
|
31162
31373
|
// re-reached at a higher level (re-ring / promote)
|
|
31163
31374
|
"coalesced",
|
|
@@ -31166,8 +31377,14 @@ var ReceiptEventSchema2 = external_exports.enum([
|
|
|
31166
31377
|
// deadline passed unanswered
|
|
31167
31378
|
"woke",
|
|
31168
31379
|
// the agent was woken for an owed obligation (callback)
|
|
31169
|
-
"gave_up"
|
|
31380
|
+
"gave_up",
|
|
31170
31381
|
// the budget was spent — stopped re-engaging
|
|
31382
|
+
// The ladder starts over — a silent pickup (the owner's fresh-miss rule, 2026-07-28), a
|
|
31383
|
+
// promote to call, a re-delivery. APPENDED, never a rewind: `ring_step` was a cache of
|
|
31384
|
+
// the escalated-count and a writer rewound it to 0 on every unanswered call (2026-08-24,
|
|
31385
|
+
// nine rings in an hour, `gaveUp` unreachable). A count since the last `restarted` cannot
|
|
31386
|
+
// be rewound by a writer that forgot to advance it.
|
|
31387
|
+
"restarted"
|
|
31171
31388
|
]);
|
|
31172
31389
|
var AttentionSchema2 = external_exports.object({
|
|
31173
31390
|
urgency: NotifyLevelSchema2,
|
|
@@ -31299,7 +31516,7 @@ var NotifyRequestSchema2 = external_exports.object({
|
|
|
31299
31516
|
if (r.ask !== void 0) {
|
|
31300
31517
|
for (const f of ["context", "select", "points"]) {
|
|
31301
31518
|
if (r[f] !== void 0)
|
|
31302
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives it
|
|
31519
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
|
|
31303
31520
|
}
|
|
31304
31521
|
return;
|
|
31305
31522
|
}
|
|
@@ -31374,6 +31591,14 @@ var IntentSchema2 = external_exports.object({
|
|
|
31374
31591
|
* about what cannot answer "is the bot looping less this week?". */
|
|
31375
31592
|
fault: external_exports.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
31376
31593
|
});
|
|
31594
|
+
var RideAlongSchema2 = external_exports.object({
|
|
31595
|
+
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
31596
|
+
noteId: external_exports.string(),
|
|
31597
|
+
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
31598
|
+
text: external_exports.string(),
|
|
31599
|
+
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
31600
|
+
parentId: external_exports.string().nullable()
|
|
31601
|
+
});
|
|
31377
31602
|
var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
31378
31603
|
external_exports.object({
|
|
31379
31604
|
type: external_exports.literal("reply"),
|
|
@@ -31394,7 +31619,13 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
31394
31619
|
transcript: external_exports.string().optional(),
|
|
31395
31620
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
31396
31621
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
31397
|
-
covered: external_exports.array(external_exports.string()).optional()
|
|
31622
|
+
covered: external_exports.array(external_exports.string()).optional(),
|
|
31623
|
+
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
31624
|
+
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
31625
|
+
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
31626
|
+
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
31627
|
+
* dropped. Absent/empty = nothing owed. */
|
|
31628
|
+
also: external_exports.array(RideAlongSchema2).optional()
|
|
31398
31629
|
}),
|
|
31399
31630
|
external_exports.object({
|
|
31400
31631
|
type: external_exports.literal("remind"),
|
|
@@ -31429,7 +31660,7 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
31429
31660
|
acts: external_exports.array(IntentSchema2).nullable().optional()
|
|
31430
31661
|
})
|
|
31431
31662
|
}),
|
|
31432
|
-
external_exports.object({ type: external_exports.literal("idle") })
|
|
31663
|
+
external_exports.object({ type: external_exports.literal("idle"), also: external_exports.array(RideAlongSchema2).optional() })
|
|
31433
31664
|
]);
|
|
31434
31665
|
var CallbackTriggerSchema2 = external_exports.enum(["on_done", "on_blocked", "scheduled"]);
|
|
31435
31666
|
var ScheduleCallbackSchema2 = external_exports.object({
|
|
@@ -31460,6 +31691,12 @@ var PendingRepliesSchema2 = external_exports.object({
|
|
|
31460
31691
|
pending: external_exports.array(
|
|
31461
31692
|
external_exports.object({ parentId: external_exports.string(), notificationId: external_exports.string(), createdAt: external_exports.string() })
|
|
31462
31693
|
),
|
|
31694
|
+
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
31695
|
+
* sees for this session's identity. From inside a session there was no way to find out —
|
|
31696
|
+
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
31697
|
+
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
31698
|
+
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
31699
|
+
you: external_exports.object({ name: external_exports.string(), device: external_exports.string().nullable(), tokenId: external_exports.string() }).optional(),
|
|
31463
31700
|
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
31464
31701
|
* contact on the same parentId. Keeps reappearing until you call
|
|
31465
31702
|
* set_task_state on its notificationId. */
|
|
@@ -31471,12 +31708,21 @@ var PendingRepliesSchema2 = external_exports.object({
|
|
|
31471
31708
|
createdAt: external_exports.string(),
|
|
31472
31709
|
/** The user seeded this request with a past conversation — call get_thread on it
|
|
31473
31710
|
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
31474
|
-
contextParentId: external_exports.string().optional()
|
|
31711
|
+
contextParentId: external_exports.string().optional(),
|
|
31712
|
+
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
31713
|
+
* the account — the name here — which has not been seen since it landed, so nobody
|
|
31714
|
+
* came for it. Handed to you because you are the session that is here. Take it like
|
|
31715
|
+
* any request (set_task_state claims it, reply with contact on its parentId), and say
|
|
31716
|
+
* whose it was, because the user chose that agent on purpose. */
|
|
31717
|
+
stranded: external_exports.string().optional()
|
|
31475
31718
|
})
|
|
31476
31719
|
),
|
|
31477
31720
|
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
31478
31721
|
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
31479
31722
|
* fulfill one by calling contact on its parentId. */
|
|
31723
|
+
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
31724
|
+
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
31725
|
+
also: external_exports.array(RideAlongSchema2).optional(),
|
|
31480
31726
|
owedCallbacks: external_exports.array(
|
|
31481
31727
|
external_exports.object({ parentId: external_exports.string(), trigger: CallbackTriggerSchema2, note: external_exports.string() })
|
|
31482
31728
|
),
|
|
@@ -31512,7 +31758,11 @@ var NotifyResponseSchema2 = external_exports.object({
|
|
|
31512
31758
|
status: NotifyStatusSchema2,
|
|
31513
31759
|
createdAt: external_exports.string().datetime(),
|
|
31514
31760
|
answer: UserAnswerSchema2.optional(),
|
|
31515
|
-
answeredAt: external_exports.string().datetime().optional()
|
|
31761
|
+
answeredAt: external_exports.string().datetime().optional(),
|
|
31762
|
+
/** Ride-alongs for THIS agent — pending work it should pick up when it's done with
|
|
31763
|
+
* what it came for. Present on any reply, because an unwakeable agent's only
|
|
31764
|
+
* reliable moment is one it initiated. Absent/empty = nothing owed. */
|
|
31765
|
+
also: external_exports.array(RideAlongSchema2).optional()
|
|
31516
31766
|
});
|
|
31517
31767
|
var NotifyPlanUnitSchema2 = external_exports.object({
|
|
31518
31768
|
notificationId: external_exports.string(),
|
|
@@ -31534,7 +31784,13 @@ var NotifyPlanUnitSchema2 = external_exports.object({
|
|
|
31534
31784
|
proposal: external_exports.object({
|
|
31535
31785
|
select: SelectShapeSchema2,
|
|
31536
31786
|
options: external_exports.array(external_exports.object({ label: external_exports.string().min(1) })).optional()
|
|
31537
|
-
}).optional()
|
|
31787
|
+
}).optional(),
|
|
31788
|
+
/** What the broker READ this unit as wanting from the human (#952 layer 2): a `decision`
|
|
31789
|
+
* between alternatives, an `approval` the agent is blocked on, or `knowledge` it just
|
|
31790
|
+
* needs to know. Reported so the agent can correct a misread the same way it ratifies a
|
|
31791
|
+
* shape — the read RAISES (a decision always asks) and never silences a question the
|
|
31792
|
+
* agent declared (#731, #923). */
|
|
31793
|
+
wants: external_exports.enum(["decision", "approval", "knowledge"]).optional()
|
|
31538
31794
|
});
|
|
31539
31795
|
var NotifyPlanSchema2 = external_exports.object({
|
|
31540
31796
|
units: external_exports.array(NotifyPlanUnitSchema2),
|
|
@@ -31558,6 +31814,9 @@ var UserResponseSchema2 = external_exports.object({
|
|
|
31558
31814
|
});
|
|
31559
31815
|
var VoiceKeySchema2 = external_exports.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
31560
31816
|
var AgendaTurnSchema2 = external_exports.object({
|
|
31817
|
+
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
31818
|
+
* the planner declares duplicates instead of asking them twice. */
|
|
31819
|
+
coveredIds: external_exports.array(external_exports.string()).optional(),
|
|
31561
31820
|
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
31562
31821
|
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
31563
31822
|
info: external_exports.array(external_exports.string().min(1)).max(3).default([]),
|
|
@@ -31591,6 +31850,7 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
31591
31850
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
31592
31851
|
blocking: external_exports.boolean().optional()
|
|
31593
31852
|
});
|
|
31853
|
+
var CLAIM_STALE_MS2 = 30 * 6e4;
|
|
31594
31854
|
var InboxItemSchema2 = external_exports.object({
|
|
31595
31855
|
id: external_exports.string(),
|
|
31596
31856
|
/** The conversation thread + connection this item lives on. Present on the replied
|
|
@@ -31610,6 +31870,15 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
31610
31870
|
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
31611
31871
|
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
31612
31872
|
seq: external_exports.number().int().optional(),
|
|
31873
|
+
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
31874
|
+
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
31875
|
+
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
31876
|
+
* screen read "1 of 3", then "1 of 2", then no chip at all, each answer having removed the
|
|
31877
|
+
* only evidence of itself. How big an arrival is, is a fact about the arrival, so the
|
|
31878
|
+
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
31879
|
+
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
31880
|
+
* the one part of its own arrival a single row cannot answer. */
|
|
31881
|
+
units: external_exports.number().int().positive().optional(),
|
|
31613
31882
|
tokenId: external_exports.string().optional(),
|
|
31614
31883
|
status: NotifyStatusSchema2,
|
|
31615
31884
|
context: ContextSchema2,
|
|
@@ -31630,6 +31899,16 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
31630
31899
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
31631
31900
|
* which must never CLAIM stalled. */
|
|
31632
31901
|
lastSeenAt: external_exports.string().optional(),
|
|
31902
|
+
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
31903
|
+
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
31904
|
+
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
31905
|
+
* than `createdAt`: a claim is very often picked up long after the row was born — the
|
|
31906
|
+
* inbox keeps an ANSWERED row visible while the agent works the follow-up, so a question
|
|
31907
|
+
* asked this morning and claimed a minute ago is eight hours old and one minute into its
|
|
31908
|
+
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
31909
|
+
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
31910
|
+
* `createdAt`. */
|
|
31911
|
+
agentStateAt: external_exports.string().datetime().optional(),
|
|
31633
31912
|
agenda: external_exports.array(AgendaTurnSchema2).optional(),
|
|
31634
31913
|
/** On a replied detail (#397): the next steps the user attached to the answer
|
|
31635
31914
|
* ("call back after lunch") — shown so they can see the commitment was captured. */
|
|
@@ -31701,11 +31980,23 @@ var SnoozeRequestSchema2 = external_exports.object({
|
|
|
31701
31980
|
requestId: external_exports.string(),
|
|
31702
31981
|
until: external_exports.string().datetime()
|
|
31703
31982
|
});
|
|
31983
|
+
var APNS_TOKEN_RE2 = /^[0-9a-fA-F]{64}$/;
|
|
31704
31984
|
var PushTokenSchema2 = external_exports.object({
|
|
31705
31985
|
voipToken: external_exports.string().min(1).optional(),
|
|
31706
31986
|
alertToken: external_exports.string().min(1).optional(),
|
|
31707
31987
|
fcmToken: external_exports.string().min(1).optional(),
|
|
31708
31988
|
platform: external_exports.enum(["ios", "android"])
|
|
31989
|
+
}).superRefine((v, ctx) => {
|
|
31990
|
+
if (v.platform !== "ios") return;
|
|
31991
|
+
for (const field of ["voipToken", "alertToken"]) {
|
|
31992
|
+
const token = v[field];
|
|
31993
|
+
if (token === void 0 || APNS_TOKEN_RE2.test(token)) continue;
|
|
31994
|
+
ctx.addIssue({
|
|
31995
|
+
code: external_exports.ZodIssueCode.custom,
|
|
31996
|
+
path: [field],
|
|
31997
|
+
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
31998
|
+
});
|
|
31999
|
+
}
|
|
31709
32000
|
});
|
|
31710
32001
|
var MissedCallSchema2 = external_exports.enum([
|
|
31711
32002
|
"retry_10m",
|
|
@@ -31756,6 +32047,15 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
31756
32047
|
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
31757
32048
|
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
31758
32049
|
pstnCalls: external_exports.boolean().optional(),
|
|
32050
|
+
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
32051
|
+
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
32052
|
+
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
32053
|
+
* the same reason `voiceMode` is (a stale client PATCHing the whole object must not
|
|
32054
|
+
* clobber it) and one more: a GUESSED timezone schedules reminders hours off, and
|
|
32055
|
+
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
32056
|
+
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
32057
|
+
* call — honest about what we know. */
|
|
32058
|
+
timezone: external_exports.string().min(1).max(64).optional(),
|
|
31759
32059
|
/** Account E2EE state (text lane): 'off' (default) = today's plaintext; 'on' =
|
|
31760
32060
|
* content is sealed end-to-end between the local agent and the phone. Like
|
|
31761
32061
|
* voiceMode, OPTIONAL and NOT defaulted so a stale client PATCHing the full
|
|
@@ -31781,6 +32081,15 @@ var HistoryItemSchema2 = external_exports.object({
|
|
|
31781
32081
|
/** When you answered the agent's notification (agent→user only). */
|
|
31782
32082
|
humanAckedAt: external_exports.string().nullable()
|
|
31783
32083
|
});
|
|
32084
|
+
var ACTIVITY_LINES2 = 2;
|
|
32085
|
+
var ACTIVITY_LINE_MAX2 = 80;
|
|
32086
|
+
var AgentActivitySchema2 = external_exports.object({
|
|
32087
|
+
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
32088
|
+
lines: external_exports.array(external_exports.string().max(ACTIVITY_LINE_MAX2)).max(ACTIVITY_LINES2),
|
|
32089
|
+
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
32090
|
+
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
32091
|
+
at: external_exports.string().datetime()
|
|
32092
|
+
});
|
|
31784
32093
|
var ConnectionSummarySchema2 = external_exports.object({
|
|
31785
32094
|
/** The connection = the agent's token id (used to address a request). */
|
|
31786
32095
|
id: external_exports.string(),
|
|
@@ -31814,6 +32123,11 @@ var ConnectionSummarySchema2 = external_exports.object({
|
|
|
31814
32123
|
harnesses: external_exports.array(external_exports.object({ name: external_exports.string(), label: external_exports.string(), status: external_exports.string() })).optional(),
|
|
31815
32124
|
workspaces: external_exports.array(external_exports.string()).optional()
|
|
31816
32125
|
}).optional(),
|
|
32126
|
+
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
32127
|
+
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
32128
|
+
* used straight from a terminal emits no work events; the page says so rather than
|
|
32129
|
+
* drawing an empty box). */
|
|
32130
|
+
activity: AgentActivitySchema2.optional(),
|
|
31817
32131
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
31818
32132
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
31819
32133
|
managed: external_exports.boolean()
|
|
@@ -31882,7 +32196,8 @@ var HandoffSchema2 = external_exports.object({
|
|
|
31882
32196
|
recap: external_exports.boolean().optional()
|
|
31883
32197
|
});
|
|
31884
32198
|
var NoteSourceSchema2 = external_exports.enum(["app", "call"]);
|
|
31885
|
-
var NoteStatusSchema2 = external_exports.enum(["open", "assigned", "done"]);
|
|
32199
|
+
var NoteStatusSchema2 = external_exports.enum(["open", "assigned", "in_progress", "done"]);
|
|
32200
|
+
var NoteRepeatSchema2 = external_exports.enum(["once", "until_done"]);
|
|
31886
32201
|
var DecisionSchema2 = external_exports.object({
|
|
31887
32202
|
id: external_exports.string(),
|
|
31888
32203
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
@@ -31907,11 +32222,29 @@ var NoteSchema2 = external_exports.object({
|
|
|
31907
32222
|
assignee: external_exports.string().nullable(),
|
|
31908
32223
|
/** The request thread minted at assignment; null until assigned. */
|
|
31909
32224
|
parentId: external_exports.string().nullable(),
|
|
32225
|
+
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
32226
|
+
* call — never a deadline, and nothing rings when it passes. Null = "the very next
|
|
32227
|
+
* call", the right reading of "remind me to…" with no time attached. */
|
|
32228
|
+
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
32229
|
+
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
32230
|
+
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
32231
|
+
dueAt: external_exports.string().nullable().default(null),
|
|
32232
|
+
repeat: NoteRepeatSchema2.default("once"),
|
|
32233
|
+
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
32234
|
+
rides: external_exports.number().int().default(0),
|
|
32235
|
+
lastRideAt: external_exports.string().nullable().default(null),
|
|
31910
32236
|
createdAt: external_exports.string()
|
|
31911
32237
|
});
|
|
31912
32238
|
var CreateNoteSchema2 = external_exports.object({
|
|
31913
32239
|
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
31914
|
-
text: external_exports.string().min(1).max(4e3)
|
|
32240
|
+
text: external_exports.string().min(1).max(4e3),
|
|
32241
|
+
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
32242
|
+
* their next call instead of being handed to an agent. Everything else about the
|
|
32243
|
+
* note is identical; this is the one parameter that separates the two. */
|
|
32244
|
+
forMe: external_exports.boolean().optional(),
|
|
32245
|
+
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
32246
|
+
dueAt: external_exports.string().datetime().optional(),
|
|
32247
|
+
repeat: NoteRepeatSchema2.optional()
|
|
31915
32248
|
});
|
|
31916
32249
|
var RecordDecisionSchema2 = external_exports.object({
|
|
31917
32250
|
/** An open decision (from /clarify) to answer. */
|
|
@@ -32690,9 +33023,30 @@ function startSession(opts) {
|
|
|
32690
33023
|
};
|
|
32691
33024
|
}
|
|
32692
33025
|
|
|
33026
|
+
// src/paigy/activity.ts
|
|
33027
|
+
function shortenPaths(s) {
|
|
33028
|
+
return s.replace(/(?<![\w.@+-])(?:\/[\w.@+-]+){3,}/g, (p) => {
|
|
33029
|
+
const parts = p.split("/").filter(Boolean);
|
|
33030
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
33031
|
+
});
|
|
33032
|
+
}
|
|
33033
|
+
function workLine(event) {
|
|
33034
|
+
const note = (event.note ?? "").split("\n").find((l) => l.trim()) ?? "";
|
|
33035
|
+
const line = shortenPaths([event.tool.trim(), note.trim()].filter(Boolean).join(" \u2014 ").replace(/\s+/g, " "));
|
|
33036
|
+
return line.length > ACTIVITY_LINE_MAX2 ? `${line.slice(0, ACTIVITY_LINE_MAX2 - 1)}\u2026` : line;
|
|
33037
|
+
}
|
|
33038
|
+
function pushWork(lines, line) {
|
|
33039
|
+
if (!line || lines[lines.length - 1] === line) return [...lines];
|
|
33040
|
+
return [...lines, line].slice(-ACTIVITY_LINES2);
|
|
33041
|
+
}
|
|
33042
|
+
function sameTail(a, b) {
|
|
33043
|
+
return a.length === b.length && a.every((l, i) => l === b[i]);
|
|
33044
|
+
}
|
|
33045
|
+
|
|
32693
33046
|
// src/run.ts
|
|
32694
33047
|
function runHarness(opts) {
|
|
32695
33048
|
let running = true;
|
|
33049
|
+
let tail = [];
|
|
32696
33050
|
const state = { ...opts.exclusive ? { exclusive: true } : {} };
|
|
32697
33051
|
let session = null;
|
|
32698
33052
|
const asMe = { token: opts.token };
|
|
@@ -32722,6 +33076,7 @@ function runHarness(opts) {
|
|
|
32722
33076
|
}
|
|
32723
33077
|
if (event.kind === "work") {
|
|
32724
33078
|
opts.log(`\u2699 ${event.tool}${event.note ? ` \u2014 ${event.note.split("\n")[0] ?? ""}` : ""}`);
|
|
33079
|
+
tail = pushWork(tail, workLine(event));
|
|
32725
33080
|
return;
|
|
32726
33081
|
}
|
|
32727
33082
|
const { parentId } = await mirror(event, state, deps);
|
|
@@ -32729,6 +33084,7 @@ function runHarness(opts) {
|
|
|
32729
33084
|
if (event.kind === "turn") opts.log(`${event.role}: ${event.text.split("\n")[0] ?? ""}`);
|
|
32730
33085
|
if (event.kind === "idle") {
|
|
32731
33086
|
state.resting = true;
|
|
33087
|
+
tail = [];
|
|
32732
33088
|
if (endsWithQuestion(event.result)) {
|
|
32733
33089
|
const local = await opts.localAsk?.question?.(event.result ?? "") ?? null;
|
|
32734
33090
|
if (local?.trim() && session && running) {
|
|
@@ -32766,8 +33122,10 @@ function runHarness(opts) {
|
|
|
32766
33122
|
session?.send(text);
|
|
32767
33123
|
},
|
|
32768
33124
|
working: () => running && state.resting !== true,
|
|
33125
|
+
tail: () => [...tail],
|
|
32769
33126
|
stop() {
|
|
32770
33127
|
running = false;
|
|
33128
|
+
tail = [];
|
|
32771
33129
|
cancelAsks(state);
|
|
32772
33130
|
session?.stop();
|
|
32773
33131
|
session = null;
|
|
@@ -32820,6 +33178,12 @@ function resolveWakeDir(pinned, deps) {
|
|
|
32820
33178
|
|
|
32821
33179
|
// src/host.ts
|
|
32822
33180
|
var HOST_FILE = join4(homedir5(), ".paigy", "host.json");
|
|
33181
|
+
var IDLE_WAKE_MS = 10 * 6e4;
|
|
33182
|
+
function wakeAction(live, seenAt, now) {
|
|
33183
|
+
if (!live) return "spawn";
|
|
33184
|
+
if (seenAt === void 0) return "skip";
|
|
33185
|
+
return now - seenAt >= IDLE_WAKE_MS ? "prompt" : "skip";
|
|
33186
|
+
}
|
|
32823
33187
|
function startHost(opts) {
|
|
32824
33188
|
const runs = /* @__PURE__ */ new Map();
|
|
32825
33189
|
const asHost = { token: opts.token };
|
|
@@ -32875,12 +33239,15 @@ function startHost(opts) {
|
|
|
32875
33239
|
}
|
|
32876
33240
|
}
|
|
32877
33241
|
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex", antigravity: "agy" };
|
|
32878
|
-
const
|
|
33242
|
+
const wakePrompt = (fresh) => "You were woken because Paigy work is waiting for you. " + (fresh ? "This is a fresh session with your existing identity, so you may already have work in flight that you can't remember. " : "You have been running, so some of this may already be yours \u2014 but not what arrived while you were idle. ") + "Call check_replies FIRST, then get_thread on each conversation it returns and read it before you touch anything: it holds what you were doing, where (a repo or worktree may not be this folder), and what the owner already decided. Then handle what's waiting and follow your paigy instructions to stay in the loop. Speak to the owner at exactly TWO moments: when you are BLOCKED on a decision only they can make (contact with waiting:'hard', the decision as the ask, options if you have real ones), and when you are DONE (one short report: what shipped, how you verified it, anything you flagged). Progress is never a contact \u2014 call set_task_state('in_progress') when you pick work up and the app shows you working; narrate to the terminal, not to the human.";
|
|
32879
33243
|
async function sweepSlots() {
|
|
32880
33244
|
for (const slot of listSlots()) {
|
|
32881
33245
|
const harness = SLOT_HARNESS[slot] ?? (slot === "Desktop" ? void 0 : "claude");
|
|
32882
33246
|
const key = `slot:${slot}`;
|
|
32883
|
-
if (!harness
|
|
33247
|
+
if (!harness) continue;
|
|
33248
|
+
const live = runs.get(key);
|
|
33249
|
+
const action = wakeAction(!!live, published.get(key)?.movedAt, Date.now());
|
|
33250
|
+
if (action === "skip") continue;
|
|
32884
33251
|
const token = readToken(slot);
|
|
32885
33252
|
if (!token) continue;
|
|
32886
33253
|
const workspace = resolveWakeDir(slotIdentity(slot).workspace, opts.wsDeps);
|
|
@@ -32888,12 +33255,20 @@ function startHost(opts) {
|
|
|
32888
33255
|
const work = await checkReplies({ token }).catch(() => null);
|
|
32889
33256
|
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
32890
33257
|
const label = slotName(slot) ?? slot;
|
|
33258
|
+
if (live) {
|
|
33259
|
+
const seen = published.get(key);
|
|
33260
|
+
const quiet = seen ? Math.round((Date.now() - seen.movedAt) / 6e4) : 0;
|
|
33261
|
+
live.run.send(wakePrompt(false));
|
|
33262
|
+
if (seen) seen.movedAt = Date.now();
|
|
33263
|
+
opts.log(`\u25B6 re-woke ${label} \u2014 quiet ${quiet}m with work waiting`);
|
|
33264
|
+
continue;
|
|
33265
|
+
}
|
|
32891
33266
|
const log = (line) => opts.log(`[${label}] ${line}`);
|
|
32892
33267
|
const run = runHarness({
|
|
32893
33268
|
harness,
|
|
32894
33269
|
cwd: workspace,
|
|
32895
33270
|
mode: "bypass",
|
|
32896
|
-
prompt:
|
|
33271
|
+
prompt: wakePrompt(true),
|
|
32897
33272
|
exclusive: true,
|
|
32898
33273
|
token,
|
|
32899
33274
|
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
@@ -32909,6 +33284,27 @@ function startHost(opts) {
|
|
|
32909
33284
|
opts.log(`\u25B6 woke ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
32910
33285
|
}
|
|
32911
33286
|
}
|
|
33287
|
+
const ACTIVITY_MS = 2e3;
|
|
33288
|
+
const published = /* @__PURE__ */ new Map();
|
|
33289
|
+
const streamActivity = () => {
|
|
33290
|
+
const publishTail = (token, lines) => {
|
|
33291
|
+
void heartbeat(void 0, { token, activity: { lines, at: (/* @__PURE__ */ new Date()).toISOString() } }).catch(() => {
|
|
33292
|
+
});
|
|
33293
|
+
};
|
|
33294
|
+
for (const [key, r] of runs) {
|
|
33295
|
+
if (!r.token) continue;
|
|
33296
|
+
const lines = r.run.tail();
|
|
33297
|
+
const was = published.get(key);
|
|
33298
|
+
if (was && sameTail(was.lines, lines)) continue;
|
|
33299
|
+
published.set(key, { token: r.token, lines, movedAt: Date.now() });
|
|
33300
|
+
publishTail(r.token, lines);
|
|
33301
|
+
}
|
|
33302
|
+
for (const [key, was] of published) {
|
|
33303
|
+
if (runs.has(key)) continue;
|
|
33304
|
+
published.delete(key);
|
|
33305
|
+
if (was.lines.length > 0) publishTail(was.token, []);
|
|
33306
|
+
}
|
|
33307
|
+
};
|
|
32912
33308
|
const publish = () => {
|
|
32913
33309
|
try {
|
|
32914
33310
|
writeFileSync3(HOST_FILE, JSON.stringify({ pid: process.pid, at: Date.now(), roster: api.roster() }));
|
|
@@ -32922,6 +33318,7 @@ function startHost(opts) {
|
|
|
32922
33318
|
void sweepSlots();
|
|
32923
33319
|
publish();
|
|
32924
33320
|
}, 5e3);
|
|
33321
|
+
const activityTick = setInterval(streamActivity, ACTIVITY_MS);
|
|
32925
33322
|
const stopSessions = () => {
|
|
32926
33323
|
for (const { run, label } of runs.values()) {
|
|
32927
33324
|
run.stop();
|
|
@@ -32959,6 +33356,8 @@ function startHost(opts) {
|
|
|
32959
33356
|
clearInterval(pulse);
|
|
32960
33357
|
clearInterval(spawnPoll);
|
|
32961
33358
|
stopSessions();
|
|
33359
|
+
streamActivity();
|
|
33360
|
+
clearInterval(activityTick);
|
|
32962
33361
|
try {
|
|
32963
33362
|
writeFileSync3(HOST_FILE, JSON.stringify({ pid: process.pid, at: 0, roster: [] }));
|
|
32964
33363
|
} catch {
|
|
@@ -32970,74 +33369,38 @@ function startHost(opts) {
|
|
|
32970
33369
|
}
|
|
32971
33370
|
|
|
32972
33371
|
// src/cli.ts
|
|
32973
|
-
function parseArgs(argv) {
|
|
32974
|
-
const args = { harness: "claude", mode: "bypass", cwd: process.cwd(), doctor: false, host: false, pair: false, service: false, setup: false, grant: [], hatch: null, voice: null, slot: null, identity: null, graceSeconds: 90, prompt: "", handoff: false };
|
|
32975
|
-
const words = [];
|
|
32976
|
-
for (let i = 0; i < argv.length; i++) {
|
|
32977
|
-
const arg = argv[i];
|
|
32978
|
-
const next = () => argv[++i];
|
|
32979
|
-
if (arg === "--harness") {
|
|
32980
|
-
const v = next();
|
|
32981
|
-
if (v !== "claude" && v !== "codex" && v !== "agy") return { error: `--harness must be claude, codex, or agy, got ${v ?? "nothing"}` };
|
|
32982
|
-
args.harness = v;
|
|
32983
|
-
} else if (arg === "--mode") {
|
|
32984
|
-
const v = next();
|
|
32985
|
-
if (v !== "bypass" && v !== "ask") return { error: `--mode must be bypass or ask, got ${v ?? "nothing"}` };
|
|
32986
|
-
args.mode = v;
|
|
32987
|
-
} else if (arg === "--cwd") {
|
|
32988
|
-
const v = next();
|
|
32989
|
-
if (!v) return { error: "--cwd needs a directory" };
|
|
32990
|
-
args.cwd = v;
|
|
32991
|
-
} else if (arg === "--doctor") {
|
|
32992
|
-
args.doctor = true;
|
|
32993
|
-
} else if (arg === "--grace") {
|
|
32994
|
-
const v = Number(next());
|
|
32995
|
-
if (!Number.isFinite(v) || v < 0) return { error: "--grace needs seconds (0 = straight to phone)" };
|
|
32996
|
-
args.graceSeconds = v;
|
|
32997
|
-
} else if (arg === "host") {
|
|
32998
|
-
args.host = true;
|
|
32999
|
-
} else if (arg === "pair") {
|
|
33000
|
-
args.pair = true;
|
|
33001
|
-
} else if (arg === "setup") {
|
|
33002
|
-
args.setup = true;
|
|
33003
|
-
} else if (arg === "service") {
|
|
33004
|
-
args.service = true;
|
|
33005
|
-
} else if (arg === "--grant") {
|
|
33006
|
-
const v = next();
|
|
33007
|
-
if (!v) return { error: "--grant needs a folder to allow" };
|
|
33008
|
-
args.grant.push(v);
|
|
33009
|
-
} else if (arg === "handoff") {
|
|
33010
|
-
args.handoff = true;
|
|
33011
|
-
} else if (arg === "hatch") {
|
|
33012
|
-
const v = next();
|
|
33013
|
-
if (!v) return { error: 'hatch needs a name: paigy-harness hatch "Voice bug hunter"' };
|
|
33014
|
-
args.hatch = v;
|
|
33015
|
-
} else if (arg === "--voice") {
|
|
33016
|
-
args.voice = next() ?? null;
|
|
33017
|
-
} else if (arg === "--slot") {
|
|
33018
|
-
args.slot = next() ?? null;
|
|
33019
|
-
} else if (arg === "--identity") {
|
|
33020
|
-
const v = next();
|
|
33021
|
-
if (!v) return { error: "--identity needs a hatched agent's name" };
|
|
33022
|
-
args.identity = v;
|
|
33023
|
-
} else if (arg?.startsWith("--")) {
|
|
33024
|
-
return { error: `unknown flag ${arg}` };
|
|
33025
|
-
} else if (arg) {
|
|
33026
|
-
words.push(arg);
|
|
33027
|
-
}
|
|
33028
|
-
}
|
|
33029
|
-
args.prompt = words.join(" ");
|
|
33030
|
-
if (!args.doctor && !args.handoff && !args.hatch && !args.host && !args.pair && !args.service && !args.setup && !args.prompt) return { error: "a prompt is required (or setup / --doctor / pair / host / service / hatch NAME / handoff)" };
|
|
33031
|
-
return args;
|
|
33032
|
-
}
|
|
33033
33372
|
var STATUS_MARK = { ready: "\u2713", login: "\u25D0", "adapter-missing": "\u25D0", missing: "\u2717" };
|
|
33034
33373
|
async function main() {
|
|
33035
33374
|
const parsed = parseArgs(process.argv.slice(2));
|
|
33036
33375
|
if ("error" in parsed) {
|
|
33037
33376
|
console.error(`paigy-harness: ${parsed.error}`);
|
|
33038
|
-
console.error(
|
|
33377
|
+
console.error(USAGE);
|
|
33039
33378
|
process.exit(2);
|
|
33040
33379
|
}
|
|
33380
|
+
if (parsed.help) {
|
|
33381
|
+
console.log(USAGE);
|
|
33382
|
+
console.log("");
|
|
33383
|
+
console.log(" setup pair this machine, set up every agent found here, install the host service");
|
|
33384
|
+
console.log(" pair pair this machine only (QR / code)");
|
|
33385
|
+
console.log(" host host sessions your phone launches (--grant DIR to allow a folder)");
|
|
33386
|
+
console.log(" service install the host as a login service (macOS)");
|
|
33387
|
+
console.log(" enable-tools allowlist Paigy's tools in Claude Code so they don't prompt each time");
|
|
33388
|
+
console.log(" hatch NAME mint a sibling identity from this machine's credential");
|
|
33389
|
+
console.log(" handoff pin this terminal's identity here so your phone can resume it");
|
|
33390
|
+
console.log(" --doctor report which agent CLIs are installed and ready");
|
|
33391
|
+
console.log("");
|
|
33392
|
+
console.log('Anything else is a PROMPT: paigy-harness --harness codex "fix the tests"');
|
|
33393
|
+
return;
|
|
33394
|
+
}
|
|
33395
|
+
if (parsed.enableTools) {
|
|
33396
|
+
try {
|
|
33397
|
+
execSync(`${ENABLE_TOOLS_COMMAND} --scope ${parsed.scope}`, { stdio: "inherit", shell: "/bin/sh" });
|
|
33398
|
+
} catch {
|
|
33399
|
+
console.error(`Couldn't run it \u2014 try directly: ${ENABLE_TOOLS_COMMAND} --scope ${parsed.scope}`);
|
|
33400
|
+
process.exit(1);
|
|
33401
|
+
}
|
|
33402
|
+
return;
|
|
33403
|
+
}
|
|
33041
33404
|
if (parsed.doctor) {
|
|
33042
33405
|
for (const a of detectAll()) {
|
|
33043
33406
|
console.log(`${STATUS_MARK[a.status]} ${a.label}${a.hint ? ` \u2014 ${a.hint}` : ""}${a.install ? `
|
|
@@ -33120,6 +33483,14 @@ async function main() {
|
|
|
33120
33483
|
} catch {
|
|
33121
33484
|
}
|
|
33122
33485
|
}
|
|
33486
|
+
if (which("claude")) {
|
|
33487
|
+
try {
|
|
33488
|
+
execSync(ENABLE_TOOLS_COMMAND, { stdio: "ignore", shell: "/bin/sh" });
|
|
33489
|
+
console.log("\u2713 Paigy's tools allowlisted in Claude Code (they won't prompt each time)");
|
|
33490
|
+
} catch {
|
|
33491
|
+
console.log(` allowlist it later with: paigy-harness enable-tools`);
|
|
33492
|
+
}
|
|
33493
|
+
}
|
|
33123
33494
|
try {
|
|
33124
33495
|
const settings = join5(homedir6(), ".claude", "settings.json");
|
|
33125
33496
|
if (which("claude") && existsSync5(settings)) {
|
|
@@ -33306,9 +33677,6 @@ async function main() {
|
|
|
33306
33677
|
});
|
|
33307
33678
|
}
|
|
33308
33679
|
void main();
|
|
33309
|
-
export {
|
|
33310
|
-
parseArgs
|
|
33311
|
-
};
|
|
33312
33680
|
/*! Bundled license information:
|
|
33313
33681
|
|
|
33314
33682
|
undici/lib/web/fetch/body.js:
|