@paigy/harness 0.3.34 → 0.3.36
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 +949 -444
- package/dist/main.js +876 -376
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -901,14 +901,14 @@ var require_regex = __commonJS({
|
|
|
901
901
|
var TEST_KANJI = new RegExp("^" + kanji + "$");
|
|
902
902
|
var TEST_NUMERIC = new RegExp("^" + numeric + "$");
|
|
903
903
|
var TEST_ALPHANUMERIC = new RegExp("^[A-Z0-9 $%*+\\-./:]+$");
|
|
904
|
-
exports.testKanji = function testKanji(
|
|
905
|
-
return TEST_KANJI.test(
|
|
904
|
+
exports.testKanji = function testKanji(str5) {
|
|
905
|
+
return TEST_KANJI.test(str5);
|
|
906
906
|
};
|
|
907
|
-
exports.testNumeric = function testNumeric(
|
|
908
|
-
return TEST_NUMERIC.test(
|
|
907
|
+
exports.testNumeric = function testNumeric(str5) {
|
|
908
|
+
return TEST_NUMERIC.test(str5);
|
|
909
909
|
};
|
|
910
|
-
exports.testAlphanumeric = function testAlphanumeric(
|
|
911
|
-
return TEST_ALPHANUMERIC.test(
|
|
910
|
+
exports.testAlphanumeric = function testAlphanumeric(str5) {
|
|
911
|
+
return TEST_ALPHANUMERIC.test(str5);
|
|
912
912
|
};
|
|
913
913
|
}
|
|
914
914
|
});
|
|
@@ -1410,13 +1410,13 @@ var require_segments = __commonJS({
|
|
|
1410
1410
|
var Regex = require_regex();
|
|
1411
1411
|
var Utils = require_utils();
|
|
1412
1412
|
var dijkstra = require_dijkstra();
|
|
1413
|
-
function getStringByteLength(
|
|
1414
|
-
return unescape(encodeURIComponent(
|
|
1413
|
+
function getStringByteLength(str5) {
|
|
1414
|
+
return unescape(encodeURIComponent(str5)).length;
|
|
1415
1415
|
}
|
|
1416
|
-
function getSegments(regex, mode,
|
|
1416
|
+
function getSegments(regex, mode, str5) {
|
|
1417
1417
|
const segments = [];
|
|
1418
1418
|
let result;
|
|
1419
|
-
while ((result = regex.exec(
|
|
1419
|
+
while ((result = regex.exec(str5)) !== null) {
|
|
1420
1420
|
segments.push({
|
|
1421
1421
|
data: result[0],
|
|
1422
1422
|
index: result.index,
|
|
@@ -1930,27 +1930,27 @@ var require_chunkstream = __commonJS({
|
|
|
1930
1930
|
this._buffers = null;
|
|
1931
1931
|
this.emit("close");
|
|
1932
1932
|
};
|
|
1933
|
-
ChunkStream.prototype._processReadAllowingLess = function(
|
|
1933
|
+
ChunkStream.prototype._processReadAllowingLess = function(read3) {
|
|
1934
1934
|
this._reads.shift();
|
|
1935
1935
|
let smallerBuf = this._buffers[0];
|
|
1936
|
-
if (smallerBuf.length >
|
|
1937
|
-
this._buffered -=
|
|
1938
|
-
this._buffers[0] = smallerBuf.slice(
|
|
1939
|
-
|
|
1936
|
+
if (smallerBuf.length > read3.length) {
|
|
1937
|
+
this._buffered -= read3.length;
|
|
1938
|
+
this._buffers[0] = smallerBuf.slice(read3.length);
|
|
1939
|
+
read3.func.call(this, smallerBuf.slice(0, read3.length));
|
|
1940
1940
|
} else {
|
|
1941
1941
|
this._buffered -= smallerBuf.length;
|
|
1942
1942
|
this._buffers.shift();
|
|
1943
|
-
|
|
1943
|
+
read3.func.call(this, smallerBuf);
|
|
1944
1944
|
}
|
|
1945
1945
|
};
|
|
1946
|
-
ChunkStream.prototype._processRead = function(
|
|
1946
|
+
ChunkStream.prototype._processRead = function(read3) {
|
|
1947
1947
|
this._reads.shift();
|
|
1948
1948
|
let pos = 0;
|
|
1949
1949
|
let count = 0;
|
|
1950
|
-
let data = Buffer.alloc(
|
|
1951
|
-
while (pos <
|
|
1950
|
+
let data = Buffer.alloc(read3.length);
|
|
1951
|
+
while (pos < read3.length) {
|
|
1952
1952
|
let buf = this._buffers[count++];
|
|
1953
|
-
let len = Math.min(buf.length,
|
|
1953
|
+
let len = Math.min(buf.length, read3.length - pos);
|
|
1954
1954
|
buf.copy(data, pos, 0, len);
|
|
1955
1955
|
pos += len;
|
|
1956
1956
|
if (len !== buf.length) {
|
|
@@ -1960,17 +1960,17 @@ var require_chunkstream = __commonJS({
|
|
|
1960
1960
|
if (count > 0) {
|
|
1961
1961
|
this._buffers.splice(0, count);
|
|
1962
1962
|
}
|
|
1963
|
-
this._buffered -=
|
|
1964
|
-
|
|
1963
|
+
this._buffered -= read3.length;
|
|
1964
|
+
read3.func.call(this, data);
|
|
1965
1965
|
};
|
|
1966
1966
|
ChunkStream.prototype._process = function() {
|
|
1967
1967
|
try {
|
|
1968
1968
|
while (this._buffered > 0 && this._reads && this._reads.length > 0) {
|
|
1969
|
-
let
|
|
1970
|
-
if (
|
|
1971
|
-
this._processReadAllowingLess(
|
|
1972
|
-
} else if (this._buffered >=
|
|
1973
|
-
this._processRead(
|
|
1969
|
+
let read3 = this._reads[0];
|
|
1970
|
+
if (read3.allowLess) {
|
|
1971
|
+
this._processReadAllowingLess(read3);
|
|
1972
|
+
} else if (this._buffered >= read3.length) {
|
|
1973
|
+
this._processRead(read3);
|
|
1974
1974
|
} else {
|
|
1975
1975
|
break;
|
|
1976
1976
|
}
|
|
@@ -3595,12 +3595,12 @@ var require_sync_reader = __commonJS({
|
|
|
3595
3595
|
};
|
|
3596
3596
|
SyncReader.prototype.process = function() {
|
|
3597
3597
|
while (this._reads.length > 0 && this._buffer.length) {
|
|
3598
|
-
let
|
|
3599
|
-
if (this._buffer.length && (this._buffer.length >=
|
|
3598
|
+
let read3 = this._reads[0];
|
|
3599
|
+
if (this._buffer.length && (this._buffer.length >= read3.length || read3.allowLess)) {
|
|
3600
3600
|
this._reads.shift();
|
|
3601
3601
|
let buf = this._buffer;
|
|
3602
|
-
this._buffer = buf.slice(
|
|
3603
|
-
|
|
3602
|
+
this._buffer = buf.slice(read3.length);
|
|
3603
|
+
read3.func.call(this, buf.slice(0, read3.length));
|
|
3604
3604
|
} else {
|
|
3605
3605
|
break;
|
|
3606
3606
|
}
|
|
@@ -4250,13 +4250,13 @@ var require_svg_tag = __commonJS({
|
|
|
4250
4250
|
var Utils = require_utils2();
|
|
4251
4251
|
function getColorAttrib(color, attrib) {
|
|
4252
4252
|
const alpha = color.a / 255;
|
|
4253
|
-
const
|
|
4254
|
-
return alpha < 1 ?
|
|
4253
|
+
const str5 = attrib + '="' + color.hex + '"';
|
|
4254
|
+
return alpha < 1 ? str5 + " " + attrib + '-opacity="' + alpha.toFixed(2).slice(1) + '"' : str5;
|
|
4255
4255
|
}
|
|
4256
4256
|
function svgCmd(cmd, x, y) {
|
|
4257
|
-
let
|
|
4258
|
-
if (typeof y !== "undefined")
|
|
4259
|
-
return
|
|
4257
|
+
let str5 = cmd + x;
|
|
4258
|
+
if (typeof y !== "undefined") str5 += " " + y;
|
|
4259
|
+
return str5;
|
|
4260
4260
|
}
|
|
4261
4261
|
function qrToPath(data, size, margin) {
|
|
4262
4262
|
let path = "";
|
|
@@ -4589,7 +4589,7 @@ import { createHash, randomUUID } from "crypto";
|
|
|
4589
4589
|
import { readFileSync } from "fs";
|
|
4590
4590
|
import { homedir } from "os";
|
|
4591
4591
|
import { join } from "path";
|
|
4592
|
-
import { randomUUID as
|
|
4592
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4593
4593
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
4594
4594
|
|
|
4595
4595
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
@@ -8634,11 +8634,9 @@ var coerce = {
|
|
|
8634
8634
|
var NEVER = INVALID;
|
|
8635
8635
|
|
|
8636
8636
|
// ../../packages/sdk/dist/index.js
|
|
8637
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
8638
8637
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
8639
8638
|
import { homedir as homedir2 } from "os";
|
|
8640
8639
|
import { join as join2 } from "path";
|
|
8641
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
8642
8640
|
var import_undici = __toESM(require_undici(), 1);
|
|
8643
8641
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
8644
8642
|
var CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "CODEX_HOME", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID", "PAIGY_ON_WAKE"];
|
|
@@ -9932,14 +9930,20 @@ var OptionSchema = external_exports.object({
|
|
|
9932
9930
|
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
9933
9931
|
// the constraints below are what an agent reads when deciding to use these.
|
|
9934
9932
|
html: external_exports.string().max(16384).describe(
|
|
9935
|
-
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use
|
|
9933
|
+
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use the image URL instead."
|
|
9936
9934
|
).optional(),
|
|
9937
9935
|
image: external_exports.string().url().describe(
|
|
9938
|
-
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use
|
|
9936
|
+
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use the HTML preview instead."
|
|
9939
9937
|
).optional()
|
|
9940
9938
|
});
|
|
9941
|
-
var OptionInputSchema =
|
|
9942
|
-
|
|
9939
|
+
var OptionInputSchema = external_exports.object({
|
|
9940
|
+
id: external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_.:-]+$/).optional().describe(
|
|
9941
|
+
`Optional: this option's own id, which answers name it by (selectedOptionIds). Give every option one, or none: without them the options are numbered "1", "2", \u2026 in order.`
|
|
9942
|
+
),
|
|
9943
|
+
label: external_exports.string().trim().min(1).max(1e3),
|
|
9944
|
+
hint: OptionSchema.shape.hint,
|
|
9945
|
+
htmlPreview: OptionSchema.shape.html,
|
|
9946
|
+
imgUrl: OptionSchema.shape.image
|
|
9943
9947
|
}).strict();
|
|
9944
9948
|
var NIGHT = { from: 23, to: 7 };
|
|
9945
9949
|
function draft2020(node) {
|
|
@@ -9971,33 +9975,162 @@ function mcpInputSchema(s) {
|
|
|
9971
9975
|
delete schema.$schema;
|
|
9972
9976
|
return draft2020(schema);
|
|
9973
9977
|
}
|
|
9974
|
-
var
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9978
|
+
var ASK_MAX = 250;
|
|
9979
|
+
var UNITS_MAX = 12;
|
|
9980
|
+
var UNIT_TITLE_MAX = 50;
|
|
9981
|
+
var UNIT_BODY_MAX = 200;
|
|
9982
|
+
var UnitInputSchema = external_exports.object({
|
|
9983
|
+
title: external_exports.string().trim().min(1).describe(`What this unit is about, at most ${UNIT_TITLE_MAX} characters. The app lists the titles; the person taps one to read its body.`),
|
|
9984
|
+
body: external_exports.string().trim().min(1).describe(`The unit itself, at most ${UNIT_BODY_MAX} characters.`)
|
|
9985
|
+
}).strict();
|
|
9986
|
+
function capRefusals(text, units22, field) {
|
|
9987
|
+
const out = [];
|
|
9988
|
+
const name = field === "text" ? "answer" : field;
|
|
9989
|
+
if (text.length > ASK_MAX) out.push({
|
|
9990
|
+
refusal: "ask_too_long",
|
|
9991
|
+
length: text.length,
|
|
9992
|
+
cap: ASK_MAX,
|
|
9993
|
+
path: field === "text" ? ["answer", "text"] : [field],
|
|
9994
|
+
message: `ask_too_long: the ${name} is ${text.length} characters; the cap is ${ASK_MAX}. Keep the one point in the ${name} and move the context into units.`
|
|
9995
|
+
});
|
|
9996
|
+
const all = units22 ?? [];
|
|
9997
|
+
if (all.length > UNITS_MAX) out.push({
|
|
9998
|
+
refusal: "too_many_units",
|
|
9999
|
+
length: all.length,
|
|
10000
|
+
cap: UNITS_MAX,
|
|
10001
|
+
path: ["units"],
|
|
10002
|
+
message: `too_many_units: ${all.length} units; the cap is ${UNITS_MAX}. Keep only the context this ${name} needs.`
|
|
10003
|
+
});
|
|
10004
|
+
all.forEach((unit, index) => {
|
|
10005
|
+
if (unit.title.length > UNIT_TITLE_MAX) out.push({
|
|
10006
|
+
refusal: "unit_title_too_long",
|
|
10007
|
+
index,
|
|
10008
|
+
length: unit.title.length,
|
|
10009
|
+
cap: UNIT_TITLE_MAX,
|
|
10010
|
+
path: ["units", index, "title"],
|
|
10011
|
+
message: `unit_title_too_long: units[${index}]'s title is ${unit.title.length} characters; the cap is ${UNIT_TITLE_MAX}.`
|
|
10012
|
+
});
|
|
10013
|
+
if (unit.body.length > UNIT_BODY_MAX) out.push({
|
|
10014
|
+
refusal: "unit_body_too_long",
|
|
10015
|
+
index,
|
|
10016
|
+
length: unit.body.length,
|
|
10017
|
+
cap: UNIT_BODY_MAX,
|
|
10018
|
+
path: ["units", index, "body"],
|
|
10019
|
+
message: `unit_body_too_long: units[${index}]'s body is ${unit.body.length} characters; the cap is ${UNIT_BODY_MAX}. Cut it into two units.`
|
|
10020
|
+
});
|
|
10021
|
+
});
|
|
10022
|
+
return out;
|
|
10023
|
+
}
|
|
10024
|
+
function refuseOverCaps(text, units22, field, ctx) {
|
|
10025
|
+
for (const { path, message, ...params } of capRefusals(text, units22, field)) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message, params });
|
|
10026
|
+
}
|
|
10027
|
+
var questionHandle = external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
|
|
10028
|
+
var NodeRef = external_exports.object({
|
|
10029
|
+
type: external_exports.enum(["goal", "question"]),
|
|
10030
|
+
id: external_exports.string().uuid()
|
|
10031
|
+
}).strict().describe("A Goal or a Question, by its id.");
|
|
10032
|
+
var BlockedAction = external_exports.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
|
|
10033
|
+
var reason = external_exports.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
|
|
10034
|
+
var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
|
|
10035
|
+
var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
|
|
10036
|
+
var userExplicitlyRequested = external_exports.enum(["any", "call"]).optional().describe(
|
|
10037
|
+
`Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
|
|
10038
|
+
);
|
|
10039
|
+
var units = external_exports.array(UnitInputSchema).optional().describe(
|
|
10040
|
+
`The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit. Leave it out when the main text says it all.`
|
|
10041
|
+
);
|
|
10042
|
+
var UpdateInputSchema = external_exports.object({
|
|
10043
|
+
message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
10044
|
+
goalIds: external_exports.array(external_exports.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
10045
|
+
units,
|
|
10046
|
+
userExplicitlyRequested
|
|
10047
|
+
}).strict().superRefine((u, ctx) => refuseOverCaps(u.message, u.units, "message", ctx));
|
|
10048
|
+
var questionFields = {
|
|
10049
|
+
question: external_exports.string().trim().min(1).describe(
|
|
10050
|
+
`ONE question, in at most ${ASK_MAX} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
9980
10051
|
),
|
|
9981
|
-
|
|
10052
|
+
goalId: external_exports.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
10053
|
+
units,
|
|
10054
|
+
options: external_exports.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own."),
|
|
9982
10055
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
9983
|
-
// brain_prompts.md §1: structured agent questions need no model).
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
10056
|
+
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
10057
|
+
pickMode: external_exports.enum(["one", "many", "rank"]).optional().describe(
|
|
10058
|
+
'How the options are answered: "one" (the default) a pick, for alternatives; "many" a checklist, for independent options they may want several of; "rank" an order of all of them. Only with options.'
|
|
10059
|
+
),
|
|
10060
|
+
// WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
|
|
10061
|
+
// blocks work asks for a call (the person's settings and ring cooldowns still decide).
|
|
10062
|
+
workItBlocks: external_exports.array(external_exports.object({ blocked: NodeRef, action: BlockedAction, reason: reason.optional() }).strict().refine(actionFits, ACTION_FITS)).min(1).max(10).optional().describe(
|
|
10063
|
+
"The work that cannot go on until this is answered: a Goal's start or completion, or another Question's answer. Only for work truly blocked; it asks for a call. Leave it out for a question you can keep working around."
|
|
10064
|
+
),
|
|
10065
|
+
userExplicitlyRequested
|
|
10066
|
+
};
|
|
10067
|
+
var refineQuestion = (q, ctx) => {
|
|
10068
|
+
refuseOverCaps(q.question, q.units, "question", ctx);
|
|
10069
|
+
if (q.pickMode && !q.options) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
|
|
10070
|
+
const ids = (q.options ?? []).map((o) => o.id);
|
|
10071
|
+
if (ids.some((id22) => id22) && (ids.some((id22) => !id22) || new Set(ids).size !== ids.length)) {
|
|
10072
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
|
|
10073
|
+
}
|
|
10074
|
+
};
|
|
10075
|
+
var QuestionInputSchema = external_exports.object({
|
|
10076
|
+
questionId: external_exports.string().uuid().optional().describe("Optional: your own stable id for this question (a UUID), so a resend is the same question. Minted for you when omitted; the result returns it."),
|
|
10077
|
+
...questionFields
|
|
10078
|
+
}).strict().superRefine(refineQuestion);
|
|
10079
|
+
var QuestionWireSchema = external_exports.object({ questionId: external_exports.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
|
|
10080
|
+
var AnswerInputSchema = external_exports.object({
|
|
10081
|
+
questionId: questionHandle.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
|
|
10082
|
+
answer: external_exports.object({
|
|
10083
|
+
text: external_exports.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
10084
|
+
selectedOptionIds: external_exports.array(external_exports.string().trim().min(1)).min(1).max(6).optional().describe("The ids of the options you choose, when the Question offered options (in order, for a ranking).")
|
|
10085
|
+
}).strict(),
|
|
10086
|
+
units
|
|
10087
|
+
}).strict().superRefine((a, ctx) => refuseOverCaps(a.answer.text, a.units, "text", ctx));
|
|
10088
|
+
var QuestionDependencySchema = external_exports.object({
|
|
10089
|
+
questionId: questionHandle.describe("A Question you asked."),
|
|
10090
|
+
change: external_exports.enum(["add", "remove"]),
|
|
10091
|
+
blocked: NodeRef,
|
|
10092
|
+
action: BlockedAction,
|
|
10093
|
+
reason: reason.optional()
|
|
10094
|
+
}).strict().refine(actionFits, ACTION_FITS);
|
|
10095
|
+
var sendFields = {
|
|
10096
|
+
updates: external_exports.array(UpdateInputSchema).min(1).max(20).optional().describe(
|
|
10097
|
+
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
|
|
9988
10098
|
),
|
|
9989
|
-
answers: external_exports.
|
|
9990
|
-
|
|
10099
|
+
answers: external_exports.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
|
|
10100
|
+
withdrawQuestionIds: external_exports.array(questionHandle).min(1).max(10).optional().describe(
|
|
10101
|
+
"Questions you asked that no longer need an answer: you acted on them yourself, they no longer matter, or you asked wrongly. Each is cancelled, not answered: its card closes and nothing rings for it."
|
|
10102
|
+
),
|
|
10103
|
+
questionDependencies: external_exports.array(QuestionDependencySchema).min(1).max(20).optional().describe(
|
|
10104
|
+
"Add or remove what a Question you asked holds up, after asking it."
|
|
10105
|
+
)
|
|
10106
|
+
};
|
|
10107
|
+
var ContactSchema = external_exports.object({
|
|
10108
|
+
...sendFields,
|
|
10109
|
+
questions: external_exports.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
|
|
10110
|
+
/** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
|
|
10111
|
+
ackEventIds: external_exports.array(external_exports.string().uuid()).min(1).max(100).optional().describe(
|
|
10112
|
+
"The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (answers), never acknowledged away."
|
|
10113
|
+
),
|
|
10114
|
+
wait: external_exports.boolean().optional().describe(
|
|
10115
|
+
"Only controls when this returns. Sending: false (the default) returns at once; true waits up to about 45 seconds for a response to what you sent. Sending nothing: true (the default) waits up to about 45 seconds for anything addressed to you; false returns what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived; wait again without sending again), or `not_waited`."
|
|
9991
10116
|
)
|
|
9992
10117
|
}).strict();
|
|
9993
|
-
var
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
10118
|
+
var sendsAnything = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
10119
|
+
var ContactWireSchema = external_exports.object({
|
|
10120
|
+
operationId: external_exports.string().uuid(),
|
|
10121
|
+
...sendFields,
|
|
10122
|
+
questions: external_exports.array(QuestionWireSchema).min(1).max(20).optional()
|
|
10123
|
+
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
9999
10124
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
10000
|
-
var CONTACT_DESCRIPTION =
|
|
10125
|
+
var CONTACT_DESCRIPTION = `Send to and receive from the person: updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read \`results\`, where a failed item names why while the rest still apply.
|
|
10126
|
+
|
|
10127
|
+
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
10128
|
+
|
|
10129
|
+
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
10130
|
+
|
|
10131
|
+
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
10132
|
+
|
|
10133
|
+
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
10001
10134
|
var CreateGoalSchema = external_exports.object({
|
|
10002
10135
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
10003
10136
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -10017,10 +10150,6 @@ var CreateGoalSchema = external_exports.object({
|
|
|
10017
10150
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
10018
10151
|
repo: external_exports.string().trim().min(1).max(200).optional()
|
|
10019
10152
|
});
|
|
10020
|
-
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
10021
|
-
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
10022
|
-
}).strict();
|
|
10023
|
-
var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
|
|
10024
10153
|
var UpdateGoalSchema = external_exports.object({
|
|
10025
10154
|
revision: external_exports.number().int().positive(),
|
|
10026
10155
|
changes: external_exports.object({
|
|
@@ -10035,17 +10164,64 @@ var UpdateGoalSchema = external_exports.object({
|
|
|
10035
10164
|
state: external_exports.enum(["active", "done", "cancelled"]).optional(),
|
|
10036
10165
|
progress: external_exports.string().trim().min(1).max(1e4).optional(),
|
|
10037
10166
|
reviewed: external_exports.literal(true).optional(),
|
|
10038
|
-
dueAt: external_exports.string().datetime({ offset: true }).nullable().optional()
|
|
10039
|
-
/** WITHDRAW YOUR OWN QUESTION (#2777, owner 2026-09-30). The question's id as every read shows it
|
|
10040
|
-
* (the conversation's `id`, eight characters, or the whole request Entry id), on THIS Goal, asked
|
|
10041
|
-
* by you and still open. It is cancelled, not answered: its cards close and nothing rings for it. */
|
|
10042
|
-
withdraw: external_exports.array(external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/)).min(1).max(10).optional()
|
|
10167
|
+
dueAt: external_exports.string().datetime({ offset: true }).nullable().optional()
|
|
10043
10168
|
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
10044
10169
|
reason: external_exports.string().trim().min(1).max(2e3),
|
|
10045
10170
|
operationId: external_exports.string().uuid().optional()
|
|
10046
10171
|
}).strict();
|
|
10047
|
-
var
|
|
10048
|
-
var
|
|
10172
|
+
var changedGoal = external_exports.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
10173
|
+
var goalTitle = external_exports.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
10174
|
+
var goalOutcome = external_exports.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
|
|
10175
|
+
var CreateChange = external_exports.object({
|
|
10176
|
+
kind: external_exports.literal("create"),
|
|
10177
|
+
title: goalTitle,
|
|
10178
|
+
outcome: goalOutcome,
|
|
10179
|
+
ownerId: external_exports.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
|
|
10180
|
+
parentGoalId: external_exports.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
10181
|
+
sourceEntryIds: external_exports.array(external_exports.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
10182
|
+
}).strict();
|
|
10183
|
+
var EditChange = external_exports.object({ kind: external_exports.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
|
|
10184
|
+
var StateChange = external_exports.object({ kind: external_exports.literal("state"), goalId: changedGoal, state: external_exports.enum(["open", "completed", "canceled"]).describe(
|
|
10185
|
+
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
10186
|
+
) }).strict();
|
|
10187
|
+
var AssignChange = external_exports.object({ kind: external_exports.literal("assign"), goalId: changedGoal, ownerId: external_exports.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
10188
|
+
var DeferChange = external_exports.object({ kind: external_exports.literal("defer"), goalId: changedGoal, until: external_exports.string().datetime({ offset: true }).nullable().describe(
|
|
10189
|
+
"Postpone it until this instant: it waits until then, and its owner is woken when it passes (a promise to follow up later). null takes the postponement back."
|
|
10190
|
+
) }).strict();
|
|
10191
|
+
var MoveChange = external_exports.object({ kind: external_exports.literal("move"), goalId: changedGoal, parentGoalId: external_exports.string().uuid().nullable().describe(
|
|
10192
|
+
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
10193
|
+
) }).strict();
|
|
10194
|
+
var DependencyChange = external_exports.object({
|
|
10195
|
+
kind: external_exports.literal("dependency"),
|
|
10196
|
+
change: external_exports.enum(["add", "remove"]),
|
|
10197
|
+
goalId: changedGoal,
|
|
10198
|
+
dependsOnGoalId: external_exports.string().uuid().describe("The Goal it waits on."),
|
|
10199
|
+
action: external_exports.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
10200
|
+
reason: external_exports.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
|
|
10201
|
+
}).strict();
|
|
10202
|
+
var read = { revision: external_exports.number().int().positive().optional() };
|
|
10203
|
+
var GoalChangeSchema = external_exports.discriminatedUnion("kind", [
|
|
10204
|
+
CreateChange,
|
|
10205
|
+
EditChange.extend(read),
|
|
10206
|
+
StateChange.extend(read),
|
|
10207
|
+
AssignChange.extend(read),
|
|
10208
|
+
DeferChange.extend(read),
|
|
10209
|
+
MoveChange.extend(read),
|
|
10210
|
+
DependencyChange
|
|
10211
|
+
]);
|
|
10212
|
+
var editNames = (changes, ctx) => changes.forEach((c, i) => {
|
|
10213
|
+
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
10214
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
10215
|
+
}
|
|
10216
|
+
});
|
|
10217
|
+
var ManageGoalsSchema = external_exports.object({
|
|
10218
|
+
requestId: external_exports.string().uuid().optional(),
|
|
10219
|
+
changes: external_exports.array(GoalChangeSchema).min(1).max(50)
|
|
10220
|
+
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
10221
|
+
var ManageGoalsToolSchema = external_exports.object({
|
|
10222
|
+
changes: external_exports.array(external_exports.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
10223
|
+
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
10224
|
+
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
10049
10225
|
var GetGoalSchema = external_exports.object({
|
|
10050
10226
|
goalId: external_exports.string().uuid(),
|
|
10051
10227
|
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
@@ -10056,19 +10232,29 @@ var GetGoalSchema = external_exports.object({
|
|
|
10056
10232
|
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
10057
10233
|
diagnose: external_exports.boolean().optional()
|
|
10058
10234
|
}).strict();
|
|
10059
|
-
var GET_GOAL_DESCRIPTION = "Read one Goal
|
|
10060
|
-
var
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10235
|
+
var GET_GOAL_DESCRIPTION = "Read one Goal (a read never puts you on it), including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
10236
|
+
var SearchToolSchema = external_exports.object({
|
|
10237
|
+
query: external_exports.string().trim().min(1).max(500),
|
|
10238
|
+
types: external_exports.array(external_exports.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
10239
|
+
goalId: external_exports.string().uuid().optional(),
|
|
10240
|
+
limit: external_exports.number().int().min(1).max(20).optional()
|
|
10241
|
+
}).strict();
|
|
10242
|
+
var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
10243
|
+
var CheckActivitySchema = external_exports.object({}).strict();
|
|
10244
|
+
var FEEDBACK_TEXT_MAX = 5e4;
|
|
10245
|
+
var SendFeedbackSchema = external_exports.object({
|
|
10246
|
+
text: external_exports.string().trim().min(1).max(FEEDBACK_TEXT_MAX).describe(`The report, in markdown: what happened, what was expected, how to reproduce it, versions, evidence. At most ${FEEDBACK_TEXT_MAX} characters.`),
|
|
10247
|
+
title: external_exports.string().trim().min(1).max(200).optional().describe("One line naming it."),
|
|
10248
|
+
kind: external_exports.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
|
|
10249
|
+
}).strict();
|
|
10250
|
+
var SEND_FEEDBACK_DESCRIPTION = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
|
|
10064
10251
|
var AGENT_TOOLS = [
|
|
10065
|
-
{ name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckRepliesSchema) },
|
|
10066
10252
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
10067
|
-
{ name: "
|
|
10068
|
-
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
10069
|
-
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
10253
|
+
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
10070
10254
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
10071
|
-
{ name: "
|
|
10255
|
+
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
10256
|
+
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
|
|
10257
|
+
{ name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) }
|
|
10072
10258
|
];
|
|
10073
10259
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
10074
10260
|
var id = external_exports.string().uuid();
|
|
@@ -10099,17 +10285,6 @@ var existingGoalChanges = [
|
|
|
10099
10285
|
reason: external_exports.string().max(500).optional()
|
|
10100
10286
|
}).strict()
|
|
10101
10287
|
];
|
|
10102
|
-
var GoalChangeSchema = external_exports.union([
|
|
10103
|
-
external_exports.object({
|
|
10104
|
-
kind: external_exports.literal("create"),
|
|
10105
|
-
title: external_exports.string().min(1).max(120),
|
|
10106
|
-
outcome: external_exports.string().min(1),
|
|
10107
|
-
ownerId: participant,
|
|
10108
|
-
parentGoalId: id.optional(),
|
|
10109
|
-
sourceEntryIds: external_exports.array(id)
|
|
10110
|
-
}).strict(),
|
|
10111
|
-
...existingGoalChanges
|
|
10112
|
-
]);
|
|
10113
10288
|
var BrainGoalChangeSchema = external_exports.union([
|
|
10114
10289
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
10115
10290
|
external_exports.object({
|
|
@@ -10218,13 +10393,83 @@ var CompactResultSchema = external_exports.object({
|
|
|
10218
10393
|
var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
10219
10394
|
zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
|
|
10220
10395
|
);
|
|
10396
|
+
var str2 = external_exports.string();
|
|
10397
|
+
var strs2 = external_exports.array(external_exports.string());
|
|
10398
|
+
var TalkerReplySchema = external_exports.object({
|
|
10399
|
+
/** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
|
|
10400
|
+
* or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
|
|
10401
|
+
* answered before ("" for none). */
|
|
10402
|
+
messages: external_exports.array(external_exports.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
|
|
10403
|
+
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
10404
|
+
then: external_exports.enum(["listen", "hold", "end"]),
|
|
10405
|
+
/** Items the person's words settle: the item's handle, the chosen option IDs, the line handles. */
|
|
10406
|
+
answers: external_exports.array(external_exports.object({ item: str2, options: strs2, lines: strs2 }).strict()),
|
|
10407
|
+
/** The line handles that hold a new instruction from the person. */
|
|
10408
|
+
instruction: strs2,
|
|
10409
|
+
/** The line handles that ask for new work, a change to existing work, or a standing rule: the filer
|
|
10410
|
+
* reads only these (3e). */
|
|
10411
|
+
file: strs2,
|
|
10412
|
+
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
10413
|
+
need: external_exports.object({ kind: external_exports.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
|
|
10414
|
+
/** The handle of the item to explain ("" for none). */
|
|
10415
|
+
explain: str2
|
|
10416
|
+
}).strict();
|
|
10417
|
+
var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
10418
|
+
zodToJsonSchema(TalkerReplySchema, { $refStrategy: "none" })
|
|
10419
|
+
);
|
|
10420
|
+
var str3 = external_exports.string();
|
|
10421
|
+
var strs3 = external_exports.array(external_exports.string());
|
|
10422
|
+
var FilerReplySchema = external_exports.object({
|
|
10423
|
+
/** Which Goals each new line goes on: a line's handle and Goal handles (or keys of new Goals). */
|
|
10424
|
+
filed: external_exports.array(external_exports.object({ line: str3, goals: strs3 }).strict()),
|
|
10425
|
+
goals: external_exports.array(external_exports.object({
|
|
10426
|
+
op: external_exports.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
10427
|
+
key: str3,
|
|
10428
|
+
goal: str3,
|
|
10429
|
+
title: str3,
|
|
10430
|
+
outcome: str3,
|
|
10431
|
+
owner: str3,
|
|
10432
|
+
under: str3,
|
|
10433
|
+
until: str3,
|
|
10434
|
+
on: str3,
|
|
10435
|
+
gate: external_exports.enum(["start", "complete", "answer", ""]),
|
|
10436
|
+
why: str3
|
|
10437
|
+
}).strict()),
|
|
10438
|
+
questions: external_exports.array(external_exports.object({
|
|
10439
|
+
op: external_exports.enum(["create", "edit", "assign", "withdraw"]),
|
|
10440
|
+
key: str3,
|
|
10441
|
+
question: str3,
|
|
10442
|
+
text: str3,
|
|
10443
|
+
to: str3,
|
|
10444
|
+
goal: str3,
|
|
10445
|
+
options: external_exports.array(external_exports.object({ id: str3, label: str3 }).strict()),
|
|
10446
|
+
pick: external_exports.enum(["one", "many", "rank", "words", ""]),
|
|
10447
|
+
blocks: str3
|
|
10448
|
+
}).strict()),
|
|
10449
|
+
lessons: external_exports.array(external_exports.object({ op: external_exports.enum(["remember", "revise", "withdraw"]), lesson: str3, text: str3, scope: str3 }).strict()),
|
|
10450
|
+
/** Line handles whose words comment on Paigy itself. */
|
|
10451
|
+
feedback: strs3
|
|
10452
|
+
}).strict();
|
|
10453
|
+
var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
10454
|
+
zodToJsonSchema(FilerReplySchema, { $refStrategy: "none" })
|
|
10455
|
+
);
|
|
10456
|
+
function entryUnit(entry) {
|
|
10457
|
+
if (entry.kind !== "contribution" || !entry.aboutId) return null;
|
|
10458
|
+
const plain = entry.content && "plain" in entry.content ? entry.content.plain : null;
|
|
10459
|
+
if (!plain || typeof plain !== "object") return null;
|
|
10460
|
+
const { title, text } = plain;
|
|
10461
|
+
return typeof title === "string" && typeof text === "string" ? { title, body: text } : null;
|
|
10462
|
+
}
|
|
10221
10463
|
var LIVE_MS = 3 * 6e4;
|
|
10222
10464
|
var WORKING_MS = 60 * 6e4;
|
|
10223
10465
|
var ContextSchema = external_exports.object({
|
|
10224
10466
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
10225
10467
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
10226
10468
|
"Semantic chunks of detail (each a standalone, non-empty piece). The user can select chunks to ask you to expand. MAY BE EMPTY: a claim whose whole content is its heading \u2014 a single sentence \u2014 has no body, and saying so beats repeating the heading underneath itself. That repeat is what `min(1)` used to force, at 2x the storage, with every reader subtracting it back out at render time."
|
|
10227
|
-
)
|
|
10469
|
+
),
|
|
10470
|
+
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
10471
|
+
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
10472
|
+
units: external_exports.array(external_exports.object({ title: external_exports.string(), body: external_exports.string() })).optional()
|
|
10228
10473
|
});
|
|
10229
10474
|
var ParticipantSchema = external_exports.object({
|
|
10230
10475
|
kind: external_exports.enum(["human", "agent"]),
|
|
@@ -10299,7 +10544,7 @@ var NotifyRequestFields = external_exports.object({
|
|
|
10299
10544
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
10300
10545
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
10301
10546
|
context: ContextSchema.optional(),
|
|
10302
|
-
options: external_exports.array(
|
|
10547
|
+
options: external_exports.array(OptionSchema.omit({ id: true }).extend({ label: external_exports.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
10303
10548
|
"The choices, in order \u2014 required when select is 'one'/'many'/'rank', omitted otherwise. Ids are assigned automatically by position ('1', '2', \u2026); the user's answer references them as optionId(s)."
|
|
10304
10549
|
),
|
|
10305
10550
|
points: external_exports.array(external_exports.string().min(1)).optional().describe(
|
|
@@ -10493,7 +10738,7 @@ var AwaitItemSchema = external_exports.discriminatedUnion("type", [
|
|
|
10493
10738
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
10494
10739
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
10495
10740
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
10496
|
-
* re-orient via
|
|
10741
|
+
* re-orient via contact({})). */
|
|
10497
10742
|
external_exports.object({
|
|
10498
10743
|
type: external_exports.literal("superseded"),
|
|
10499
10744
|
parentId: external_exports.string(),
|
|
@@ -11060,8 +11305,8 @@ var QueueItemSchema = external_exports.object({
|
|
|
11060
11305
|
repo: external_exports.string().nullable().optional(),
|
|
11061
11306
|
createdAt: external_exports.string(),
|
|
11062
11307
|
updatedAt: external_exports.string().nullable().default(null),
|
|
11063
|
-
/** When its owner last SAID something about it (`
|
|
11064
|
-
*
|
|
11308
|
+
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
11309
|
+
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
11065
11310
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
11066
11311
|
lastProgressAt: external_exports.string().nullable().optional(),
|
|
11067
11312
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
@@ -11219,11 +11464,11 @@ var HostDecisionSchema = external_exports.object({
|
|
|
11219
11464
|
/** The agent's token id: the row's `recipient`. */
|
|
11220
11465
|
agent: external_exports.string().uuid(),
|
|
11221
11466
|
decision: external_exports.enum(["stood_back", "took_over"]),
|
|
11222
|
-
/** The work it was about: the Goal
|
|
11467
|
+
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
11223
11468
|
goalId: external_exports.string().uuid().nullable().optional(),
|
|
11224
11469
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
11225
11470
|
seenAt: external_exports.string().datetime().nullable().optional(),
|
|
11226
|
-
/** When that work last moved (`claimable.since` on `
|
|
11471
|
+
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
11227
11472
|
since: external_exports.string().datetime().nullable().optional(),
|
|
11228
11473
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
11229
11474
|
said: external_exports.string().max(300).optional()
|
|
@@ -11566,39 +11811,26 @@ var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
|
11566
11811
|
async function fail(what, res) {
|
|
11567
11812
|
throw new ApiError(what, res.status, await res.text());
|
|
11568
11813
|
}
|
|
11569
|
-
async function
|
|
11814
|
+
async function manageGoals(input, opts = {}) {
|
|
11570
11815
|
const token = authToken(opts.token) ?? "";
|
|
11571
|
-
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
|
|
11816
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/manage`, {
|
|
11572
11817
|
method: "POST",
|
|
11573
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}
|
|
11818
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
11574
11819
|
body: JSON.stringify(input)
|
|
11575
11820
|
}));
|
|
11576
|
-
if (!res.ok) await fail("
|
|
11821
|
+
if (!res.ok) await fail("manage_goals", res);
|
|
11577
11822
|
return await res.json();
|
|
11578
11823
|
}
|
|
11579
|
-
async function
|
|
11824
|
+
async function getGoal(goalId, opts = {}, read3 = {}) {
|
|
11580
11825
|
const token = authToken(opts.token) ?? "";
|
|
11581
|
-
const
|
|
11582
|
-
if (!res.ok) await fail("claim_goal", res);
|
|
11583
|
-
return await res.json();
|
|
11584
|
-
}
|
|
11585
|
-
async function getGoal(goalId, opts = {}, read = {}) {
|
|
11586
|
-
const token = authToken(opts.token) ?? "";
|
|
11587
|
-
const query = [read.history ? "history=1" : "", read.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
|
|
11826
|
+
const query = [read3.history ? "history=1" : "", read3.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
|
|
11588
11827
|
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${query ? `?${query}` : ""}`, {
|
|
11589
11828
|
method: "GET",
|
|
11590
|
-
headers: { authorization: `Bearer ${token}
|
|
11829
|
+
headers: { authorization: `Bearer ${token}` }
|
|
11591
11830
|
}));
|
|
11592
11831
|
if (!res.ok) await fail("get_goal", res);
|
|
11593
11832
|
return await res.json();
|
|
11594
11833
|
}
|
|
11595
|
-
async function updateGoal(goalId, input, opts = {}) {
|
|
11596
|
-
const token = authToken(opts.token) ?? "";
|
|
11597
|
-
const operationId = input.operationId ?? randomUUID2();
|
|
11598
|
-
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
|
|
11599
|
-
if (!res.ok) await fail("update_goal", res);
|
|
11600
|
-
return await res.json();
|
|
11601
|
-
}
|
|
11602
11834
|
var AWAIT_WINDOW_MS = 45e3;
|
|
11603
11835
|
async function whoAmI(opts = {}) {
|
|
11604
11836
|
try {
|
|
@@ -11674,7 +11906,7 @@ async function listNotes(opts = {}) {
|
|
|
11674
11906
|
}
|
|
11675
11907
|
async function listConnections(opts = {}) {
|
|
11676
11908
|
const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
|
|
11677
|
-
headers: { authorization: `Bearer ${authToken(opts.token)}
|
|
11909
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
11678
11910
|
}));
|
|
11679
11911
|
if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
|
|
11680
11912
|
return await res.json();
|
|
@@ -11712,121 +11944,99 @@ async function dismissTriage(proposalId, opts = {}) {
|
|
|
11712
11944
|
if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
|
|
11713
11945
|
return await res.json();
|
|
11714
11946
|
}
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
const
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
var asked;
|
|
11726
|
-
function currentRepo(cwd = process.cwd()) {
|
|
11727
|
-
if (asked !== void 0) return asked;
|
|
11728
|
-
try {
|
|
11729
|
-
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
11730
|
-
cwd,
|
|
11731
|
-
encoding: "utf8",
|
|
11732
|
-
timeout: 2e3,
|
|
11733
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
11734
|
-
});
|
|
11735
|
-
asked = repoFromRemote(url);
|
|
11736
|
-
} catch {
|
|
11737
|
-
asked = null;
|
|
11738
|
-
}
|
|
11739
|
-
return asked;
|
|
11947
|
+
var NOT_SENT = `Recorded as the Goal's progress, not sent: nobody was notified. The person is told only a question, an answer to one they asked you, and what you say once the Goal is done. To close the work, mark the Goal done first, then send the one update saying what is done and anything they need to do or check. If the person asked you, in so many words, to send them this, send it again with userExplicitlyRequested: "any" on that update.`;
|
|
11948
|
+
var APPLIED = "Applied; nothing was sent to the person.";
|
|
11949
|
+
var DEMOTED = "Delivered as a card, not a call: the person's settings do not allow a call right now (silent, quiet hours, or a mode). It has reached them; do not send it again.";
|
|
11950
|
+
async function readDelivery(deliveryId, opts = {}) {
|
|
11951
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, {
|
|
11952
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` },
|
|
11953
|
+
signal: opts.signal
|
|
11954
|
+
}));
|
|
11955
|
+
if (!res.ok) await fail("read_delivery", res);
|
|
11956
|
+
return await res.json();
|
|
11740
11957
|
}
|
|
11741
|
-
var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
|
|
11742
|
-
var DEMOTED = "Delivered as a card, not a call: the person's settings do not allow a call right now (silent, quiet hours, or a mode). It has reached them; do not send it again, with or without a Goal.";
|
|
11743
11958
|
async function contact(input, opts = {}) {
|
|
11744
11959
|
const parsed = ContactSchema.parse(input);
|
|
11960
|
+
if (!sendsAnything(parsed)) throw new Error("contact with nothing to send receives: call receiveContact");
|
|
11745
11961
|
opts.signal?.throwIfAborted();
|
|
11746
11962
|
const send22 = opts.reach ?? reach;
|
|
11747
|
-
const
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
})
|
|
11778
|
-
}));
|
|
11779
|
-
if (!res.ok) await fail("batch contact", res);
|
|
11780
|
-
const receipt = await res.json();
|
|
11781
|
-
deliveries = receipt.deliveries ?? [];
|
|
11782
|
-
if (parsed.channel === "call" && receipt.channel && receipt.channel !== "call") demoted = DEMOTED;
|
|
11783
|
-
recorded = receipt.recorded ?? [];
|
|
11784
|
-
if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
|
|
11785
|
-
if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
|
|
11786
|
-
deliveryId = deliveries[0]?.deliveryId ?? "";
|
|
11787
|
-
if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
|
|
11788
|
-
}
|
|
11789
|
-
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id22, deliveryId: deliveryId2, goalId }) => ({ ...id22 ? { id: id22 } : {}, deliveryId: deliveryId2, goalId }));
|
|
11790
|
-
const joinedCard = deliveries[0]?.joinedCard === true;
|
|
11791
|
-
const joinedCall = deliveries[0]?.joinedCall === true;
|
|
11792
|
-
const read = async (signal2) => {
|
|
11793
|
-
const res = await ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
11794
|
-
if (!res.ok) await fail("read_delivery", res);
|
|
11795
|
-
const one = await res.json();
|
|
11963
|
+
const acknowledged = parsed.ackEventIds?.length ? await acknowledge(parsed.ackEventIds, opts) : void 0;
|
|
11964
|
+
const questions = parsed.questions?.map((q) => ({ ...q, questionId: q.questionId ?? randomUUID2() }));
|
|
11965
|
+
const res = await ensureAuthed(await send22(`${BACKEND_URL}/api/goals/contact`, {
|
|
11966
|
+
method: "POST",
|
|
11967
|
+
signal: opts.signal,
|
|
11968
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
|
|
11969
|
+
body: JSON.stringify({
|
|
11970
|
+
operationId: opts.operationId ?? randomUUID2(),
|
|
11971
|
+
...parsed.updates ? { updates: parsed.updates } : {},
|
|
11972
|
+
...questions ? { questions } : {},
|
|
11973
|
+
...parsed.answers ? { answers: parsed.answers } : {},
|
|
11974
|
+
...parsed.withdrawQuestionIds ? { withdrawQuestionIds: parsed.withdrawQuestionIds } : {},
|
|
11975
|
+
...parsed.questionDependencies ? { questionDependencies: parsed.questionDependencies } : {}
|
|
11976
|
+
})
|
|
11977
|
+
}));
|
|
11978
|
+
if (!res.ok) await fail("contact", res);
|
|
11979
|
+
const receipt = await res.json();
|
|
11980
|
+
const results = receipt.results ?? [];
|
|
11981
|
+
const ok = receipt.ok ?? results.every((r) => r.status !== "failed");
|
|
11982
|
+
const deliveries = receipt.deliveries ?? [];
|
|
11983
|
+
const recorded = results.filter((r) => r.status === "recorded" && r.goalId).map((r) => ({ goalId: r.goalId }));
|
|
11984
|
+
const askedCall = [...parsed.updates ?? [], ...questions ?? []].some((m) => m.userExplicitlyRequested === "call") || !!questions?.some((q) => q.workItBlocks?.length);
|
|
11985
|
+
const demoted = askedCall && receipt.channel && receipt.channel !== "call" ? DEMOTED : void 0;
|
|
11986
|
+
const acked = acknowledged ? { acknowledged } : {};
|
|
11987
|
+
if (!deliveries.length) {
|
|
11988
|
+
return { sent: false, ok, results, ...recorded.length ? { recorded } : {}, message: recorded.length ? NOT_SENT : APPLIED, waitOutcome: "not_waited", ...acked };
|
|
11989
|
+
}
|
|
11990
|
+
const lead = deliveries.find((d) => d.kind === "question") ?? deliveries[0];
|
|
11991
|
+
const read3 = async (signal2) => {
|
|
11992
|
+
const one = await readDelivery(lead.deliveryId, { ...opts, signal: signal2 });
|
|
11796
11993
|
return {
|
|
11797
11994
|
...one,
|
|
11798
|
-
|
|
11995
|
+
ok,
|
|
11996
|
+
results,
|
|
11997
|
+
...deliveries.length > 1 ? { deliveries: deliveries.map(({ deliveryId, goalId, kind, questionId }) => ({ deliveryId, goalId, kind, ...questionId ? { questionId } : {} })) } : {},
|
|
11799
11998
|
...recorded.length ? { recorded, notSent: NOT_SENT } : {},
|
|
11800
|
-
...joinedCard ? { joinedCard: true } : {},
|
|
11801
|
-
|
|
11802
|
-
...
|
|
11999
|
+
...lead.joinedCard ? { joinedCard: true } : {},
|
|
12000
|
+
// IT WENT ONTO A CALL ALREADY HAPPENING (owner, 2026-09-28): the view words it as that call.
|
|
12001
|
+
...lead.joinedCall ? { joinedCall: true } : {},
|
|
12002
|
+
...demoted ? { demoted } : {},
|
|
12003
|
+
...acked
|
|
11803
12004
|
};
|
|
11804
12005
|
};
|
|
11805
|
-
const settled = (d) => d.
|
|
11806
|
-
if (opts.waits === false) return
|
|
12006
|
+
const settled = (d) => d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution" && !entryUnit(e));
|
|
12007
|
+
if (parsed.wait !== true || opts.waits === false) return { ...await read3(opts.signal), waitOutcome: "not_waited" };
|
|
11807
12008
|
const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
|
|
11808
12009
|
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
11809
12010
|
let latest;
|
|
11810
12011
|
try {
|
|
11811
12012
|
while (true) {
|
|
11812
12013
|
signal.throwIfAborted();
|
|
11813
|
-
latest = await
|
|
11814
|
-
if (settled(latest)) return latest;
|
|
12014
|
+
latest = await read3(signal);
|
|
12015
|
+
if (settled(latest)) return { ...latest, waitOutcome: "available" };
|
|
11815
12016
|
await sleep2(5e3, void 0, { signal });
|
|
11816
12017
|
}
|
|
11817
12018
|
} catch (error) {
|
|
11818
12019
|
opts.signal?.throwIfAborted();
|
|
11819
|
-
if (window.aborted && latest) return latest;
|
|
12020
|
+
if (window.aborted && latest) return { ...latest, waitOutcome: "expired" };
|
|
11820
12021
|
throw error;
|
|
11821
12022
|
}
|
|
11822
12023
|
}
|
|
11823
|
-
async function
|
|
12024
|
+
async function receive(opts = {}) {
|
|
11824
12025
|
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
|
|
11825
|
-
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}
|
|
12026
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
|
|
11826
12027
|
}));
|
|
11827
|
-
if (!res.ok) await fail("
|
|
12028
|
+
if (!res.ok) await fail("receive", res);
|
|
11828
12029
|
return await res.json();
|
|
11829
12030
|
}
|
|
12031
|
+
async function acknowledge(eventIds, opts = {}) {
|
|
12032
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/ack`, {
|
|
12033
|
+
method: "POST",
|
|
12034
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
|
|
12035
|
+
body: JSON.stringify({ eventIds })
|
|
12036
|
+
}));
|
|
12037
|
+
if (!res.ok) await fail("acknowledge", res);
|
|
12038
|
+
return (await res.json()).results ?? [];
|
|
12039
|
+
}
|
|
11830
12040
|
var HEARTBEAT_MS = 25e3;
|
|
11831
12041
|
var RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
11832
12042
|
var JOIN = { config: { broadcast: { self: false } } };
|
|
@@ -12307,9 +12517,9 @@ async function available(deps = {}) {
|
|
|
12307
12517
|
});
|
|
12308
12518
|
return [ollama, ...harnesses];
|
|
12309
12519
|
}
|
|
12310
|
-
function refusal(options,
|
|
12520
|
+
function refusal(options, asked) {
|
|
12311
12521
|
const lines2 = [
|
|
12312
|
-
|
|
12522
|
+
asked ? `Can't judge with --via ${asked}: ${options.find((o) => o.provider === asked)?.hint ?? "it isn't available on this machine"}` : "No local judge available \u2014 triage refuses rather than sending your queue to a cloud model.",
|
|
12313
12523
|
"",
|
|
12314
12524
|
"Triage runs the model where your queue already lives (#1106). Your options:"
|
|
12315
12525
|
];
|
|
@@ -12333,10 +12543,10 @@ var RUNNERS = {
|
|
|
12333
12543
|
agy: { cli: "agy", argv: () => ["-p"] }
|
|
12334
12544
|
};
|
|
12335
12545
|
var RUN_TIMEOUT_MS = 5 * 6e4;
|
|
12336
|
-
async function judgeFor(
|
|
12546
|
+
async function judgeFor(asked, deps = {}) {
|
|
12337
12547
|
const options = await available(deps);
|
|
12338
|
-
const chosen =
|
|
12339
|
-
if (!chosen?.ready) return { error: refusal(options,
|
|
12548
|
+
const chosen = asked ? options.find((o) => o.provider === asked) : options.find((o) => o.ready);
|
|
12549
|
+
if (!chosen?.ready) return { error: refusal(options, asked) };
|
|
12340
12550
|
if (chosen.provider === "ollama") {
|
|
12341
12551
|
const model = chosen.model;
|
|
12342
12552
|
return {
|
|
@@ -12357,9 +12567,9 @@ async function judgeFor(asked2, deps = {}) {
|
|
|
12357
12567
|
};
|
|
12358
12568
|
}
|
|
12359
12569
|
const runner = RUNNERS[chosen.provider];
|
|
12360
|
-
if (!runner) return { error: refusal(options,
|
|
12570
|
+
if (!runner) return { error: refusal(options, asked) };
|
|
12361
12571
|
const bin = which(runner.cli);
|
|
12362
|
-
if (!bin) return { error: refusal(options,
|
|
12572
|
+
if (!bin) return { error: refusal(options, asked) };
|
|
12363
12573
|
return {
|
|
12364
12574
|
provider: chosen.provider,
|
|
12365
12575
|
model: null,
|
|
@@ -12628,10 +12838,10 @@ function ago(iso, now) {
|
|
|
12628
12838
|
return `${plural2(Math.floor(hours / 24), "day")} ago`;
|
|
12629
12839
|
}
|
|
12630
12840
|
function standing(queue, proposal, now) {
|
|
12631
|
-
const
|
|
12632
|
-
if (!proposal) return `${
|
|
12841
|
+
const head2 = queue.length === 0 ? "Nothing open \u2014 the queue is clear" : `Holding ${plural2(queue.length, "open note")}`;
|
|
12842
|
+
if (!proposal) return `${head2} \xB7 no sweep waiting`;
|
|
12633
12843
|
const n = suggested(proposal);
|
|
12634
|
-
return n === 0 ? `${
|
|
12844
|
+
return n === 0 ? `${head2} \xB7 swept ${ago(proposal.runAt, now)}, nothing to suggest` : `${head2} \xB7 ${plural2(n, "suggestion")} waiting on you, swept ${ago(proposal.runAt, now)}`;
|
|
12635
12845
|
}
|
|
12636
12846
|
async function brokerView(deps) {
|
|
12637
12847
|
const now = (deps.now ?? Date.now)();
|
|
@@ -12731,7 +12941,7 @@ function listRepos(deps) {
|
|
|
12731
12941
|
}
|
|
12732
12942
|
|
|
12733
12943
|
// src/holder.ts
|
|
12734
|
-
import { execFileSync as
|
|
12944
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
12735
12945
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
12736
12946
|
import { homedir as homedir6 } from "os";
|
|
12737
12947
|
import { join as join6 } from "path";
|
|
@@ -12746,7 +12956,7 @@ function isAlive(pid) {
|
|
|
12746
12956
|
function psStart(pid, zone) {
|
|
12747
12957
|
try {
|
|
12748
12958
|
const env = zone === "UTC" ? { ...process.env, TZ: "UTC", LC_ALL: "C" } : { ...process.env, LC_ALL: "C" };
|
|
12749
|
-
return
|
|
12959
|
+
return execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], { env, encoding: "utf8", timeout: 2e3 }).trim() || null;
|
|
12750
12960
|
} catch {
|
|
12751
12961
|
return null;
|
|
12752
12962
|
}
|
|
@@ -12774,7 +12984,7 @@ function proven(pid, procStart, deps) {
|
|
|
12774
12984
|
function localHolderPids(deps = {}) {
|
|
12775
12985
|
const dir = deps.dir ?? join6(process.env.CLAUDE_CONFIG_DIR?.trim() || join6(homedir6(), ".claude"), "sessions");
|
|
12776
12986
|
const list = deps.list ?? ((d) => readdirSync2(d));
|
|
12777
|
-
const
|
|
12987
|
+
const read3 = deps.read ?? ((f) => readFileSync4(f, "utf8"));
|
|
12778
12988
|
const check = { alive: deps.alive ?? isAlive, started: deps.started ?? psStart };
|
|
12779
12989
|
const held = /* @__PURE__ */ new Map();
|
|
12780
12990
|
let files;
|
|
@@ -12787,7 +12997,7 @@ function localHolderPids(deps = {}) {
|
|
|
12787
12997
|
if (!/^\d+\.json$/.test(file)) continue;
|
|
12788
12998
|
let row;
|
|
12789
12999
|
try {
|
|
12790
|
-
row = JSON.parse(
|
|
13000
|
+
row = JSON.parse(read3(join6(dir, file)));
|
|
12791
13001
|
} catch {
|
|
12792
13002
|
continue;
|
|
12793
13003
|
}
|
|
@@ -12801,7 +13011,7 @@ function localHolderPids(deps = {}) {
|
|
|
12801
13011
|
}
|
|
12802
13012
|
function psParent(pid) {
|
|
12803
13013
|
try {
|
|
12804
|
-
const out =
|
|
13014
|
+
const out = execFileSync2("ps", ["-o", "ppid=", "-p", String(pid)], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
12805
13015
|
const n = Number(out);
|
|
12806
13016
|
return Number.isInteger(n) && n > 0 ? n : null;
|
|
12807
13017
|
} catch {
|
|
@@ -12819,7 +13029,7 @@ function descendsFrom(pid, ancestor, parent = psParent) {
|
|
|
12819
13029
|
function localHolders(deps = {}) {
|
|
12820
13030
|
const dir = deps.dir ?? join6(process.env.CLAUDE_CONFIG_DIR?.trim() || join6(homedir6(), ".claude"), "sessions");
|
|
12821
13031
|
const list = deps.list ?? ((d) => readdirSync2(d));
|
|
12822
|
-
const
|
|
13032
|
+
const read3 = deps.read ?? ((f) => readFileSync4(f, "utf8"));
|
|
12823
13033
|
const check = { alive: deps.alive ?? isAlive, started: deps.started ?? psStart };
|
|
12824
13034
|
const held = /* @__PURE__ */ new Map();
|
|
12825
13035
|
let files;
|
|
@@ -12832,7 +13042,7 @@ function localHolders(deps = {}) {
|
|
|
12832
13042
|
if (!/^\d+\.json$/.test(file)) continue;
|
|
12833
13043
|
let row;
|
|
12834
13044
|
try {
|
|
12835
|
-
row = JSON.parse(
|
|
13045
|
+
row = JSON.parse(read3(join6(dir, file)));
|
|
12836
13046
|
} catch {
|
|
12837
13047
|
continue;
|
|
12838
13048
|
}
|
|
@@ -12925,7 +13135,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
12925
13135
|
const prefix = m[1];
|
|
12926
13136
|
const root = deps.projectsDir ?? join7(homedir7(), ".claude", "projects");
|
|
12927
13137
|
const list = deps.list ?? ((dir) => readdirSync3(dir));
|
|
12928
|
-
const
|
|
13138
|
+
const head2 = deps.head ?? readHead;
|
|
12929
13139
|
let folders;
|
|
12930
13140
|
try {
|
|
12931
13141
|
folders = list(root);
|
|
@@ -12943,7 +13153,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
12943
13153
|
if (!file) continue;
|
|
12944
13154
|
let text;
|
|
12945
13155
|
try {
|
|
12946
|
-
text =
|
|
13156
|
+
text = head2(join7(root, folder, file));
|
|
12947
13157
|
} catch {
|
|
12948
13158
|
continue;
|
|
12949
13159
|
}
|
|
@@ -14254,14 +14464,20 @@ var OptionSchema2 = external_exports.object({
|
|
|
14254
14464
|
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
14255
14465
|
// the constraints below are what an agent reads when deciding to use these.
|
|
14256
14466
|
html: external_exports.string().max(16384).describe(
|
|
14257
|
-
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use
|
|
14467
|
+
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use the image URL instead."
|
|
14258
14468
|
).optional(),
|
|
14259
14469
|
image: external_exports.string().url().describe(
|
|
14260
|
-
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use
|
|
14470
|
+
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use the HTML preview instead."
|
|
14261
14471
|
).optional()
|
|
14262
14472
|
});
|
|
14263
|
-
var OptionInputSchema2 =
|
|
14264
|
-
|
|
14473
|
+
var OptionInputSchema2 = external_exports.object({
|
|
14474
|
+
id: external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_.:-]+$/).optional().describe(
|
|
14475
|
+
`Optional: this option's own id, which answers name it by (selectedOptionIds). Give every option one, or none: without them the options are numbered "1", "2", \u2026 in order.`
|
|
14476
|
+
),
|
|
14477
|
+
label: external_exports.string().trim().min(1).max(1e3),
|
|
14478
|
+
hint: OptionSchema2.shape.hint,
|
|
14479
|
+
htmlPreview: OptionSchema2.shape.html,
|
|
14480
|
+
imgUrl: OptionSchema2.shape.image
|
|
14265
14481
|
}).strict();
|
|
14266
14482
|
var NIGHT2 = { from: 23, to: 7 };
|
|
14267
14483
|
function draft20202(node) {
|
|
@@ -14293,33 +14509,162 @@ function mcpInputSchema2(s) {
|
|
|
14293
14509
|
delete schema.$schema;
|
|
14294
14510
|
return draft20202(schema);
|
|
14295
14511
|
}
|
|
14296
|
-
var
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14512
|
+
var ASK_MAX2 = 250;
|
|
14513
|
+
var UNITS_MAX2 = 12;
|
|
14514
|
+
var UNIT_TITLE_MAX2 = 50;
|
|
14515
|
+
var UNIT_BODY_MAX2 = 200;
|
|
14516
|
+
var UnitInputSchema2 = external_exports.object({
|
|
14517
|
+
title: external_exports.string().trim().min(1).describe(`What this unit is about, at most ${UNIT_TITLE_MAX2} characters. The app lists the titles; the person taps one to read its body.`),
|
|
14518
|
+
body: external_exports.string().trim().min(1).describe(`The unit itself, at most ${UNIT_BODY_MAX2} characters.`)
|
|
14519
|
+
}).strict();
|
|
14520
|
+
function capRefusals2(text, units22, field) {
|
|
14521
|
+
const out = [];
|
|
14522
|
+
const name = field === "text" ? "answer" : field;
|
|
14523
|
+
if (text.length > ASK_MAX2) out.push({
|
|
14524
|
+
refusal: "ask_too_long",
|
|
14525
|
+
length: text.length,
|
|
14526
|
+
cap: ASK_MAX2,
|
|
14527
|
+
path: field === "text" ? ["answer", "text"] : [field],
|
|
14528
|
+
message: `ask_too_long: the ${name} is ${text.length} characters; the cap is ${ASK_MAX2}. Keep the one point in the ${name} and move the context into units.`
|
|
14529
|
+
});
|
|
14530
|
+
const all = units22 ?? [];
|
|
14531
|
+
if (all.length > UNITS_MAX2) out.push({
|
|
14532
|
+
refusal: "too_many_units",
|
|
14533
|
+
length: all.length,
|
|
14534
|
+
cap: UNITS_MAX2,
|
|
14535
|
+
path: ["units"],
|
|
14536
|
+
message: `too_many_units: ${all.length} units; the cap is ${UNITS_MAX2}. Keep only the context this ${name} needs.`
|
|
14537
|
+
});
|
|
14538
|
+
all.forEach((unit, index) => {
|
|
14539
|
+
if (unit.title.length > UNIT_TITLE_MAX2) out.push({
|
|
14540
|
+
refusal: "unit_title_too_long",
|
|
14541
|
+
index,
|
|
14542
|
+
length: unit.title.length,
|
|
14543
|
+
cap: UNIT_TITLE_MAX2,
|
|
14544
|
+
path: ["units", index, "title"],
|
|
14545
|
+
message: `unit_title_too_long: units[${index}]'s title is ${unit.title.length} characters; the cap is ${UNIT_TITLE_MAX2}.`
|
|
14546
|
+
});
|
|
14547
|
+
if (unit.body.length > UNIT_BODY_MAX2) out.push({
|
|
14548
|
+
refusal: "unit_body_too_long",
|
|
14549
|
+
index,
|
|
14550
|
+
length: unit.body.length,
|
|
14551
|
+
cap: UNIT_BODY_MAX2,
|
|
14552
|
+
path: ["units", index, "body"],
|
|
14553
|
+
message: `unit_body_too_long: units[${index}]'s body is ${unit.body.length} characters; the cap is ${UNIT_BODY_MAX2}. Cut it into two units.`
|
|
14554
|
+
});
|
|
14555
|
+
});
|
|
14556
|
+
return out;
|
|
14557
|
+
}
|
|
14558
|
+
function refuseOverCaps2(text, units22, field, ctx) {
|
|
14559
|
+
for (const { path, message, ...params } of capRefusals2(text, units22, field)) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message, params });
|
|
14560
|
+
}
|
|
14561
|
+
var questionHandle2 = external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
|
|
14562
|
+
var NodeRef2 = external_exports.object({
|
|
14563
|
+
type: external_exports.enum(["goal", "question"]),
|
|
14564
|
+
id: external_exports.string().uuid()
|
|
14565
|
+
}).strict().describe("A Goal or a Question, by its id.");
|
|
14566
|
+
var BlockedAction2 = external_exports.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
|
|
14567
|
+
var reason2 = external_exports.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
|
|
14568
|
+
var actionFits2 = (b) => b.blocked.type === "goal" === (b.action !== "answer");
|
|
14569
|
+
var ACTION_FITS2 = "a goal is blocked from start or complete; a question from answer";
|
|
14570
|
+
var userExplicitlyRequested2 = external_exports.enum(["any", "call"]).optional().describe(
|
|
14571
|
+
`Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
|
|
14572
|
+
);
|
|
14573
|
+
var units2 = external_exports.array(UnitInputSchema2).optional().describe(
|
|
14574
|
+
`The context this message needs, as up to ${UNITS_MAX2} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX2} characters and a body of at most ${UNIT_BODY_MAX2}. One idea per unit. Leave it out when the main text says it all.`
|
|
14575
|
+
);
|
|
14576
|
+
var UpdateInputSchema2 = external_exports.object({
|
|
14577
|
+
message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX2} characters; the context goes in units.`),
|
|
14578
|
+
goalIds: external_exports.array(external_exports.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
14579
|
+
units: units2,
|
|
14580
|
+
userExplicitlyRequested: userExplicitlyRequested2
|
|
14581
|
+
}).strict().superRefine((u, ctx) => refuseOverCaps2(u.message, u.units, "message", ctx));
|
|
14582
|
+
var questionFields2 = {
|
|
14583
|
+
question: external_exports.string().trim().min(1).describe(
|
|
14584
|
+
`ONE question, in at most ${ASK_MAX2} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
14302
14585
|
),
|
|
14303
|
-
|
|
14586
|
+
goalId: external_exports.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
14587
|
+
units: units2,
|
|
14588
|
+
options: external_exports.array(OptionInputSchema2).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own."),
|
|
14304
14589
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
14305
|
-
// brain_prompts.md §1: structured agent questions need no model).
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14590
|
+
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
14591
|
+
pickMode: external_exports.enum(["one", "many", "rank"]).optional().describe(
|
|
14592
|
+
'How the options are answered: "one" (the default) a pick, for alternatives; "many" a checklist, for independent options they may want several of; "rank" an order of all of them. Only with options.'
|
|
14593
|
+
),
|
|
14594
|
+
// WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
|
|
14595
|
+
// blocks work asks for a call (the person's settings and ring cooldowns still decide).
|
|
14596
|
+
workItBlocks: external_exports.array(external_exports.object({ blocked: NodeRef2, action: BlockedAction2, reason: reason2.optional() }).strict().refine(actionFits2, ACTION_FITS2)).min(1).max(10).optional().describe(
|
|
14597
|
+
"The work that cannot go on until this is answered: a Goal's start or completion, or another Question's answer. Only for work truly blocked; it asks for a call. Leave it out for a question you can keep working around."
|
|
14310
14598
|
),
|
|
14311
|
-
|
|
14312
|
-
|
|
14599
|
+
userExplicitlyRequested: userExplicitlyRequested2
|
|
14600
|
+
};
|
|
14601
|
+
var refineQuestion2 = (q, ctx) => {
|
|
14602
|
+
refuseOverCaps2(q.question, q.units, "question", ctx);
|
|
14603
|
+
if (q.pickMode && !q.options) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
|
|
14604
|
+
const ids = (q.options ?? []).map((o) => o.id);
|
|
14605
|
+
if (ids.some((id22) => id22) && (ids.some((id22) => !id22) || new Set(ids).size !== ids.length)) {
|
|
14606
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
|
|
14607
|
+
}
|
|
14608
|
+
};
|
|
14609
|
+
var QuestionInputSchema2 = external_exports.object({
|
|
14610
|
+
questionId: external_exports.string().uuid().optional().describe("Optional: your own stable id for this question (a UUID), so a resend is the same question. Minted for you when omitted; the result returns it."),
|
|
14611
|
+
...questionFields2
|
|
14612
|
+
}).strict().superRefine(refineQuestion2);
|
|
14613
|
+
var QuestionWireSchema2 = external_exports.object({ questionId: external_exports.string().uuid(), ...questionFields2 }).strict().superRefine(refineQuestion2);
|
|
14614
|
+
var AnswerInputSchema2 = external_exports.object({
|
|
14615
|
+
questionId: questionHandle2.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
|
|
14616
|
+
answer: external_exports.object({
|
|
14617
|
+
text: external_exports.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX2} characters; the context goes in units.`),
|
|
14618
|
+
selectedOptionIds: external_exports.array(external_exports.string().trim().min(1)).min(1).max(6).optional().describe("The ids of the options you choose, when the Question offered options (in order, for a ranking).")
|
|
14619
|
+
}).strict(),
|
|
14620
|
+
units: units2
|
|
14621
|
+
}).strict().superRefine((a, ctx) => refuseOverCaps2(a.answer.text, a.units, "text", ctx));
|
|
14622
|
+
var QuestionDependencySchema2 = external_exports.object({
|
|
14623
|
+
questionId: questionHandle2.describe("A Question you asked."),
|
|
14624
|
+
change: external_exports.enum(["add", "remove"]),
|
|
14625
|
+
blocked: NodeRef2,
|
|
14626
|
+
action: BlockedAction2,
|
|
14627
|
+
reason: reason2.optional()
|
|
14628
|
+
}).strict().refine(actionFits2, ACTION_FITS2);
|
|
14629
|
+
var sendFields2 = {
|
|
14630
|
+
updates: external_exports.array(UpdateInputSchema2).min(1).max(20).optional().describe(
|
|
14631
|
+
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
|
|
14632
|
+
),
|
|
14633
|
+
answers: external_exports.array(AnswerInputSchema2).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
|
|
14634
|
+
withdrawQuestionIds: external_exports.array(questionHandle2).min(1).max(10).optional().describe(
|
|
14635
|
+
"Questions you asked that no longer need an answer: you acted on them yourself, they no longer matter, or you asked wrongly. Each is cancelled, not answered: its card closes and nothing rings for it."
|
|
14636
|
+
),
|
|
14637
|
+
questionDependencies: external_exports.array(QuestionDependencySchema2).min(1).max(20).optional().describe(
|
|
14638
|
+
"Add or remove what a Question you asked holds up, after asking it."
|
|
14639
|
+
)
|
|
14640
|
+
};
|
|
14641
|
+
var ContactSchema2 = external_exports.object({
|
|
14642
|
+
...sendFields2,
|
|
14643
|
+
questions: external_exports.array(QuestionInputSchema2).min(1).max(20).optional().describe("Questions for the person, one per object."),
|
|
14644
|
+
/** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
|
|
14645
|
+
ackEventIds: external_exports.array(external_exports.string().uuid()).min(1).max(100).optional().describe(
|
|
14646
|
+
"The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (answers), never acknowledged away."
|
|
14647
|
+
),
|
|
14648
|
+
wait: external_exports.boolean().optional().describe(
|
|
14649
|
+
"Only controls when this returns. Sending: false (the default) returns at once; true waits up to about 45 seconds for a response to what you sent. Sending nothing: true (the default) waits up to about 45 seconds for anything addressed to you; false returns what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived; wait again without sending again), or `not_waited`."
|
|
14313
14650
|
)
|
|
14314
14651
|
}).strict();
|
|
14315
|
-
var
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14652
|
+
var sendsAnything2 = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
14653
|
+
var ContactWireSchema2 = external_exports.object({
|
|
14654
|
+
operationId: external_exports.string().uuid(),
|
|
14655
|
+
...sendFields2,
|
|
14656
|
+
questions: external_exports.array(QuestionWireSchema2).min(1).max(20).optional()
|
|
14657
|
+
}).strict().refine(sendsAnything2, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
14321
14658
|
var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
14322
|
-
var CONTACT_DESCRIPTION2 =
|
|
14659
|
+
var CONTACT_DESCRIPTION2 = `Send to and receive from the person: updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read \`results\`, where a failed item names why while the rest still apply.
|
|
14660
|
+
|
|
14661
|
+
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
14662
|
+
|
|
14663
|
+
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX2} characters; everything else it needs goes in 'units', up to ${UNITS_MAX2} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX2} characters and every body at most ${UNIT_BODY_MAX2}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
14664
|
+
|
|
14665
|
+
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
14666
|
+
|
|
14667
|
+
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
14323
14668
|
var CreateGoalSchema2 = external_exports.object({
|
|
14324
14669
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
14325
14670
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -14339,10 +14684,6 @@ var CreateGoalSchema2 = external_exports.object({
|
|
|
14339
14684
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
14340
14685
|
repo: external_exports.string().trim().min(1).max(200).optional()
|
|
14341
14686
|
});
|
|
14342
|
-
var CreateGoalToolSchema2 = CreateGoalSchema2.extend({
|
|
14343
|
-
idempotencyKey: CreateGoalSchema2.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
14344
|
-
}).strict();
|
|
14345
|
-
var CREATE_GOAL_DESCRIPTION2 = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
|
|
14346
14687
|
var UpdateGoalSchema2 = external_exports.object({
|
|
14347
14688
|
revision: external_exports.number().int().positive(),
|
|
14348
14689
|
changes: external_exports.object({
|
|
@@ -14357,17 +14698,64 @@ var UpdateGoalSchema2 = external_exports.object({
|
|
|
14357
14698
|
state: external_exports.enum(["active", "done", "cancelled"]).optional(),
|
|
14358
14699
|
progress: external_exports.string().trim().min(1).max(1e4).optional(),
|
|
14359
14700
|
reviewed: external_exports.literal(true).optional(),
|
|
14360
|
-
dueAt: external_exports.string().datetime({ offset: true }).nullable().optional()
|
|
14361
|
-
/** WITHDRAW YOUR OWN QUESTION (#2777, owner 2026-09-30). The question's id as every read shows it
|
|
14362
|
-
* (the conversation's `id`, eight characters, or the whole request Entry id), on THIS Goal, asked
|
|
14363
|
-
* by you and still open. It is cancelled, not answered: its cards close and nothing rings for it. */
|
|
14364
|
-
withdraw: external_exports.array(external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/)).min(1).max(10).optional()
|
|
14701
|
+
dueAt: external_exports.string().datetime({ offset: true }).nullable().optional()
|
|
14365
14702
|
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
14366
14703
|
reason: external_exports.string().trim().min(1).max(2e3),
|
|
14367
14704
|
operationId: external_exports.string().uuid().optional()
|
|
14368
14705
|
}).strict();
|
|
14369
|
-
var
|
|
14370
|
-
var
|
|
14706
|
+
var changedGoal2 = external_exports.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
14707
|
+
var goalTitle2 = external_exports.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
14708
|
+
var goalOutcome2 = external_exports.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
|
|
14709
|
+
var CreateChange2 = external_exports.object({
|
|
14710
|
+
kind: external_exports.literal("create"),
|
|
14711
|
+
title: goalTitle2,
|
|
14712
|
+
outcome: goalOutcome2,
|
|
14713
|
+
ownerId: external_exports.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
|
|
14714
|
+
parentGoalId: external_exports.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
14715
|
+
sourceEntryIds: external_exports.array(external_exports.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
14716
|
+
}).strict();
|
|
14717
|
+
var EditChange2 = external_exports.object({ kind: external_exports.literal("edit"), goalId: changedGoal2, title: goalTitle2.optional(), outcome: goalOutcome2.optional() }).strict();
|
|
14718
|
+
var StateChange2 = external_exports.object({ kind: external_exports.literal("state"), goalId: changedGoal2, state: external_exports.enum(["open", "completed", "canceled"]).describe(
|
|
14719
|
+
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
14720
|
+
) }).strict();
|
|
14721
|
+
var AssignChange2 = external_exports.object({ kind: external_exports.literal("assign"), goalId: changedGoal2, ownerId: external_exports.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
14722
|
+
var DeferChange2 = external_exports.object({ kind: external_exports.literal("defer"), goalId: changedGoal2, until: external_exports.string().datetime({ offset: true }).nullable().describe(
|
|
14723
|
+
"Postpone it until this instant: it waits until then, and its owner is woken when it passes (a promise to follow up later). null takes the postponement back."
|
|
14724
|
+
) }).strict();
|
|
14725
|
+
var MoveChange2 = external_exports.object({ kind: external_exports.literal("move"), goalId: changedGoal2, parentGoalId: external_exports.string().uuid().nullable().describe(
|
|
14726
|
+
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
14727
|
+
) }).strict();
|
|
14728
|
+
var DependencyChange2 = external_exports.object({
|
|
14729
|
+
kind: external_exports.literal("dependency"),
|
|
14730
|
+
change: external_exports.enum(["add", "remove"]),
|
|
14731
|
+
goalId: changedGoal2,
|
|
14732
|
+
dependsOnGoalId: external_exports.string().uuid().describe("The Goal it waits on."),
|
|
14733
|
+
action: external_exports.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
14734
|
+
reason: external_exports.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
|
|
14735
|
+
}).strict();
|
|
14736
|
+
var read2 = { revision: external_exports.number().int().positive().optional() };
|
|
14737
|
+
var GoalChangeSchema2 = external_exports.discriminatedUnion("kind", [
|
|
14738
|
+
CreateChange2,
|
|
14739
|
+
EditChange2.extend(read2),
|
|
14740
|
+
StateChange2.extend(read2),
|
|
14741
|
+
AssignChange2.extend(read2),
|
|
14742
|
+
DeferChange2.extend(read2),
|
|
14743
|
+
MoveChange2.extend(read2),
|
|
14744
|
+
DependencyChange2
|
|
14745
|
+
]);
|
|
14746
|
+
var editNames2 = (changes, ctx) => changes.forEach((c, i) => {
|
|
14747
|
+
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
14748
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
14749
|
+
}
|
|
14750
|
+
});
|
|
14751
|
+
var ManageGoalsSchema2 = external_exports.object({
|
|
14752
|
+
requestId: external_exports.string().uuid().optional(),
|
|
14753
|
+
changes: external_exports.array(GoalChangeSchema2).min(1).max(50)
|
|
14754
|
+
}).strict().superRefine((v, ctx) => editNames2(v.changes, ctx));
|
|
14755
|
+
var ManageGoalsToolSchema2 = external_exports.object({
|
|
14756
|
+
changes: external_exports.array(external_exports.discriminatedUnion("kind", [CreateChange2, EditChange2, StateChange2, AssignChange2, DeferChange2, MoveChange2, DependencyChange2])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
14757
|
+
}).strict().superRefine((v, ctx) => editNames2(v.changes, ctx));
|
|
14758
|
+
var MANAGE_GOALS_DESCRIPTION2 = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
14371
14759
|
var GetGoalSchema2 = external_exports.object({
|
|
14372
14760
|
goalId: external_exports.string().uuid(),
|
|
14373
14761
|
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
@@ -14378,19 +14766,29 @@ var GetGoalSchema2 = external_exports.object({
|
|
|
14378
14766
|
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
14379
14767
|
diagnose: external_exports.boolean().optional()
|
|
14380
14768
|
}).strict();
|
|
14381
|
-
var GET_GOAL_DESCRIPTION2 = "Read one Goal
|
|
14382
|
-
var
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14769
|
+
var GET_GOAL_DESCRIPTION2 = "Read one Goal (a read never puts you on it), including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
14770
|
+
var SearchToolSchema2 = external_exports.object({
|
|
14771
|
+
query: external_exports.string().trim().min(1).max(500),
|
|
14772
|
+
types: external_exports.array(external_exports.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
14773
|
+
goalId: external_exports.string().uuid().optional(),
|
|
14774
|
+
limit: external_exports.number().int().min(1).max(20).optional()
|
|
14775
|
+
}).strict();
|
|
14776
|
+
var SEARCH_DESCRIPTION2 = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
14777
|
+
var CheckActivitySchema2 = external_exports.object({}).strict();
|
|
14778
|
+
var FEEDBACK_TEXT_MAX2 = 5e4;
|
|
14779
|
+
var SendFeedbackSchema2 = external_exports.object({
|
|
14780
|
+
text: external_exports.string().trim().min(1).max(FEEDBACK_TEXT_MAX2).describe(`The report, in markdown: what happened, what was expected, how to reproduce it, versions, evidence. At most ${FEEDBACK_TEXT_MAX2} characters.`),
|
|
14781
|
+
title: external_exports.string().trim().min(1).max(200).optional().describe("One line naming it."),
|
|
14782
|
+
kind: external_exports.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
|
|
14783
|
+
}).strict();
|
|
14784
|
+
var SEND_FEEDBACK_DESCRIPTION2 = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
|
|
14386
14785
|
var AGENT_TOOLS2 = [
|
|
14387
|
-
{ name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema2(CheckRepliesSchema2) },
|
|
14388
14786
|
{ name: "contact", description: CONTACT_DESCRIPTION2, inputSchema: CONTACT_SCHEMA2 },
|
|
14389
|
-
{ name: "
|
|
14390
|
-
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(CreateGoalToolSchema2) },
|
|
14391
|
-
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(ClaimGoalSchema2) },
|
|
14787
|
+
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION2, inputSchema: mcpInputSchema2(ManageGoalsToolSchema2) },
|
|
14392
14788
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(GetGoalSchema2) },
|
|
14393
|
-
{ name: "
|
|
14789
|
+
{ name: "search", description: SEARCH_DESCRIPTION2, inputSchema: mcpInputSchema2(SearchToolSchema2) },
|
|
14790
|
+
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema2(CheckActivitySchema2) },
|
|
14791
|
+
{ name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION2, inputSchema: mcpInputSchema2(SendFeedbackSchema2) }
|
|
14394
14792
|
];
|
|
14395
14793
|
var AGENT_TOOL_NAMES2 = AGENT_TOOLS2.map((t) => t.name);
|
|
14396
14794
|
var id2 = external_exports.string().uuid();
|
|
@@ -14421,17 +14819,6 @@ var existingGoalChanges2 = [
|
|
|
14421
14819
|
reason: external_exports.string().max(500).optional()
|
|
14422
14820
|
}).strict()
|
|
14423
14821
|
];
|
|
14424
|
-
var GoalChangeSchema2 = external_exports.union([
|
|
14425
|
-
external_exports.object({
|
|
14426
|
-
kind: external_exports.literal("create"),
|
|
14427
|
-
title: external_exports.string().min(1).max(120),
|
|
14428
|
-
outcome: external_exports.string().min(1),
|
|
14429
|
-
ownerId: participant2,
|
|
14430
|
-
parentGoalId: id2.optional(),
|
|
14431
|
-
sourceEntryIds: external_exports.array(id2)
|
|
14432
|
-
}).strict(),
|
|
14433
|
-
...existingGoalChanges2
|
|
14434
|
-
]);
|
|
14435
14822
|
var BrainGoalChangeSchema2 = external_exports.union([
|
|
14436
14823
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
14437
14824
|
external_exports.object({
|
|
@@ -14513,33 +14900,93 @@ var BrainResultSchema2 = external_exports.object({
|
|
|
14513
14900
|
next: BrainNextSchema2,
|
|
14514
14901
|
search: BrainSearchSchema2.optional()
|
|
14515
14902
|
}).strict();
|
|
14516
|
-
var
|
|
14517
|
-
var
|
|
14903
|
+
var str4 = external_exports.string();
|
|
14904
|
+
var strs4 = external_exports.array(external_exports.string());
|
|
14518
14905
|
var CompactResultSchema2 = external_exports.object({
|
|
14519
|
-
next: external_exports.object({ say:
|
|
14520
|
-
messages: external_exports.array(external_exports.object({ key:
|
|
14521
|
-
entries: external_exports.array(external_exports.object({ key:
|
|
14522
|
-
answers: external_exports.array(external_exports.object({ question:
|
|
14906
|
+
next: external_exports.object({ say: strs4, then: external_exports.enum(["listen", "hold", "end", "none"]), waitFor: strs4, reason: str4, instructions: strs4 }).strict(),
|
|
14907
|
+
messages: external_exports.array(external_exports.object({ key: str4, to: str4, text: str4, about: strs4, entries: strs4, owed: strs4, inviteToCall: external_exports.boolean() }).strict()),
|
|
14908
|
+
entries: external_exports.array(external_exports.object({ key: str4, from: str4, on: strs4 }).strict()),
|
|
14909
|
+
answers: external_exports.array(external_exports.object({ question: str4, entries: strs4, summary: str4, picked: strs4 }).strict()),
|
|
14523
14910
|
changes: external_exports.array(external_exports.object({
|
|
14524
|
-
key:
|
|
14911
|
+
key: str4,
|
|
14525
14912
|
what: external_exports.enum(["goal", "question", "lesson"]),
|
|
14526
14913
|
op: external_exports.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
|
|
14527
|
-
id:
|
|
14528
|
-
text:
|
|
14529
|
-
title:
|
|
14530
|
-
who:
|
|
14531
|
-
under:
|
|
14914
|
+
id: str4,
|
|
14915
|
+
text: str4,
|
|
14916
|
+
title: str4,
|
|
14917
|
+
who: str4,
|
|
14918
|
+
under: str4,
|
|
14532
14919
|
gate: external_exports.enum(["start", "complete", "answer", "none"]),
|
|
14533
|
-
options: external_exports.array(external_exports.object({ id:
|
|
14920
|
+
options: external_exports.array(external_exports.object({ id: str4, label: str4 }).strict()),
|
|
14534
14921
|
pick: external_exports.enum(["one", "many", "rank", "words"]),
|
|
14535
|
-
entries:
|
|
14922
|
+
entries: strs4
|
|
14536
14923
|
}).strict()),
|
|
14537
|
-
feedback: external_exports.array(
|
|
14538
|
-
search: external_exports.object({ query:
|
|
14924
|
+
feedback: external_exports.array(strs4),
|
|
14925
|
+
search: external_exports.object({ query: str4, within: strs4, goal: str4 }).strict()
|
|
14539
14926
|
}).strict();
|
|
14540
14927
|
var COMPACT_RESULT_JSON_SCHEMA2 = (({ $schema: _, ...rest }) => rest)(
|
|
14541
14928
|
zodToJsonSchema2(CompactResultSchema2, { $refStrategy: "none" })
|
|
14542
14929
|
);
|
|
14930
|
+
var str22 = external_exports.string();
|
|
14931
|
+
var strs22 = external_exports.array(external_exports.string());
|
|
14932
|
+
var TalkerReplySchema2 = external_exports.object({
|
|
14933
|
+
/** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
|
|
14934
|
+
* or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
|
|
14935
|
+
* answered before ("" for none). */
|
|
14936
|
+
messages: external_exports.array(external_exports.object({ key: str22, to: str22, text: str22, about: strs22, blocks: str22 }).strict()),
|
|
14937
|
+
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
14938
|
+
then: external_exports.enum(["listen", "hold", "end"]),
|
|
14939
|
+
/** Items the person's words settle: the item's handle, the chosen option IDs, the line handles. */
|
|
14940
|
+
answers: external_exports.array(external_exports.object({ item: str22, options: strs22, lines: strs22 }).strict()),
|
|
14941
|
+
/** The line handles that hold a new instruction from the person. */
|
|
14942
|
+
instruction: strs22,
|
|
14943
|
+
/** The line handles that ask for new work, a change to existing work, or a standing rule: the filer
|
|
14944
|
+
* reads only these (3e). */
|
|
14945
|
+
file: strs22,
|
|
14946
|
+
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
14947
|
+
need: external_exports.object({ kind: external_exports.enum(["search", "item", "agent", ""]), text: str22 }).strict(),
|
|
14948
|
+
/** The handle of the item to explain ("" for none). */
|
|
14949
|
+
explain: str22
|
|
14950
|
+
}).strict();
|
|
14951
|
+
var TALKER_REPLY_JSON_SCHEMA2 = (({ $schema: _, ...rest }) => rest)(
|
|
14952
|
+
zodToJsonSchema2(TalkerReplySchema2, { $refStrategy: "none" })
|
|
14953
|
+
);
|
|
14954
|
+
var str32 = external_exports.string();
|
|
14955
|
+
var strs32 = external_exports.array(external_exports.string());
|
|
14956
|
+
var FilerReplySchema2 = external_exports.object({
|
|
14957
|
+
/** Which Goals each new line goes on: a line's handle and Goal handles (or keys of new Goals). */
|
|
14958
|
+
filed: external_exports.array(external_exports.object({ line: str32, goals: strs32 }).strict()),
|
|
14959
|
+
goals: external_exports.array(external_exports.object({
|
|
14960
|
+
op: external_exports.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
14961
|
+
key: str32,
|
|
14962
|
+
goal: str32,
|
|
14963
|
+
title: str32,
|
|
14964
|
+
outcome: str32,
|
|
14965
|
+
owner: str32,
|
|
14966
|
+
under: str32,
|
|
14967
|
+
until: str32,
|
|
14968
|
+
on: str32,
|
|
14969
|
+
gate: external_exports.enum(["start", "complete", "answer", ""]),
|
|
14970
|
+
why: str32
|
|
14971
|
+
}).strict()),
|
|
14972
|
+
questions: external_exports.array(external_exports.object({
|
|
14973
|
+
op: external_exports.enum(["create", "edit", "assign", "withdraw"]),
|
|
14974
|
+
key: str32,
|
|
14975
|
+
question: str32,
|
|
14976
|
+
text: str32,
|
|
14977
|
+
to: str32,
|
|
14978
|
+
goal: str32,
|
|
14979
|
+
options: external_exports.array(external_exports.object({ id: str32, label: str32 }).strict()),
|
|
14980
|
+
pick: external_exports.enum(["one", "many", "rank", "words", ""]),
|
|
14981
|
+
blocks: str32
|
|
14982
|
+
}).strict()),
|
|
14983
|
+
lessons: external_exports.array(external_exports.object({ op: external_exports.enum(["remember", "revise", "withdraw"]), lesson: str32, text: str32, scope: str32 }).strict()),
|
|
14984
|
+
/** Line handles whose words comment on Paigy itself. */
|
|
14985
|
+
feedback: strs32
|
|
14986
|
+
}).strict();
|
|
14987
|
+
var FILER_REPLY_JSON_SCHEMA2 = (({ $schema: _, ...rest }) => rest)(
|
|
14988
|
+
zodToJsonSchema2(FilerReplySchema2, { $refStrategy: "none" })
|
|
14989
|
+
);
|
|
14543
14990
|
function entryWords(entry) {
|
|
14544
14991
|
const content = entry.content;
|
|
14545
14992
|
if (content && "sealed" in content) return "";
|
|
@@ -14563,7 +15010,10 @@ var ContextSchema2 = external_exports.object({
|
|
|
14563
15010
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
14564
15011
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
14565
15012
|
"Semantic chunks of detail (each a standalone, non-empty piece). The user can select chunks to ask you to expand. MAY BE EMPTY: a claim whose whole content is its heading \u2014 a single sentence \u2014 has no body, and saying so beats repeating the heading underneath itself. That repeat is what `min(1)` used to force, at 2x the storage, with every reader subtracting it back out at render time."
|
|
14566
|
-
)
|
|
15013
|
+
),
|
|
15014
|
+
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
15015
|
+
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
15016
|
+
units: external_exports.array(external_exports.object({ title: external_exports.string(), body: external_exports.string() })).optional()
|
|
14567
15017
|
});
|
|
14568
15018
|
var ParticipantSchema2 = external_exports.object({
|
|
14569
15019
|
kind: external_exports.enum(["human", "agent"]),
|
|
@@ -14638,7 +15088,7 @@ var NotifyRequestFields2 = external_exports.object({
|
|
|
14638
15088
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
14639
15089
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
14640
15090
|
context: ContextSchema2.optional(),
|
|
14641
|
-
options: external_exports.array(
|
|
15091
|
+
options: external_exports.array(OptionSchema2.omit({ id: true }).extend({ label: external_exports.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN2).max(OPTIONS_MAX2).optional().describe(
|
|
14642
15092
|
"The choices, in order \u2014 required when select is 'one'/'many'/'rank', omitted otherwise. Ids are assigned automatically by position ('1', '2', \u2026); the user's answer references them as optionId(s)."
|
|
14643
15093
|
),
|
|
14644
15094
|
points: external_exports.array(external_exports.string().min(1)).optional().describe(
|
|
@@ -14832,7 +15282,7 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
14832
15282
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
14833
15283
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
14834
15284
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
14835
|
-
* re-orient via
|
|
15285
|
+
* re-orient via contact({})). */
|
|
14836
15286
|
external_exports.object({
|
|
14837
15287
|
type: external_exports.literal("superseded"),
|
|
14838
15288
|
parentId: external_exports.string(),
|
|
@@ -15399,8 +15849,8 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
15399
15849
|
repo: external_exports.string().nullable().optional(),
|
|
15400
15850
|
createdAt: external_exports.string(),
|
|
15401
15851
|
updatedAt: external_exports.string().nullable().default(null),
|
|
15402
|
-
/** When its owner last SAID something about it (`
|
|
15403
|
-
*
|
|
15852
|
+
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
15853
|
+
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
15404
15854
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
15405
15855
|
lastProgressAt: external_exports.string().nullable().optional(),
|
|
15406
15856
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
@@ -15556,11 +16006,11 @@ var HostDecisionSchema2 = external_exports.object({
|
|
|
15556
16006
|
/** The agent's token id: the row's `recipient`. */
|
|
15557
16007
|
agent: external_exports.string().uuid(),
|
|
15558
16008
|
decision: external_exports.enum(["stood_back", "took_over"]),
|
|
15559
|
-
/** The work it was about: the Goal
|
|
16009
|
+
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
15560
16010
|
goalId: external_exports.string().uuid().nullable().optional(),
|
|
15561
16011
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
15562
16012
|
seenAt: external_exports.string().datetime().nullable().optional(),
|
|
15563
|
-
/** When that work last moved (`claimable.since` on `
|
|
16013
|
+
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
15564
16014
|
since: external_exports.string().datetime().nullable().optional(),
|
|
15565
16015
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
15566
16016
|
said: external_exports.string().max(300).optional()
|
|
@@ -15783,7 +16233,7 @@ function sameTail(a, b) {
|
|
|
15783
16233
|
}
|
|
15784
16234
|
|
|
15785
16235
|
// src/run.ts
|
|
15786
|
-
import { randomUUID as
|
|
16236
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
15787
16237
|
|
|
15788
16238
|
// src/harness/session.ts
|
|
15789
16239
|
import { spawn } from "child_process";
|
|
@@ -16067,7 +16517,7 @@ var frame2 = (body) => JSON.stringify({ jsonrpc: "2.0", ...body });
|
|
|
16067
16517
|
import { mkdirSync as mkdirSync3, readdirSync as readdirSync4, renameSync, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
16068
16518
|
import { join as join8 } from "path";
|
|
16069
16519
|
import { homedir as homedir8 } from "os";
|
|
16070
|
-
import { randomUUID as
|
|
16520
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
16071
16521
|
var MIDTURN_HOOK = `d="$PAIGY_INBOX"; [ -n "$d" ] || { cat >/dev/null; exit 0; }; set -- "$d"/*.note; [ -e "$1" ] || { cat >/dev/null; exit 0; }; grep '"agent_id"' >/dev/null && exit 0; mkdir -p "$d/taken"; o=""; for f in "$@"; do n=\${f##*/}; mv "$f" "$d/taken/$n" 2>/dev/null || continue; touch "$d/taken/$n"; o="$o\${o:+\\n\\n}$(cat "$d/taken/$n")"; done; [ -n "$o" ] || exit 0; printf '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"%s"}}' "$o"`;
|
|
16072
16522
|
var missing = (error) => error.code === "ENOENT";
|
|
16073
16523
|
function openInbox(dir) {
|
|
@@ -16127,7 +16577,7 @@ function inboxFor(root = INBOX_ROOT) {
|
|
|
16127
16577
|
} catch (error) {
|
|
16128
16578
|
if (!missing(error)) throw error;
|
|
16129
16579
|
}
|
|
16130
|
-
return openInbox(join8(root, `${process.pid}-${
|
|
16580
|
+
return openInbox(join8(root, `${process.pid}-${randomUUID3().slice(0, 8)}`));
|
|
16131
16581
|
}
|
|
16132
16582
|
|
|
16133
16583
|
// src/harness/session.ts
|
|
@@ -16261,6 +16711,32 @@ function startSession(opts) {
|
|
|
16261
16711
|
};
|
|
16262
16712
|
}
|
|
16263
16713
|
|
|
16714
|
+
// src/paigy/cut.ts
|
|
16715
|
+
function cut(text, title = "Details") {
|
|
16716
|
+
const all = text.trim();
|
|
16717
|
+
if (all.length <= ASK_MAX2) return { main: all };
|
|
16718
|
+
const main = head(all, ASK_MAX2);
|
|
16719
|
+
const units3 = [];
|
|
16720
|
+
let rest = all.slice(main.length).trim();
|
|
16721
|
+
while (rest && units3.length < UNITS_MAX2) {
|
|
16722
|
+
const last = units3.length === UNITS_MAX2 - 1;
|
|
16723
|
+
const body = last && rest.length > UNIT_BODY_MAX2 ? `${head(rest, UNIT_BODY_MAX2 - 1).trimEnd()}\u2026` : head(rest, UNIT_BODY_MAX2);
|
|
16724
|
+
units3.push({ title: units3.length ? `${title} (${units3.length + 1})` : title, body: body.trim() });
|
|
16725
|
+
rest = last ? "" : rest.slice(body.length).trim();
|
|
16726
|
+
}
|
|
16727
|
+
return { main: main.trim(), units: units3 };
|
|
16728
|
+
}
|
|
16729
|
+
function head(text, max) {
|
|
16730
|
+
if (text.length <= max) return text;
|
|
16731
|
+
const window = text.slice(0, max + 1);
|
|
16732
|
+
for (const boundary of [/\n\s*\n/g, /[.!?](\s)/g, /\s/g]) {
|
|
16733
|
+
let at = -1;
|
|
16734
|
+
for (const m of window.matchAll(boundary)) if (m.index > max / 3 && m.index < max) at = m.index + (boundary.source.startsWith("[") ? 1 : 0);
|
|
16735
|
+
if (at > 0) return text.slice(0, at);
|
|
16736
|
+
}
|
|
16737
|
+
return text.slice(0, max);
|
|
16738
|
+
}
|
|
16739
|
+
|
|
16264
16740
|
// src/paigy/limit.ts
|
|
16265
16741
|
var LIMIT_PREFIXES = [
|
|
16266
16742
|
"You've hit your",
|
|
@@ -16389,13 +16865,27 @@ var LIMITS = episodes();
|
|
|
16389
16865
|
// src/paigy/bridge.ts
|
|
16390
16866
|
function railFor(opts) {
|
|
16391
16867
|
return {
|
|
16392
|
-
|
|
16868
|
+
next: async () => {
|
|
16869
|
+
const waiting = await receive(opts);
|
|
16870
|
+
const goalId = waiting.claimable?.goalId ?? waiting.review?.[0]?.goalId;
|
|
16871
|
+
return goalId ? getGoal(goalId, opts) : { state: "idle", message: "" };
|
|
16872
|
+
},
|
|
16393
16873
|
read: (goalId) => getGoal(goalId, opts),
|
|
16394
|
-
|
|
16395
|
-
|
|
16874
|
+
complete: async (goalId, revision) => {
|
|
16875
|
+
const done = await manageGoals({ changes: [{ kind: "state", goalId, state: "completed", ...revision !== void 0 ? { revision } : {} }] }, opts);
|
|
16876
|
+
const result = done.results[0];
|
|
16877
|
+
if (result?.status !== "applied") throw new Error(`manage_goals failed: ${result?.error ?? "no result"}`);
|
|
16878
|
+
},
|
|
16879
|
+
ack: (eventIds) => acknowledge(eventIds, opts),
|
|
16880
|
+
// One read and back: the pump owns the waiting, so no wait is held here.
|
|
16396
16881
|
contact: (input) => contact(input, { ...opts, waits: false }),
|
|
16397
|
-
create: (input) =>
|
|
16398
|
-
|
|
16882
|
+
create: async (input) => {
|
|
16883
|
+
const made = await manageGoals({ changes: [{ kind: "create", ...input }] }, opts);
|
|
16884
|
+
const goal = made.goals.find((g) => g.goalId === made.results[0]?.goalId);
|
|
16885
|
+
if (!goal) throw new Error(`manage_goals create failed: ${made.results[0]?.error ?? "no goal"}`);
|
|
16886
|
+
return { goalId: goal.goalId, revision: goal.revision };
|
|
16887
|
+
},
|
|
16888
|
+
waiting: () => receive(opts)
|
|
16399
16889
|
};
|
|
16400
16890
|
}
|
|
16401
16891
|
function cancelAsks(state) {
|
|
@@ -16416,13 +16906,18 @@ function decisionFrom(result) {
|
|
|
16416
16906
|
}
|
|
16417
16907
|
async function ask(state, rail, input) {
|
|
16418
16908
|
if (!state.goal) return null;
|
|
16909
|
+
const { main, units: units3 } = cut(input.ask);
|
|
16419
16910
|
try {
|
|
16420
16911
|
const sent = await rail.contact({
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16912
|
+
questions: [{
|
|
16913
|
+
question: main,
|
|
16914
|
+
...units3 ? { units: units3 } : {},
|
|
16915
|
+
goalId: state.goal.id,
|
|
16916
|
+
options: input.options,
|
|
16917
|
+
workItBlocks: [{ blocked: { type: "goal", id: state.goal.id }, action: "complete", reason: "The agent cannot go on without this approval." }]
|
|
16918
|
+
}]
|
|
16424
16919
|
});
|
|
16425
|
-
if ("
|
|
16920
|
+
if ("sent" in sent) return null;
|
|
16426
16921
|
const entryId = sent.requestEntryIds[0];
|
|
16427
16922
|
if (!entryId) return null;
|
|
16428
16923
|
(state.seen ??= /* @__PURE__ */ new Set()).add(entryId);
|
|
@@ -16440,57 +16935,54 @@ async function askPermission(event, state, rail) {
|
|
|
16440
16935
|
]
|
|
16441
16936
|
}));
|
|
16442
16937
|
}
|
|
16443
|
-
async function finish(
|
|
16938
|
+
async function finish(state, rail, opts) {
|
|
16444
16939
|
const goal = state.goal;
|
|
16445
16940
|
if (!goal) return;
|
|
16446
|
-
const progress = result?.trim().slice(0, 1e4) || void 0;
|
|
16447
|
-
let revision = goal.revision;
|
|
16448
16941
|
try {
|
|
16449
16942
|
const held = await rail.read(goal.id);
|
|
16450
|
-
revision = held.revision ?? revision;
|
|
16451
16943
|
const owed = state.noted?.some((b) => b.goals.includes(goal.id));
|
|
16452
16944
|
if (held.state === "done" || held.state === "cancelled") {
|
|
16453
16945
|
if (!owed) state.goal = void 0;
|
|
16454
16946
|
return;
|
|
16455
16947
|
}
|
|
16456
16948
|
if (!held.openDecisionNeedIds?.length && !owed) {
|
|
16457
|
-
await rail.
|
|
16949
|
+
await rail.complete(goal.id, held.revision ?? goal.revision);
|
|
16458
16950
|
state.goal = void 0;
|
|
16459
|
-
return;
|
|
16460
16951
|
}
|
|
16461
16952
|
} catch (error) {
|
|
16462
16953
|
opts?.log?.(`\u26A0 the Goal did not close, still holding it: ${error.message}`);
|
|
16463
16954
|
}
|
|
16464
|
-
if (!progress) return;
|
|
16465
|
-
try {
|
|
16466
|
-
await rail.update(goal.id, { revision, changes: { progress }, reason: "The agent's turn ended; its Goal is not closed yet." });
|
|
16467
|
-
} catch (error) {
|
|
16468
|
-
opts?.log?.(`\u26A0 could not keep the turn as progress: ${error.message}`);
|
|
16469
|
-
}
|
|
16470
16955
|
}
|
|
16471
16956
|
async function cannotWork(report, state, rail, opts) {
|
|
16472
16957
|
state.resting = true;
|
|
16473
|
-
const
|
|
16958
|
+
const { main, units: units3 } = cut(report);
|
|
16959
|
+
const say = (goalId) => rail.contact({ updates: [{ message: main, ...units3 ? { units: units3 } : {}, goalIds: [goalId] }] });
|
|
16960
|
+
const own = async () => (await rail.create({ title: "Agent cannot work", outcome: main })).goalId;
|
|
16474
16961
|
try {
|
|
16475
16962
|
if (!state.goal) {
|
|
16476
|
-
const
|
|
16477
|
-
if (
|
|
16963
|
+
const next = await rail.next().catch(() => void 0);
|
|
16964
|
+
if (next?.goalId && next.revision !== void 0) state.goal = { id: next.goalId, revision: next.revision };
|
|
16478
16965
|
}
|
|
16479
16966
|
if (!state.goal) {
|
|
16480
|
-
await say();
|
|
16967
|
+
await say(await own());
|
|
16481
16968
|
return;
|
|
16482
16969
|
}
|
|
16483
16970
|
try {
|
|
16484
16971
|
await say(state.goal.id);
|
|
16485
16972
|
} catch (error) {
|
|
16486
|
-
if (!/
|
|
16487
|
-
opts?.log?.(`\u26A0 the held Goal ${state.goal.id.slice(0, 8)}
|
|
16488
|
-
await say();
|
|
16973
|
+
if (!/goal_not_found/.test(error.message)) throw error;
|
|
16974
|
+
opts?.log?.(`\u26A0 the held Goal ${state.goal.id.slice(0, 8)} is gone (goal_not_found) \u2014 saying it on a Goal of its own`);
|
|
16975
|
+
await say(await own());
|
|
16489
16976
|
}
|
|
16490
16977
|
} catch (error) {
|
|
16491
16978
|
opts?.log?.(`\u26A0 could not say the agent cannot work: ${error.message}`);
|
|
16492
16979
|
}
|
|
16493
16980
|
}
|
|
16981
|
+
async function callForResume(text, state, rail) {
|
|
16982
|
+
const { main, units: units3 } = cut(text);
|
|
16983
|
+
const goalId = state.goal?.id ?? (await rail.create({ title: "Usage limit reset", outcome: main })).goalId;
|
|
16984
|
+
return rail.contact({ updates: [{ message: main, ...units3 ? { units: units3 } : {}, goalIds: [goalId], userExplicitlyRequested: "call" }] });
|
|
16985
|
+
}
|
|
16494
16986
|
function conversationFull(result) {
|
|
16495
16987
|
return /prompt is too long/i.test(result ?? "");
|
|
16496
16988
|
}
|
|
@@ -16517,15 +17009,14 @@ async function afterTurn(event, state, rail, opts) {
|
|
|
16517
17009
|
${event.result?.trim() || "No result reported."}`, state, rail, opts);
|
|
16518
17010
|
return void 0;
|
|
16519
17011
|
}
|
|
16520
|
-
await finish(
|
|
17012
|
+
await finish(state, rail, opts);
|
|
16521
17013
|
state.typed = void 0;
|
|
16522
17014
|
state.resting = true;
|
|
16523
17015
|
return void 0;
|
|
16524
17016
|
}
|
|
16525
17017
|
|
|
16526
17018
|
// src/paigy/conversation.ts
|
|
16527
|
-
|
|
16528
|
-
var HARNESS_INSTRUCTION = "You are running as an unattended background harness agent; your user reaches you through Paigy and is not at this terminal. Nothing wakes you after your turn ends but the person, so wait on CI or a deploy inside the turn. The person never sees your turn's final message: it is kept as progress on this Goal. Anything the person needs \u2014 a question, a result, a blocker \u2014 you send through Paigy's contact tool, once; the harness sends nothing on your behalf. What the person says later arrives with the Goal it was said on named in front of it; answer them on that Goal (its id as the contact's parentId).";
|
|
17019
|
+
var HARNESS_INSTRUCTION = "You are running as an unattended background harness agent; your user reaches you through Paigy and is not at this terminal. Nothing wakes you after your turn ends but the person, so wait on CI or a deploy inside the turn. The person never sees your turn's final message, and it is not kept: report where the work stands with a contact update on this Goal (one the person does not need yet is kept as its progress). Anything the person needs \u2014 a question, a result, a blocker \u2014 you send through Paigy's contact tool, once; the harness sends nothing on your behalf. What the person says later arrives with the Goal it was said on named in front of it; answer them on that Goal (its id as the contact's goalId).";
|
|
16529
17020
|
var SAFETY_MS = 6e4;
|
|
16530
17021
|
var ASK_BEAT_MS = 5e3;
|
|
16531
17022
|
function beat() {
|
|
@@ -16625,43 +17116,48 @@ async function gather(state, rail, held) {
|
|
|
16625
17116
|
if (!looks.has(goalId)) looks.set(goalId, { via, ...title ? { title } : {}, ...since !== void 0 ? { since } : {} });
|
|
16626
17117
|
};
|
|
16627
17118
|
const waiting = await rail.waiting().catch(() => null);
|
|
16628
|
-
const listed = /* @__PURE__ */ new
|
|
17119
|
+
const listed = /* @__PURE__ */ new Map();
|
|
17120
|
+
const list = (goalId, eventId) => {
|
|
17121
|
+
const ids = listed.get(goalId) ?? [];
|
|
17122
|
+
if (eventId && !ids.includes(eventId)) ids.push(eventId);
|
|
17123
|
+
listed.set(goalId, ids);
|
|
17124
|
+
};
|
|
16629
17125
|
const announced = state.announced ??= /* @__PURE__ */ new Set();
|
|
16630
17126
|
if (waiting) {
|
|
16631
17127
|
for (const id3 of announced) if (!(waiting.assigned ?? []).some((g) => g.goalId === id3)) announced.delete(id3);
|
|
16632
17128
|
}
|
|
16633
17129
|
for (const g of waiting?.assigned ?? []) if (g.goalId !== state.goal.id && !announced.has(g.goalId)) look2(g.goalId, "assigned", g.title);
|
|
16634
17130
|
for (const g of waiting?.review ?? []) {
|
|
16635
|
-
|
|
17131
|
+
list(g.goalId, g.eventId);
|
|
16636
17132
|
look2(g.goalId, "waiting work", g.title);
|
|
16637
17133
|
}
|
|
16638
17134
|
for (const d of waiting?.deliveries ?? []) for (const id3 of d.goalIds) {
|
|
16639
|
-
|
|
17135
|
+
list(id3, d.deliveryId);
|
|
16640
17136
|
look2(id3, "waiting work");
|
|
16641
17137
|
}
|
|
16642
17138
|
const moved = movedBelow(held, state);
|
|
16643
17139
|
for (const { goal, since } of moved) look2(goal.goalId, "subtree", goal.title, since);
|
|
16644
17140
|
const heard = [];
|
|
16645
|
-
const
|
|
17141
|
+
const read3 = /* @__PURE__ */ new Set();
|
|
16646
17142
|
for (const [goalId, { since, ...found }] of looks) {
|
|
16647
17143
|
let brief = found.brief;
|
|
16648
17144
|
if (!brief) {
|
|
16649
17145
|
try {
|
|
16650
17146
|
brief = await rail.read(goalId);
|
|
16651
17147
|
} catch (error) {
|
|
16652
|
-
if (error.status === 404)
|
|
17148
|
+
if (error.status === 404) read3.add(goalId);
|
|
16653
17149
|
continue;
|
|
16654
17150
|
}
|
|
16655
17151
|
}
|
|
16656
|
-
|
|
17152
|
+
read3.add(goalId);
|
|
16657
17153
|
const fresh = !brief.reviewSince && (found.via === "waiting work" || found.via === "assigned") ? sinceTheWork(brief) : { after: -1 };
|
|
16658
17154
|
if (found.via === "assigned") announced.add(goalId);
|
|
16659
17155
|
heard.push({ goalId, ...found, brief, words: unheard(brief, state, brief.reviewSince ? null : since ?? null, fresh.after, brief.reviewSince), ...fresh.note ? { note: fresh.note } : {} });
|
|
16660
17156
|
}
|
|
16661
|
-
for (const { goal, since } of moved) if (
|
|
17157
|
+
for (const { goal, since } of moved) if (read3.has(goal.goalId)) state.below.goals.set(goal.goalId, { since, at: goal.updatedAt ?? null });
|
|
16662
17158
|
return { heard, listed };
|
|
16663
17159
|
}
|
|
16664
|
-
var GIVEN = "This Goal was given to you, and nobody has started it. Carry on with what you're doing unless it changes that;
|
|
17160
|
+
var GIVEN = "This Goal was given to you, and nobody has started it. Carry on with what you're doing unless it changes that; your first write to it (a contact update naming it) starts it when you take it up.";
|
|
16665
17161
|
var says = (h) => h.words.length > 0 || h.via === "assigned";
|
|
16666
17162
|
function whatItIs(h) {
|
|
16667
17163
|
const outcome2 = h.brief.outcome?.trim() ?? "";
|
|
@@ -16691,7 +17187,10 @@ async function hear(session, state, rail, held, opts) {
|
|
|
16691
17187
|
if (opts?.tally) opts.tally.announced += goals.length;
|
|
16692
17188
|
}
|
|
16693
17189
|
}
|
|
16694
|
-
for (const h of heard)
|
|
17190
|
+
for (const h of heard) {
|
|
17191
|
+
const events = listed.get(h.goalId);
|
|
17192
|
+
if (events) await acknowledge2(h.goalId, h.brief, events, state, rail, log2);
|
|
17193
|
+
}
|
|
16695
17194
|
}
|
|
16696
17195
|
var NOTED = "New from the person while you work. Carry on unless it changes what you're doing: it comes back to you as your next turn when this one ends.";
|
|
16697
17196
|
var SEEN_MID_TURN = "While you worked, the person said this, and you saw it mid-task. Act on it now, unless you already have.";
|
|
@@ -16737,15 +17236,16 @@ function settle(session, state, inbox, log2) {
|
|
|
16737
17236
|
send2(session, state, [...seen.length ? [SEEN_MID_TURN, ...seen.map((b) => b.words)] : [], ...unseen.map((b) => b.words)].join("\n\n"));
|
|
16738
17237
|
log2?.(`\u25B8 typed the person's words from ${short([...new Set(batches.flatMap((b) => b.goals))])} (noted mid-turn: ${seen.length} seen by the hook, ${unseen.length} not)`);
|
|
16739
17238
|
}
|
|
16740
|
-
async function
|
|
17239
|
+
async function acknowledge2(goalId, brief, events, state, rail, log2) {
|
|
17240
|
+
const ids = [.../* @__PURE__ */ new Set([...brief.reviewEventId ? [brief.reviewEventId] : [], ...events])];
|
|
17241
|
+
if (!ids.length) return;
|
|
16741
17242
|
const acked = state.acked ??= /* @__PURE__ */ new Map();
|
|
16742
|
-
|
|
16743
|
-
acked.
|
|
17243
|
+
const key3 = ids.join(",");
|
|
17244
|
+
if (acked.get(goalId) === key3) return;
|
|
17245
|
+
acked.set(goalId, key3);
|
|
16744
17246
|
try {
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
acked.set(goalId, done.revision);
|
|
16748
|
-
if (state.goal?.id === goalId && done.revision !== void 0) state.goal.revision = done.revision;
|
|
17247
|
+
const refused = (await rail.ack(ids)).filter((a) => a.refused);
|
|
17248
|
+
if (refused.length) log2?.(`\u26A0 could not acknowledge ${goalId.slice(0, 8)}: ${refused.map((a) => `${a.eventId.slice(0, 8)} ${a.refused}`).join(", ")} \u2014 its words are typed in; it stays listed until acknowledged`);
|
|
16749
17249
|
} catch (error) {
|
|
16750
17250
|
log2?.(`\u26A0 could not acknowledge ${goalId.slice(0, 8)}: ${error.message} \u2014 its words are typed in; it stays listed until acknowledged`);
|
|
16751
17251
|
}
|
|
@@ -16771,13 +17271,13 @@ async function look(session, state, rail, opts) {
|
|
|
16771
17271
|
if (state.resting && state.noted?.length && !state.asks?.size) return settle(session, state, opts?.inbox, opts?.log);
|
|
16772
17272
|
if (!state.goal) {
|
|
16773
17273
|
if (!state.resting) return;
|
|
16774
|
-
let brief2 = await rail.
|
|
17274
|
+
let brief2 = await rail.next();
|
|
16775
17275
|
if (!brief2.goalId || brief2.revision === void 0) {
|
|
16776
17276
|
if (opts?.initialPrompt?.trim()) {
|
|
16777
17277
|
const prompt = opts.initialPrompt.trim();
|
|
16778
17278
|
opts.initialPrompt = void 0;
|
|
16779
|
-
const admitted = await rail.create({
|
|
16780
|
-
brief2 = await rail.
|
|
17279
|
+
const admitted = await rail.create({ title: prompt.split(/\s+/).slice(0, 5).join(" ").slice(0, 80), outcome: prompt });
|
|
17280
|
+
brief2 = await rail.next();
|
|
16781
17281
|
if (!brief2.goalId || brief2.revision === void 0) {
|
|
16782
17282
|
state.goal = { id: admitted.goalId, revision: admitted.revision };
|
|
16783
17283
|
send2(session, state, [HARNESS_INSTRUCTION, named(admitted.goalId, void 0, { outcome: prompt }), prompt].join("\n\n"));
|
|
@@ -16799,7 +17299,7 @@ async function look(session, state, rail, opts) {
|
|
|
16799
17299
|
const earlier = left ? `(${left} earlier entries on this Goal are left out or shortened; get_goal with history: true reads them in full.)` : "";
|
|
16800
17300
|
const fullText = [outcome2, earlier, ...said].filter(Boolean).join("\n\n");
|
|
16801
17301
|
send2(session, state, [HARNESS_INSTRUCTION, named(brief2.goalId, void 0, brief2), fullText].filter(Boolean).join("\n\n"));
|
|
16802
|
-
if (ended(brief2.state)) await
|
|
17302
|
+
if (ended(brief2.state)) await acknowledge2(brief2.goalId, brief2, [], state, rail, opts?.log);
|
|
16803
17303
|
return;
|
|
16804
17304
|
}
|
|
16805
17305
|
const brief = await rail.read(state.goal.id);
|
|
@@ -16867,9 +17367,9 @@ async function pump(session, state, rail, opts) {
|
|
|
16867
17367
|
}
|
|
16868
17368
|
if (!await opts.onIdle()) return;
|
|
16869
17369
|
if (now() - since >= BEAT_REPORT_MS) {
|
|
16870
|
-
const
|
|
16871
|
-
if (
|
|
16872
|
-
opts.log?.(`\u25B9 beats: ${
|
|
17370
|
+
const read3 = counts.wake + counts.rest + counts.safety;
|
|
17371
|
+
if (read3 || counts.skipped || tally.noted || tally.seen || tally.announced) {
|
|
17372
|
+
opts.log?.(`\u25B9 beats: ${read3} read (${counts.wake} wake, ${counts.rest} rest, ${counts.safety} safety), ${counts.skipped} wake mid-turn skipped, ${tally.noted} noted mid-turn, ${tally.seen} seen by the hook, ${tally.announced} assigned announced${tally.paused ? `, ${tally.paused} held by the usage limit` : ""}`);
|
|
16873
17373
|
}
|
|
16874
17374
|
counts.wake = counts.rest = counts.safety = counts.skipped = tally.noted = tally.seen = tally.announced = tally.paused = 0;
|
|
16875
17375
|
since = now();
|
|
@@ -16887,7 +17387,7 @@ function runHarness(opts) {
|
|
|
16887
17387
|
const clock4 = beat();
|
|
16888
17388
|
let session = null;
|
|
16889
17389
|
const speaksAs = opts.session ?? opts.resume ?? sessionId();
|
|
16890
|
-
const instance =
|
|
17390
|
+
const instance = randomUUID4();
|
|
16891
17391
|
const rail = railFor({ token: opts.token, reach: reachAs(speaksAs, instance) });
|
|
16892
17392
|
let inbox;
|
|
16893
17393
|
if (opts.harness === "claude") {
|
|
@@ -16901,7 +17401,7 @@ function runHarness(opts) {
|
|
|
16901
17401
|
function runningAgain() {
|
|
16902
17402
|
state.carriedOn = false;
|
|
16903
17403
|
opts.log("\u25B6 running again after the limit");
|
|
16904
|
-
LIMITS.running(name, (text) =>
|
|
17404
|
+
LIMITS.running(name, (text) => callForResume(text, state, rail), opts.log);
|
|
16905
17405
|
}
|
|
16906
17406
|
async function handle(event) {
|
|
16907
17407
|
if (event.kind === "error") {
|
|
@@ -17114,7 +17614,7 @@ async function waitingWork(token, session) {
|
|
|
17114
17614
|
if (quiet !== void 0 && Date.now() < quiet) return { skip: BOUND_SKIP };
|
|
17115
17615
|
let found;
|
|
17116
17616
|
try {
|
|
17117
|
-
found = await
|
|
17617
|
+
found = await receive({ token, ...session ? { reach: reachAs(session) } : {} });
|
|
17118
17618
|
} catch (error) {
|
|
17119
17619
|
if (error instanceof RevokedError) return { skip: "was revoked: its credential is gone for good", revoked: true };
|
|
17120
17620
|
if (error instanceof ApiError && error.body?.error === "identity_session_mismatch") {
|
|
@@ -17353,7 +17853,7 @@ function startHost(opts) {
|
|
|
17353
17853
|
skip(`has a full conversation (${resumes.slice(0, 8)}) \u2014 not resuming it; its Goals go to the dead-agent handoff`);
|
|
17354
17854
|
return;
|
|
17355
17855
|
}
|
|
17356
|
-
const
|
|
17856
|
+
const read3 = async () => {
|
|
17357
17857
|
const got = await waitingWork(token, identity.sessionId ?? owner?.sessionId);
|
|
17358
17858
|
if (!("skip" in got)) return got;
|
|
17359
17859
|
if (got.revoked) {
|
|
@@ -17365,7 +17865,7 @@ function startHost(opts) {
|
|
|
17365
17865
|
return null;
|
|
17366
17866
|
};
|
|
17367
17867
|
if (decided.why === "presence") {
|
|
17368
|
-
found = await
|
|
17868
|
+
found = await read3() ?? void 0;
|
|
17369
17869
|
if (!found) return;
|
|
17370
17870
|
const since = found.since;
|
|
17371
17871
|
decided = wakeAction(false, Date.now(), seenByServer, { ...here2, ...since ? { movedAt: Date.parse(since) } : {} });
|
|
@@ -17391,7 +17891,7 @@ function startHost(opts) {
|
|
|
17391
17891
|
skip("has no granted folder to wake in");
|
|
17392
17892
|
return;
|
|
17393
17893
|
}
|
|
17394
|
-
found ??= await
|
|
17894
|
+
found ??= await read3() ?? void 0;
|
|
17395
17895
|
if (!found) return;
|
|
17396
17896
|
if (runs.has(key3)) {
|
|
17397
17897
|
skip("was started by the wake that read it first");
|
|
@@ -17527,7 +18027,7 @@ function startHost(opts) {
|
|
|
17527
18027
|
return {
|
|
17528
18028
|
name,
|
|
17529
18029
|
// The slot key rides along so the window's agent page can speak AS this agent —
|
|
17530
|
-
// its pending reads (`
|
|
18030
|
+
// its pending reads (`receive`, the same pure read the sweep does) need the
|
|
17531
18031
|
// slot's own token, and the display name is not a key.
|
|
17532
18032
|
slot,
|
|
17533
18033
|
tokenId: id3.tokenId,
|
|
@@ -17646,7 +18146,7 @@ ipcMain.handle("paigy:agent", async (_e, name) => {
|
|
|
17646
18146
|
const entry = (host?.roster() ?? readHostState()?.roster ?? []).find((a) => a.name === name);
|
|
17647
18147
|
if (!entry) return null;
|
|
17648
18148
|
const token = entry.slot ? readToken(entry.slot) : "";
|
|
17649
|
-
const open = token ? await
|
|
18149
|
+
const open = token ? await receive({ token }).then((r) => r.deliveries).catch(() => null) : null;
|
|
17650
18150
|
return {
|
|
17651
18151
|
...entry,
|
|
17652
18152
|
pending: open ? {
|