@paigy/harness 0.2.4 → 0.2.8
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 +9 -0
- package/dist/cli.js +67 -15
- package/dist/main.js +68 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -168,6 +168,15 @@ macOS launchd agent so hosting survives reboots — the host loop IS the service
|
|
|
168
168
|
launchd just keeps it alive (the old "no daemon" note was about the WAKE channel,
|
|
169
169
|
which still belongs to paigy-listen).
|
|
170
170
|
|
|
171
|
+
## The window is not distributable (yet)
|
|
172
|
+
|
|
173
|
+
npm is the ONLY shipping channel: `@paigy/harness` installs the CLI and the whole
|
|
174
|
+
bridge, and updates with `npm i -g @paigy/harness@latest`. The Electron window runs
|
|
175
|
+
from source (`electron .`) — there is no packaging, no Developer ID signing or
|
|
176
|
+
notarization, and therefore no auto-update (macOS auto-update requires the signing).
|
|
177
|
+
A downloaded unsigned app would hit Gatekeeper's "damaged" dialog, which is worse
|
|
178
|
+
than no download button. Steps for when it's time: #854.
|
|
179
|
+
|
|
171
180
|
## Run it
|
|
172
181
|
|
|
173
182
|
```sh
|
package/dist/cli.js
CHANGED
|
@@ -11631,10 +11631,10 @@ var require_headers = __commonJS({
|
|
|
11631
11631
|
const lowercaseName = isLowerCase ? name : name.toLowerCase();
|
|
11632
11632
|
const exists = this[kHeadersMap].get(lowercaseName);
|
|
11633
11633
|
if (exists) {
|
|
11634
|
-
const
|
|
11634
|
+
const delimiter3 = lowercaseName === "cookie" ? "; " : ", ";
|
|
11635
11635
|
this[kHeadersMap].set(lowercaseName, {
|
|
11636
11636
|
name: exists.name,
|
|
11637
|
-
value: `${exists.value}${
|
|
11637
|
+
value: `${exists.value}${delimiter3}${value}`
|
|
11638
11638
|
});
|
|
11639
11639
|
} else {
|
|
11640
11640
|
this[kHeadersMap].set(lowercaseName, { name, value });
|
|
@@ -23091,7 +23091,7 @@ var require_lib = __commonJS({
|
|
|
23091
23091
|
import { spawnSync } from "child_process";
|
|
23092
23092
|
import { existsSync } from "fs";
|
|
23093
23093
|
import { homedir } from "os";
|
|
23094
|
-
import { join } from "path";
|
|
23094
|
+
import { delimiter, dirname, join } from "path";
|
|
23095
23095
|
var CATALOG = [
|
|
23096
23096
|
{
|
|
23097
23097
|
name: "claude",
|
|
@@ -23115,7 +23115,13 @@ var CATALOG = [
|
|
|
23115
23115
|
}
|
|
23116
23116
|
];
|
|
23117
23117
|
function probeDirs(home = homedir()) {
|
|
23118
|
-
return [
|
|
23118
|
+
return [
|
|
23119
|
+
dirname(process.execPath),
|
|
23120
|
+
join(home, ".local", "bin"),
|
|
23121
|
+
"/usr/local/bin",
|
|
23122
|
+
"/opt/homebrew/bin",
|
|
23123
|
+
join(home, ".npm-global", "bin")
|
|
23124
|
+
];
|
|
23119
23125
|
}
|
|
23120
23126
|
function which(bin) {
|
|
23121
23127
|
const hit = spawnSync("which", [bin], { encoding: "utf8" });
|
|
@@ -23132,7 +23138,11 @@ function runProbe(argv) {
|
|
|
23132
23138
|
if (!cmd) return false;
|
|
23133
23139
|
const resolved = which(cmd);
|
|
23134
23140
|
if (!resolved) return false;
|
|
23135
|
-
return spawnSync(resolved, args, {
|
|
23141
|
+
return spawnSync(resolved, args, {
|
|
23142
|
+
timeout: 1e4,
|
|
23143
|
+
stdio: "ignore",
|
|
23144
|
+
env: { ...process.env, PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter) }
|
|
23145
|
+
}).status === 0;
|
|
23136
23146
|
}
|
|
23137
23147
|
function detect(entry, deps = {}) {
|
|
23138
23148
|
const find = deps.which ?? which;
|
|
@@ -29735,7 +29745,12 @@ var UserAnswerSchema = external_exports.discriminatedUnion("kind", [
|
|
|
29735
29745
|
external_exports.object({ kind: external_exports.literal("ranked"), optionIds: external_exports.array(external_exports.string()), labels: external_exports.array(external_exports.string()).optional() }),
|
|
29736
29746
|
external_exports.object({ kind: external_exports.literal("clarify"), chunks: external_exports.array(external_exports.string()).min(1) }),
|
|
29737
29747
|
external_exports.object({ kind: external_exports.literal("confirm"), approved: external_exports.boolean() }),
|
|
29738
|
-
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema).min(1) })
|
|
29748
|
+
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema).min(1) }),
|
|
29749
|
+
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
29750
|
+
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
29751
|
+
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
29752
|
+
* card the user can reply to — the broker never overrides the user. */
|
|
29753
|
+
external_exports.object({ kind: external_exports.literal("precedent"), answer: external_exports.string(), derivation: external_exports.string(), sources: external_exports.array(external_exports.string()).min(1) })
|
|
29739
29754
|
]);
|
|
29740
29755
|
var IntentSchema = external_exports.object({
|
|
29741
29756
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
@@ -29748,7 +29763,12 @@ var IntentSchema = external_exports.object({
|
|
|
29748
29763
|
* "after lunch") against the agent machine's clock — the user's — and attaches
|
|
29749
29764
|
* the seconds, ready to pass straight to schedule_callback. Absent when the
|
|
29750
29765
|
* detail didn't parse (the agent interprets it) or the kind isn't defer. */
|
|
29751
|
-
dueInSeconds: external_exports.number().int().positive().optional()
|
|
29766
|
+
dueInSeconds: external_exports.number().int().positive().optional(),
|
|
29767
|
+
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
29768
|
+
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
29769
|
+
* 'other' on every row. A table that records that something was wrong and nothing
|
|
29770
|
+
* about what cannot answer "is the bot looping less this week?". */
|
|
29771
|
+
fault: external_exports.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
29752
29772
|
});
|
|
29753
29773
|
var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
29754
29774
|
external_exports.object({
|
|
@@ -29918,6 +29938,11 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
29918
29938
|
claimId: external_exports.string().optional(),
|
|
29919
29939
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
29920
29940
|
voice: external_exports.string().optional(),
|
|
29941
|
+
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
29942
|
+
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
29943
|
+
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
29944
|
+
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
29945
|
+
agent: external_exports.string().optional(),
|
|
29921
29946
|
select: SelectShapeSchema.optional(),
|
|
29922
29947
|
options: external_exports.array(OptionSchema.omit({ id: true })).optional(),
|
|
29923
29948
|
/** The seat was satisfied by the call's own ACCOUNT (replan-design.md, user_info): the
|
|
@@ -31084,7 +31109,12 @@ var UserAnswerSchema2 = external_exports.discriminatedUnion("kind", [
|
|
|
31084
31109
|
external_exports.object({ kind: external_exports.literal("ranked"), optionIds: external_exports.array(external_exports.string()), labels: external_exports.array(external_exports.string()).optional() }),
|
|
31085
31110
|
external_exports.object({ kind: external_exports.literal("clarify"), chunks: external_exports.array(external_exports.string()).min(1) }),
|
|
31086
31111
|
external_exports.object({ kind: external_exports.literal("confirm"), approved: external_exports.boolean() }),
|
|
31087
|
-
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema2).min(1) })
|
|
31112
|
+
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema2).min(1) }),
|
|
31113
|
+
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
31114
|
+
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
31115
|
+
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
31116
|
+
* card the user can reply to — the broker never overrides the user. */
|
|
31117
|
+
external_exports.object({ kind: external_exports.literal("precedent"), answer: external_exports.string(), derivation: external_exports.string(), sources: external_exports.array(external_exports.string()).min(1) })
|
|
31088
31118
|
]);
|
|
31089
31119
|
var IntentSchema2 = external_exports.object({
|
|
31090
31120
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
@@ -31097,7 +31127,12 @@ var IntentSchema2 = external_exports.object({
|
|
|
31097
31127
|
* "after lunch") against the agent machine's clock — the user's — and attaches
|
|
31098
31128
|
* the seconds, ready to pass straight to schedule_callback. Absent when the
|
|
31099
31129
|
* detail didn't parse (the agent interprets it) or the kind isn't defer. */
|
|
31100
|
-
dueInSeconds: external_exports.number().int().positive().optional()
|
|
31130
|
+
dueInSeconds: external_exports.number().int().positive().optional(),
|
|
31131
|
+
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
31132
|
+
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
31133
|
+
* 'other' on every row. A table that records that something was wrong and nothing
|
|
31134
|
+
* about what cannot answer "is the bot looping less this week?". */
|
|
31135
|
+
fault: external_exports.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
31101
31136
|
});
|
|
31102
31137
|
var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
31103
31138
|
external_exports.object({
|
|
@@ -31267,6 +31302,11 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
31267
31302
|
claimId: external_exports.string().optional(),
|
|
31268
31303
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
31269
31304
|
voice: external_exports.string().optional(),
|
|
31305
|
+
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
31306
|
+
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
31307
|
+
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
31308
|
+
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
31309
|
+
agent: external_exports.string().optional(),
|
|
31270
31310
|
select: SelectShapeSchema2.optional(),
|
|
31271
31311
|
options: external_exports.array(OptionSchema2.omit({ id: true })).optional(),
|
|
31272
31312
|
/** The seat was satisfied by the call's own ACCOUNT (replan-design.md, user_info): the
|
|
@@ -31735,7 +31775,8 @@ async function drainInput(state, deps = {}) {
|
|
|
31735
31775
|
return [];
|
|
31736
31776
|
}
|
|
31737
31777
|
const ours = (parentId) => state.exclusive || parentId === state.parentId;
|
|
31738
|
-
const
|
|
31778
|
+
const opening = !!state.exclusive && !state.parentId;
|
|
31779
|
+
const claimable = (notificationId) => opening || state.resting === true || !state.sharedWithAgent || (state.mine?.has(notificationId) ?? false);
|
|
31739
31780
|
const mine = [
|
|
31740
31781
|
...work.replies.filter((r) => ours(r.parentId) && claimable(r.notificationId)).map((r) => ({ notificationId: r.notificationId, parentId: r.parentId, answer: r.answer })),
|
|
31741
31782
|
...work.requests.filter((r) => ours(r.parentId) && claimable(r.notificationId)).map((r) => ({ notificationId: r.notificationId, parentId: r.parentId, answer: { kind: "text", text: r.text } }))
|
|
@@ -31768,7 +31809,10 @@ async function pump(session, state, deps = {}) {
|
|
|
31768
31809
|
continue;
|
|
31769
31810
|
}
|
|
31770
31811
|
const words = spokenText(item.answer);
|
|
31771
|
-
if (words)
|
|
31812
|
+
if (words) {
|
|
31813
|
+
state.resting = false;
|
|
31814
|
+
session.send(words);
|
|
31815
|
+
}
|
|
31772
31816
|
}
|
|
31773
31817
|
const keepGoing = await deps.onIdle?.() ?? false;
|
|
31774
31818
|
if (!keepGoing) return;
|
|
@@ -31956,6 +32000,9 @@ function spokenText(answer) {
|
|
|
31956
32000
|
// A call log: the user's replies are the instruction, the bot's prompts are not.
|
|
31957
32001
|
case "turns":
|
|
31958
32002
|
return answer.turns.map((t) => t.reply).join(" ").trim() || null;
|
|
32003
|
+
// An auto-answer derived from the user's past decisions — reads like a fast human.
|
|
32004
|
+
case "precedent":
|
|
32005
|
+
return answer.answer;
|
|
31959
32006
|
case "ignored":
|
|
31960
32007
|
return null;
|
|
31961
32008
|
}
|
|
@@ -31967,7 +32014,7 @@ var clip = (s) => (s.length > 120 ? `${s.slice(0, 117)}\u2026` : s) || "(no text
|
|
|
31967
32014
|
import { spawn } from "child_process";
|
|
31968
32015
|
import { existsSync as existsSync3 } from "fs";
|
|
31969
32016
|
import { homedir as homedir3 } from "os";
|
|
31970
|
-
import {
|
|
32017
|
+
import { resolve, delimiter as delimiter2 } from "path";
|
|
31971
32018
|
|
|
31972
32019
|
// src/harness/acp.ts
|
|
31973
32020
|
var none = { events: [], writes: [] };
|
|
@@ -32200,7 +32247,7 @@ function startSession(opts) {
|
|
|
32200
32247
|
// PATH knows neither — live catch 2026-08-04: the first slot-wake ENOENT'd).
|
|
32201
32248
|
env: {
|
|
32202
32249
|
...process.env,
|
|
32203
|
-
PATH: [process.env.PATH,
|
|
32250
|
+
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
32204
32251
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {}
|
|
32205
32252
|
},
|
|
32206
32253
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -32281,6 +32328,7 @@ function runHarness(opts) {
|
|
|
32281
32328
|
state.parentId ??= parentId;
|
|
32282
32329
|
if (event.kind === "turn") opts.log(`${event.role}: ${event.text.split("\n")[0] ?? ""}`);
|
|
32283
32330
|
if (event.kind === "idle") {
|
|
32331
|
+
state.resting = true;
|
|
32284
32332
|
if (endsWithQuestion(event.result)) {
|
|
32285
32333
|
const local = await opts.localAsk?.question?.(event.result ?? "") ?? null;
|
|
32286
32334
|
if (local?.trim() && session && running) {
|
|
@@ -32329,7 +32377,7 @@ function runHarness(opts) {
|
|
|
32329
32377
|
|
|
32330
32378
|
// src/workspaces.ts
|
|
32331
32379
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
32332
|
-
import { dirname as
|
|
32380
|
+
import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
|
|
32333
32381
|
import { homedir as homedir4 } from "os";
|
|
32334
32382
|
function workspacesFile() {
|
|
32335
32383
|
return join3(homedir4(), ".paigy", "workspaces.json");
|
|
@@ -32347,7 +32395,7 @@ function addWorkspace(dir, deps) {
|
|
|
32347
32395
|
const all = listWorkspaces(deps);
|
|
32348
32396
|
if (!all.includes(next)) {
|
|
32349
32397
|
all.push(next);
|
|
32350
|
-
mkdirSync2(
|
|
32398
|
+
mkdirSync2(dirname3(deps.file), { recursive: true });
|
|
32351
32399
|
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
32352
32400
|
}
|
|
32353
32401
|
return all;
|
|
@@ -32383,6 +32431,10 @@ function startHost(opts) {
|
|
|
32383
32431
|
log
|
|
32384
32432
|
});
|
|
32385
32433
|
runs.set(spec.sessionId, { run, label });
|
|
32434
|
+
try {
|
|
32435
|
+
saveToken({ access_token: spec.token, name: label, device: null }, `session:${spec.sessionId.slice(0, 8)}`);
|
|
32436
|
+
} catch {
|
|
32437
|
+
}
|
|
32386
32438
|
opts.log(`\u25B6 phone-launched ${label} (${spec.harness}) in ${spec.workspace}`);
|
|
32387
32439
|
}
|
|
32388
32440
|
}
|
package/dist/main.js
CHANGED
|
@@ -4577,7 +4577,7 @@ var require_lib = __commonJS({
|
|
|
4577
4577
|
import { app, BrowserWindow, dialog, ipcMain } from "electron";
|
|
4578
4578
|
import { spawn as spawn2 } from "child_process";
|
|
4579
4579
|
import { fileURLToPath } from "url";
|
|
4580
|
-
import { dirname as
|
|
4580
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
4581
4581
|
|
|
4582
4582
|
// ../../packages/sdk/dist/index.js
|
|
4583
4583
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
@@ -11157,7 +11157,12 @@ var UserAnswerSchema = external_exports.discriminatedUnion("kind", [
|
|
|
11157
11157
|
external_exports.object({ kind: external_exports.literal("ranked"), optionIds: external_exports.array(external_exports.string()), labels: external_exports.array(external_exports.string()).optional() }),
|
|
11158
11158
|
external_exports.object({ kind: external_exports.literal("clarify"), chunks: external_exports.array(external_exports.string()).min(1) }),
|
|
11159
11159
|
external_exports.object({ kind: external_exports.literal("confirm"), approved: external_exports.boolean() }),
|
|
11160
|
-
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema).min(1) })
|
|
11160
|
+
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema).min(1) }),
|
|
11161
|
+
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
11162
|
+
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
11163
|
+
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
11164
|
+
* card the user can reply to — the broker never overrides the user. */
|
|
11165
|
+
external_exports.object({ kind: external_exports.literal("precedent"), answer: external_exports.string(), derivation: external_exports.string(), sources: external_exports.array(external_exports.string()).min(1) })
|
|
11161
11166
|
]);
|
|
11162
11167
|
var IntentSchema = external_exports.object({
|
|
11163
11168
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
@@ -11170,7 +11175,12 @@ var IntentSchema = external_exports.object({
|
|
|
11170
11175
|
* "after lunch") against the agent machine's clock — the user's — and attaches
|
|
11171
11176
|
* the seconds, ready to pass straight to schedule_callback. Absent when the
|
|
11172
11177
|
* detail didn't parse (the agent interprets it) or the kind isn't defer. */
|
|
11173
|
-
dueInSeconds: external_exports.number().int().positive().optional()
|
|
11178
|
+
dueInSeconds: external_exports.number().int().positive().optional(),
|
|
11179
|
+
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
11180
|
+
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
11181
|
+
* 'other' on every row. A table that records that something was wrong and nothing
|
|
11182
|
+
* about what cannot answer "is the bot looping less this week?". */
|
|
11183
|
+
fault: external_exports.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
11174
11184
|
});
|
|
11175
11185
|
var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
11176
11186
|
external_exports.object({
|
|
@@ -11340,6 +11350,11 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
11340
11350
|
claimId: external_exports.string().optional(),
|
|
11341
11351
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
11342
11352
|
voice: external_exports.string().optional(),
|
|
11353
|
+
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
11354
|
+
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
11355
|
+
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
11356
|
+
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
11357
|
+
agent: external_exports.string().optional(),
|
|
11343
11358
|
select: SelectShapeSchema.optional(),
|
|
11344
11359
|
options: external_exports.array(OptionSchema.omit({ id: true })).optional(),
|
|
11345
11360
|
/** The seat was satisfied by the call's own ACCOUNT (replan-design.md, user_info): the
|
|
@@ -12278,7 +12293,7 @@ import { hostname } from "os";
|
|
|
12278
12293
|
import { spawnSync } from "child_process";
|
|
12279
12294
|
import { existsSync as existsSync2 } from "fs";
|
|
12280
12295
|
import { homedir as homedir2 } from "os";
|
|
12281
|
-
import { join as join2 } from "path";
|
|
12296
|
+
import { delimiter, dirname, join as join2 } from "path";
|
|
12282
12297
|
var CATALOG = [
|
|
12283
12298
|
{
|
|
12284
12299
|
name: "claude",
|
|
@@ -12302,7 +12317,13 @@ var CATALOG = [
|
|
|
12302
12317
|
}
|
|
12303
12318
|
];
|
|
12304
12319
|
function probeDirs(home = homedir2()) {
|
|
12305
|
-
return [
|
|
12320
|
+
return [
|
|
12321
|
+
dirname(process.execPath),
|
|
12322
|
+
join2(home, ".local", "bin"),
|
|
12323
|
+
"/usr/local/bin",
|
|
12324
|
+
"/opt/homebrew/bin",
|
|
12325
|
+
join2(home, ".npm-global", "bin")
|
|
12326
|
+
];
|
|
12306
12327
|
}
|
|
12307
12328
|
function which(bin) {
|
|
12308
12329
|
const hit = spawnSync("which", [bin], { encoding: "utf8" });
|
|
@@ -12319,7 +12340,11 @@ function runProbe(argv) {
|
|
|
12319
12340
|
if (!cmd) return false;
|
|
12320
12341
|
const resolved = which(cmd);
|
|
12321
12342
|
if (!resolved) return false;
|
|
12322
|
-
return spawnSync(resolved, args, {
|
|
12343
|
+
return spawnSync(resolved, args, {
|
|
12344
|
+
timeout: 1e4,
|
|
12345
|
+
stdio: "ignore",
|
|
12346
|
+
env: { ...process.env, PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter) }
|
|
12347
|
+
}).status === 0;
|
|
12323
12348
|
}
|
|
12324
12349
|
function detect(entry, deps = {}) {
|
|
12325
12350
|
const find = deps.which ?? which;
|
|
@@ -12346,7 +12371,7 @@ function detectAll(deps = {}) {
|
|
|
12346
12371
|
|
|
12347
12372
|
// src/workspaces.ts
|
|
12348
12373
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
12349
|
-
import { dirname, join as join3, resolve } from "path";
|
|
12374
|
+
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
12350
12375
|
import { homedir as homedir3 } from "os";
|
|
12351
12376
|
function workspacesFile() {
|
|
12352
12377
|
return join3(homedir3(), ".paigy", "workspaces.json");
|
|
@@ -12364,14 +12389,14 @@ function addWorkspace(dir, deps) {
|
|
|
12364
12389
|
const all = listWorkspaces(deps);
|
|
12365
12390
|
if (!all.includes(next)) {
|
|
12366
12391
|
all.push(next);
|
|
12367
|
-
mkdirSync2(
|
|
12392
|
+
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
12368
12393
|
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
12369
12394
|
}
|
|
12370
12395
|
return all;
|
|
12371
12396
|
}
|
|
12372
12397
|
function removeWorkspace(dir, deps) {
|
|
12373
12398
|
const all = listWorkspaces(deps).filter((w) => w !== dir);
|
|
12374
|
-
mkdirSync2(
|
|
12399
|
+
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
12375
12400
|
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
12376
12401
|
return all;
|
|
12377
12402
|
}
|
|
@@ -12380,7 +12405,7 @@ function removeWorkspace(dir, deps) {
|
|
|
12380
12405
|
import { spawn } from "child_process";
|
|
12381
12406
|
import { existsSync as existsSync4 } from "fs";
|
|
12382
12407
|
import { homedir as homedir4 } from "os";
|
|
12383
|
-
import {
|
|
12408
|
+
import { resolve as resolve2, delimiter as delimiter2 } from "path";
|
|
12384
12409
|
|
|
12385
12410
|
// src/harness/acp.ts
|
|
12386
12411
|
var none = { events: [], writes: [] };
|
|
@@ -12613,7 +12638,7 @@ function startSession(opts) {
|
|
|
12613
12638
|
// PATH knows neither — live catch 2026-08-04: the first slot-wake ENOENT'd).
|
|
12614
12639
|
env: {
|
|
12615
12640
|
...process.env,
|
|
12616
|
-
PATH: [process.env.PATH,
|
|
12641
|
+
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
12617
12642
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {}
|
|
12618
12643
|
},
|
|
12619
12644
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -12876,7 +12901,12 @@ var UserAnswerSchema2 = external_exports.discriminatedUnion("kind", [
|
|
|
12876
12901
|
external_exports.object({ kind: external_exports.literal("ranked"), optionIds: external_exports.array(external_exports.string()), labels: external_exports.array(external_exports.string()).optional() }),
|
|
12877
12902
|
external_exports.object({ kind: external_exports.literal("clarify"), chunks: external_exports.array(external_exports.string()).min(1) }),
|
|
12878
12903
|
external_exports.object({ kind: external_exports.literal("confirm"), approved: external_exports.boolean() }),
|
|
12879
|
-
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema2).min(1) })
|
|
12904
|
+
external_exports.object({ kind: external_exports.literal("turns"), turns: external_exports.array(TurnSchema2).min(1) }),
|
|
12905
|
+
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
12906
|
+
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
12907
|
+
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
12908
|
+
* card the user can reply to — the broker never overrides the user. */
|
|
12909
|
+
external_exports.object({ kind: external_exports.literal("precedent"), answer: external_exports.string(), derivation: external_exports.string(), sources: external_exports.array(external_exports.string()).min(1) })
|
|
12880
12910
|
]);
|
|
12881
12911
|
var IntentSchema2 = external_exports.object({
|
|
12882
12912
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
@@ -12889,7 +12919,12 @@ var IntentSchema2 = external_exports.object({
|
|
|
12889
12919
|
* "after lunch") against the agent machine's clock — the user's — and attaches
|
|
12890
12920
|
* the seconds, ready to pass straight to schedule_callback. Absent when the
|
|
12891
12921
|
* detail didn't parse (the agent interprets it) or the kind isn't defer. */
|
|
12892
|
-
dueInSeconds: external_exports.number().int().positive().optional()
|
|
12922
|
+
dueInSeconds: external_exports.number().int().positive().optional(),
|
|
12923
|
+
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
12924
|
+
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
12925
|
+
* 'other' on every row. A table that records that something was wrong and nothing
|
|
12926
|
+
* about what cannot answer "is the bot looping less this week?". */
|
|
12927
|
+
fault: external_exports.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
12893
12928
|
});
|
|
12894
12929
|
var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
12895
12930
|
external_exports.object({
|
|
@@ -13059,6 +13094,11 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
13059
13094
|
claimId: external_exports.string().optional(),
|
|
13060
13095
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
13061
13096
|
voice: external_exports.string().optional(),
|
|
13097
|
+
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
13098
|
+
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
13099
|
+
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
13100
|
+
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
13101
|
+
agent: external_exports.string().optional(),
|
|
13062
13102
|
select: SelectShapeSchema2.optional(),
|
|
13063
13103
|
options: external_exports.array(OptionSchema2.omit({ id: true })).optional(),
|
|
13064
13104
|
/** The seat was satisfied by the call's own ACCOUNT (replan-design.md, user_info): the
|
|
@@ -13527,7 +13567,8 @@ async function drainInput(state, deps = {}) {
|
|
|
13527
13567
|
return [];
|
|
13528
13568
|
}
|
|
13529
13569
|
const ours = (parentId) => state.exclusive || parentId === state.parentId;
|
|
13530
|
-
const
|
|
13570
|
+
const opening = !!state.exclusive && !state.parentId;
|
|
13571
|
+
const claimable = (notificationId) => opening || state.resting === true || !state.sharedWithAgent || (state.mine?.has(notificationId) ?? false);
|
|
13531
13572
|
const mine = [
|
|
13532
13573
|
...work.replies.filter((r) => ours(r.parentId) && claimable(r.notificationId)).map((r) => ({ notificationId: r.notificationId, parentId: r.parentId, answer: r.answer })),
|
|
13533
13574
|
...work.requests.filter((r) => ours(r.parentId) && claimable(r.notificationId)).map((r) => ({ notificationId: r.notificationId, parentId: r.parentId, answer: { kind: "text", text: r.text } }))
|
|
@@ -13560,7 +13601,10 @@ async function pump(session, state, deps = {}) {
|
|
|
13560
13601
|
continue;
|
|
13561
13602
|
}
|
|
13562
13603
|
const words = spokenText(item.answer);
|
|
13563
|
-
if (words)
|
|
13604
|
+
if (words) {
|
|
13605
|
+
state.resting = false;
|
|
13606
|
+
session.send(words);
|
|
13607
|
+
}
|
|
13564
13608
|
}
|
|
13565
13609
|
const keepGoing = await deps.onIdle?.() ?? false;
|
|
13566
13610
|
if (!keepGoing) return;
|
|
@@ -13737,6 +13781,9 @@ function spokenText(answer) {
|
|
|
13737
13781
|
// A call log: the user's replies are the instruction, the bot's prompts are not.
|
|
13738
13782
|
case "turns":
|
|
13739
13783
|
return answer.turns.map((t) => t.reply).join(" ").trim() || null;
|
|
13784
|
+
// An auto-answer derived from the user's past decisions — reads like a fast human.
|
|
13785
|
+
case "precedent":
|
|
13786
|
+
return answer.answer;
|
|
13740
13787
|
case "ignored":
|
|
13741
13788
|
return null;
|
|
13742
13789
|
}
|
|
@@ -13778,6 +13825,7 @@ function runHarness(opts) {
|
|
|
13778
13825
|
state.parentId ??= parentId;
|
|
13779
13826
|
if (event.kind === "turn") opts.log(`${event.role}: ${event.text.split("\n")[0] ?? ""}`);
|
|
13780
13827
|
if (event.kind === "idle") {
|
|
13828
|
+
state.resting = true;
|
|
13781
13829
|
if (endsWithQuestion(event.result)) {
|
|
13782
13830
|
const local = await opts.localAsk?.question?.(event.result ?? "") ?? null;
|
|
13783
13831
|
if (local?.trim() && session && running) {
|
|
@@ -13854,6 +13902,10 @@ function startHost(opts) {
|
|
|
13854
13902
|
log: log2
|
|
13855
13903
|
});
|
|
13856
13904
|
runs.set(spec.sessionId, { run, label });
|
|
13905
|
+
try {
|
|
13906
|
+
saveToken({ access_token: spec.token, name: label, device: null }, `session:${spec.sessionId.slice(0, 8)}`);
|
|
13907
|
+
} catch {
|
|
13908
|
+
}
|
|
13857
13909
|
opts.log(`\u25B6 phone-launched ${label} (${spec.harness}) in ${spec.workspace}`);
|
|
13858
13910
|
}
|
|
13859
13911
|
}
|
|
@@ -13914,7 +13966,7 @@ function startHost(opts) {
|
|
|
13914
13966
|
}
|
|
13915
13967
|
|
|
13916
13968
|
// src/main.ts
|
|
13917
|
-
var here =
|
|
13969
|
+
var here = dirname4(fileURLToPath(import.meta.url));
|
|
13918
13970
|
var wsDeps = { file: workspacesFile() };
|
|
13919
13971
|
var DEVICE_SLOT = "Desktop";
|
|
13920
13972
|
var deviceToken = () => readToken(DEVICE_SLOT) || readToken();
|