@paigy/harness 0.3.35 → 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.
Files changed (3) hide show
  1. package/dist/cli.js +767 -381
  2. package/dist/main.js +693 -312
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5285,7 +5285,7 @@ var require_util = __commonJS({
5285
5285
  }
5286
5286
  return controller.desiredSize > 0;
5287
5287
  },
5288
- async cancel(reason) {
5288
+ async cancel(reason3) {
5289
5289
  await iterator.return();
5290
5290
  },
5291
5291
  type: "bytes"
@@ -5613,12 +5613,12 @@ var require_diagnostics = __commonJS({
5613
5613
  websocketDebuglog("connection opened %s%s", address, port ? `:${port}` : "");
5614
5614
  });
5615
5615
  diagnosticsChannel.channel("undici:websocket:close").subscribe((evt) => {
5616
- const { websocket, code, reason } = evt;
5616
+ const { websocket, code, reason: reason3 } = evt;
5617
5617
  websocketDebuglog(
5618
5618
  "closed connection to %s - %s %s",
5619
5619
  websocket.url,
5620
5620
  code,
5621
- reason
5621
+ reason3
5622
5622
  );
5623
5623
  });
5624
5624
  diagnosticsChannel.channel("undici:websocket:socket_error").subscribe((err) => {
@@ -7480,25 +7480,25 @@ var require_data_url = __commonJS({
7480
7480
  function isHTTPWhiteSpace(char) {
7481
7481
  return char === 13 || char === 10 || char === 9 || char === 32;
7482
7482
  }
7483
- function removeHTTPWhitespace(str3, leading = true, trailing = true) {
7484
- return removeChars(str3, leading, trailing, isHTTPWhiteSpace);
7483
+ function removeHTTPWhitespace(str5, leading = true, trailing = true) {
7484
+ return removeChars(str5, leading, trailing, isHTTPWhiteSpace);
7485
7485
  }
7486
7486
  function isASCIIWhitespace(char) {
7487
7487
  return char === 13 || char === 10 || char === 9 || char === 12 || char === 32;
7488
7488
  }
7489
- function removeASCIIWhitespace(str3, leading = true, trailing = true) {
7490
- return removeChars(str3, leading, trailing, isASCIIWhitespace);
7489
+ function removeASCIIWhitespace(str5, leading = true, trailing = true) {
7490
+ return removeChars(str5, leading, trailing, isASCIIWhitespace);
7491
7491
  }
7492
- function removeChars(str3, leading, trailing, predicate) {
7492
+ function removeChars(str5, leading, trailing, predicate) {
7493
7493
  let lead = 0;
7494
- let trail = str3.length - 1;
7494
+ let trail = str5.length - 1;
7495
7495
  if (leading) {
7496
- while (lead < str3.length && predicate(str3.charCodeAt(lead))) lead++;
7496
+ while (lead < str5.length && predicate(str5.charCodeAt(lead))) lead++;
7497
7497
  }
7498
7498
  if (trailing) {
7499
- while (trail > 0 && predicate(str3.charCodeAt(trail))) trail--;
7499
+ while (trail > 0 && predicate(str5.charCodeAt(trail))) trail--;
7500
7500
  }
7501
- return lead === 0 && trail === str3.length - 1 ? str3 : str3.slice(lead, trail + 1);
7501
+ return lead === 0 && trail === str5.length - 1 ? str5 : str5.slice(lead, trail + 1);
7502
7502
  }
7503
7503
  function isomorphicDecode(input) {
7504
7504
  const length = input.length;
@@ -9417,7 +9417,7 @@ var require_body = __commonJS({
9417
9417
  const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, "0")}`;
9418
9418
  const prefix = `--${boundary}\r
9419
9419
  Content-Disposition: form-data`;
9420
- const escape = (str3) => str3.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22");
9420
+ const escape = (str5) => str5.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22");
9421
9421
  const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, "\r\n");
9422
9422
  const blobParts = [];
9423
9423
  const rn = new Uint8Array([13, 10]);
@@ -9505,7 +9505,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9505
9505
  }
9506
9506
  return controller.desiredSize > 0;
9507
9507
  },
9508
- async cancel(reason) {
9508
+ async cancel(reason3) {
9509
9509
  await iterator.return();
9510
9510
  },
9511
9511
  type: "bytes"
@@ -9985,7 +9985,7 @@ var require_client_h1 = __commonJS({
9985
9985
  util2.destroy(this.socket, new HeadersOverflowError());
9986
9986
  }
9987
9987
  }
9988
- onUpgrade(head) {
9988
+ onUpgrade(head2) {
9989
9989
  const { upgrade, client, socket, headers, statusCode } = this;
9990
9990
  assert(upgrade);
9991
9991
  assert(client[kSocket] === socket);
@@ -10000,7 +10000,7 @@ var require_client_h1 = __commonJS({
10000
10000
  this.shouldKeepAlive = null;
10001
10001
  this.headers = [];
10002
10002
  this.headersSize = 0;
10003
- socket.unshift(head);
10003
+ socket.unshift(head2);
10004
10004
  socket[kParser].destroy();
10005
10005
  socket[kParser] = null;
10006
10006
  socket[kClient] = null;
@@ -12993,12 +12993,12 @@ var require_retry_handler = __commonJS({
12993
12993
  this.end = null;
12994
12994
  this.etag = null;
12995
12995
  this.resume = null;
12996
- this.handler.onConnect((reason) => {
12996
+ this.handler.onConnect((reason3) => {
12997
12997
  this.aborted = true;
12998
12998
  if (this.abort) {
12999
- this.abort(reason);
12999
+ this.abort(reason3);
13000
13000
  } else {
13001
- this.reason = reason;
13001
+ this.reason = reason3;
13002
13002
  }
13003
13003
  });
13004
13004
  }
@@ -15383,9 +15383,9 @@ var require_dump = __commonJS({
15383
15383
  this.#abort = abort;
15384
15384
  this.#handler.onConnect(this.#customAbort.bind(this));
15385
15385
  }
15386
- #customAbort(reason) {
15386
+ #customAbort(reason3) {
15387
15387
  this.#aborted = true;
15388
- this.#reason = reason;
15388
+ this.#reason = reason3;
15389
15389
  }
15390
15390
  // TODO: will require adjustment after new hooks are out
15391
15391
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
@@ -16412,13 +16412,13 @@ var require_response = __commonJS({
16412
16412
  urlList: init?.urlList ? [...init.urlList] : []
16413
16413
  };
16414
16414
  }
16415
- function makeNetworkError(reason) {
16416
- const isError = isErrorLike(reason);
16415
+ function makeNetworkError(reason3) {
16416
+ const isError = isErrorLike(reason3);
16417
16417
  return makeResponse({
16418
16418
  type: "error",
16419
16419
  status: 0,
16420
- error: isError ? reason : new Error(reason ? String(reason) : reason),
16421
- aborted: reason && reason.name === "AbortError"
16420
+ error: isError ? reason3 : new Error(reason3 ? String(reason3) : reason3),
16421
+ aborted: reason3 && reason3.name === "AbortError"
16422
16422
  });
16423
16423
  }
16424
16424
  function isNetworkError(response) {
@@ -17400,13 +17400,13 @@ var require_fetch = __commonJS({
17400
17400
  this.dump = false;
17401
17401
  this.state = "ongoing";
17402
17402
  }
17403
- terminate(reason) {
17403
+ terminate(reason3) {
17404
17404
  if (this.state !== "ongoing") {
17405
17405
  return;
17406
17406
  }
17407
17407
  this.state = "terminated";
17408
- this.connection?.destroy(reason);
17409
- this.emit("terminated", reason);
17408
+ this.connection?.destroy(reason3);
17409
+ this.emit("terminated", reason3);
17410
17410
  }
17411
17411
  // https://fetch.spec.whatwg.org/#fetch-controller-abort
17412
17412
  abort(error) {
@@ -17685,7 +17685,7 @@ var require_fetch = __commonJS({
17685
17685
  fetchParams.controller.dump = true;
17686
17686
  }
17687
17687
  if (request.integrity) {
17688
- const processBodyError = (reason) => fetchFinale(fetchParams, makeNetworkError(reason));
17688
+ const processBodyError = (reason3) => fetchFinale(fetchParams, makeNetworkError(reason3));
17689
17689
  if (request.responseTainting === "opaque" || response.body == null) {
17690
17690
  processBodyError(response.error);
17691
17691
  return;
@@ -18163,9 +18163,9 @@ var require_fetch = __commonJS({
18163
18163
  const pullAlgorithm = async () => {
18164
18164
  await fetchParams.controller.resume();
18165
18165
  };
18166
- const cancelAlgorithm = (reason) => {
18166
+ const cancelAlgorithm = (reason3) => {
18167
18167
  if (!isCancelled(fetchParams)) {
18168
- fetchParams.controller.abort(reason);
18168
+ fetchParams.controller.abort(reason3);
18169
18169
  }
18170
18170
  };
18171
18171
  const stream = new ReadableStream(
@@ -18176,8 +18176,8 @@ var require_fetch = __commonJS({
18176
18176
  async pull(controller) {
18177
18177
  await pullAlgorithm(controller);
18178
18178
  },
18179
- async cancel(reason) {
18180
- await cancelAlgorithm(reason);
18179
+ async cancel(reason3) {
18180
+ await cancelAlgorithm(reason3);
18181
18181
  },
18182
18182
  type: "bytes"
18183
18183
  }
@@ -18226,7 +18226,7 @@ var require_fetch = __commonJS({
18226
18226
  }
18227
18227
  }
18228
18228
  };
18229
- function onAborted(reason) {
18229
+ function onAborted(reason3) {
18230
18230
  if (isAborted2(fetchParams)) {
18231
18231
  response.aborted = true;
18232
18232
  if (isReadable(stream)) {
@@ -18237,7 +18237,7 @@ var require_fetch = __commonJS({
18237
18237
  } else {
18238
18238
  if (isReadable(stream)) {
18239
18239
  fetchParams.controller.controller.error(new TypeError("terminated", {
18240
- cause: isErrorLike(reason) ? reason : void 0
18240
+ cause: isErrorLike(reason3) ? reason3 : void 0
18241
18241
  }));
18242
18242
  }
18243
18243
  }
@@ -20265,9 +20265,9 @@ var require_cookies = __commonJS({
20265
20265
  webidl.argumentLengthCheck(arguments, 2, "setCookie");
20266
20266
  webidl.brandCheck(headers, Headers, { strict: false });
20267
20267
  cookie = webidl.converters.Cookie(cookie);
20268
- const str3 = stringify(cookie);
20269
- if (str3) {
20270
- headers.append("Set-Cookie", str3);
20268
+ const str5 = stringify(cookie);
20269
+ if (str5) {
20270
+ headers.append("Set-Cookie", str5);
20271
20271
  }
20272
20272
  }
20273
20273
  webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([
@@ -20778,16 +20778,16 @@ var require_util7 = __commonJS({
20778
20778
  }
20779
20779
  return code >= 3e3 && code <= 4999;
20780
20780
  }
20781
- function failWebsocketConnection(ws, reason) {
20781
+ function failWebsocketConnection(ws, reason3) {
20782
20782
  const { [kController]: controller, [kResponse]: response } = ws;
20783
20783
  controller.abort();
20784
20784
  if (response?.socket && !response.socket.destroyed) {
20785
20785
  response.socket.destroy();
20786
20786
  }
20787
- if (reason) {
20787
+ if (reason3) {
20788
20788
  fireEvent("error", ws, (type, init) => new ErrorEvent(type, init), {
20789
- error: new Error(reason),
20790
- message: reason
20789
+ error: new Error(reason3),
20790
+ message: reason3
20791
20791
  });
20792
20792
  }
20793
20793
  }
@@ -21045,7 +21045,7 @@ var require_connection = __commonJS({
21045
21045
  });
21046
21046
  return controller;
21047
21047
  }
21048
- function closeWebSocketConnection(ws, code, reason, reasonByteLength) {
21048
+ function closeWebSocketConnection(ws, code, reason3, reasonByteLength) {
21049
21049
  if (isClosing(ws) || isClosed(ws)) {
21050
21050
  } else if (!isEstablished(ws)) {
21051
21051
  failWebsocketConnection(ws, "Connection was closed before it was established.");
@@ -21053,13 +21053,13 @@ var require_connection = __commonJS({
21053
21053
  } else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) {
21054
21054
  ws[kSentClose] = sentCloseFrameState.PROCESSING;
21055
21055
  const frame3 = new WebsocketFrameSend();
21056
- if (code !== void 0 && reason === void 0) {
21056
+ if (code !== void 0 && reason3 === void 0) {
21057
21057
  frame3.frameData = Buffer.allocUnsafe(2);
21058
21058
  frame3.frameData.writeUInt16BE(code, 0);
21059
- } else if (code !== void 0 && reason !== void 0) {
21059
+ } else if (code !== void 0 && reason3 !== void 0) {
21060
21060
  frame3.frameData = Buffer.allocUnsafe(2 + reasonByteLength);
21061
21061
  frame3.frameData.writeUInt16BE(code, 0);
21062
- frame3.frameData.write(reason, 2, "utf-8");
21062
+ frame3.frameData.write(reason3, 2, "utf-8");
21063
21063
  } else {
21064
21064
  frame3.frameData = emptyBuffer;
21065
21065
  }
@@ -21084,11 +21084,11 @@ var require_connection = __commonJS({
21084
21084
  response.socket.off("error", onSocketError);
21085
21085
  const wasClean = ws[kSentClose] === sentCloseFrameState.SENT && ws[kReceivedClose];
21086
21086
  let code = 1005;
21087
- let reason = "";
21087
+ let reason3 = "";
21088
21088
  const result = ws[kByteParser].closingInfo;
21089
21089
  if (result && !result.error) {
21090
21090
  code = result.code ?? 1005;
21091
- reason = result.reason;
21091
+ reason3 = result.reason;
21092
21092
  } else if (!ws[kReceivedClose]) {
21093
21093
  code = 1006;
21094
21094
  }
@@ -21096,13 +21096,13 @@ var require_connection = __commonJS({
21096
21096
  fireEvent("close", ws, (type, init) => new CloseEvent(type, init), {
21097
21097
  wasClean,
21098
21098
  code,
21099
- reason
21099
+ reason: reason3
21100
21100
  });
21101
21101
  if (channels.close.hasSubscribers) {
21102
21102
  channels.close.publish({
21103
21103
  websocket: ws,
21104
21104
  code,
21105
- reason
21105
+ reason: reason3
21106
21106
  });
21107
21107
  }
21108
21108
  }
@@ -21225,9 +21225,9 @@ var require_receiver = __commonJS({
21225
21225
  var { closeWebSocketConnection } = require_connection();
21226
21226
  var { PerMessageDeflate } = require_permessage_deflate();
21227
21227
  var { MessageSizeExceededError } = require_errors();
21228
- function failWebsocketConnectionWithCode(ws, code, reason) {
21229
- closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason));
21230
- failWebsocketConnection(ws, reason);
21228
+ function failWebsocketConnectionWithCode(ws, code, reason3) {
21229
+ closeWebSocketConnection(ws, code, reason3, Buffer.byteLength(reason3));
21230
+ failWebsocketConnection(ws, reason3);
21231
21231
  }
21232
21232
  var ByteParser = class extends Writable {
21233
21233
  #buffers = [];
@@ -21497,16 +21497,16 @@ var require_receiver = __commonJS({
21497
21497
  if (code !== void 0 && !isValidStatusCode(code)) {
21498
21498
  return { code: 1002, reason: "Invalid status code", error: true };
21499
21499
  }
21500
- let reason = data.subarray(2);
21501
- if (reason[0] === 239 && reason[1] === 187 && reason[2] === 191) {
21502
- reason = reason.subarray(3);
21500
+ let reason3 = data.subarray(2);
21501
+ if (reason3[0] === 239 && reason3[1] === 187 && reason3[2] === 191) {
21502
+ reason3 = reason3.subarray(3);
21503
21503
  }
21504
21504
  try {
21505
- reason = utf8Decode(reason);
21505
+ reason3 = utf8Decode(reason3);
21506
21506
  } catch {
21507
21507
  return { code: 1007, reason: "Invalid UTF-8", error: true };
21508
21508
  }
21509
- return { code, reason, error: false };
21509
+ return { code, reason: reason3, error: false };
21510
21510
  }
21511
21511
  /**
21512
21512
  * Parses control frames.
@@ -21521,9 +21521,9 @@ var require_receiver = __commonJS({
21521
21521
  }
21522
21522
  this.#info.closeInfo = this.parseCloseBody(body);
21523
21523
  if (this.#info.closeInfo.error) {
21524
- const { code, reason } = this.#info.closeInfo;
21525
- closeWebSocketConnection(this.ws, code, reason, reason.length);
21526
- failWebsocketConnection(this.ws, reason);
21524
+ const { code, reason: reason3 } = this.#info.closeInfo;
21525
+ closeWebSocketConnection(this.ws, code, reason3, reason3.length);
21526
+ failWebsocketConnection(this.ws, reason3);
21527
21527
  return false;
21528
21528
  }
21529
21529
  if (this.ws[kSentClose] !== sentCloseFrameState.SENT) {
@@ -21760,14 +21760,14 @@ var require_websocket = __commonJS({
21760
21760
  * @param {number|undefined} code
21761
21761
  * @param {string|undefined} reason
21762
21762
  */
21763
- close(code = void 0, reason = void 0) {
21763
+ close(code = void 0, reason3 = void 0) {
21764
21764
  webidl.brandCheck(this, _WebSocket);
21765
21765
  const prefix = "WebSocket.close";
21766
21766
  if (code !== void 0) {
21767
21767
  code = webidl.converters["unsigned short"](code, prefix, "code", { clamp: true });
21768
21768
  }
21769
- if (reason !== void 0) {
21770
- reason = webidl.converters.USVString(reason, prefix, "reason");
21769
+ if (reason3 !== void 0) {
21770
+ reason3 = webidl.converters.USVString(reason3, prefix, "reason");
21771
21771
  }
21772
21772
  if (code !== void 0) {
21773
21773
  if (code !== 1e3 && (code < 3e3 || code > 4999)) {
@@ -21775,8 +21775,8 @@ var require_websocket = __commonJS({
21775
21775
  }
21776
21776
  }
21777
21777
  let reasonByteLength = 0;
21778
- if (reason !== void 0) {
21779
- reasonByteLength = Buffer.byteLength(reason);
21778
+ if (reason3 !== void 0) {
21779
+ reasonByteLength = Buffer.byteLength(reason3);
21780
21780
  if (reasonByteLength > 123) {
21781
21781
  throw new DOMException(
21782
21782
  `Reason must be less than 123 bytes; received ${reasonByteLength}`,
@@ -21784,7 +21784,7 @@ var require_websocket = __commonJS({
21784
21784
  );
21785
21785
  }
21786
21786
  }
21787
- closeWebSocketConnection(this, code, reason, reasonByteLength);
21787
+ closeWebSocketConnection(this, code, reason3, reasonByteLength);
21788
21788
  }
21789
21789
  /**
21790
21790
  * @see https://websockets.spec.whatwg.org/#dom-websocket-send
@@ -22749,13 +22749,11 @@ import { createHash, randomUUID } from "crypto";
22749
22749
  import { readFileSync } from "fs";
22750
22750
  import { homedir as homedir2 } from "os";
22751
22751
  import { join as join2 } from "path";
22752
- import { randomUUID as randomUUID3 } from "crypto";
22753
- import { setTimeout as sleep2 } from "timers/promises";
22754
22752
  import { randomUUID as randomUUID2 } from "crypto";
22753
+ import { setTimeout as sleep2 } from "timers/promises";
22755
22754
  import { closeSync, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
22756
22755
  import { homedir as homedir22 } from "os";
22757
22756
  import { join as join22 } from "path";
22758
- import { execFileSync as execFileSync2 } from "child_process";
22759
22757
  async function proxy() {
22760
22758
  if (!PROXY_ENV.some((k) => process.env[k])) return void 0;
22761
22759
  return agent ??= new (await Promise.resolve().then(() => __toESM(require_undici(), 1))).EnvHttpProxyAgent();
@@ -23690,6 +23688,54 @@ function mcpInputSchema(s) {
23690
23688
  delete schema.$schema;
23691
23689
  return draft2020(schema);
23692
23690
  }
23691
+ function capRefusals(text, units22, field) {
23692
+ const out = [];
23693
+ const name = field === "text" ? "answer" : field;
23694
+ if (text.length > ASK_MAX) out.push({
23695
+ refusal: "ask_too_long",
23696
+ length: text.length,
23697
+ cap: ASK_MAX,
23698
+ path: field === "text" ? ["answer", "text"] : [field],
23699
+ 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.`
23700
+ });
23701
+ const all = units22 ?? [];
23702
+ if (all.length > UNITS_MAX) out.push({
23703
+ refusal: "too_many_units",
23704
+ length: all.length,
23705
+ cap: UNITS_MAX,
23706
+ path: ["units"],
23707
+ message: `too_many_units: ${all.length} units; the cap is ${UNITS_MAX}. Keep only the context this ${name} needs.`
23708
+ });
23709
+ all.forEach((unit, index) => {
23710
+ if (unit.title.length > UNIT_TITLE_MAX) out.push({
23711
+ refusal: "unit_title_too_long",
23712
+ index,
23713
+ length: unit.title.length,
23714
+ cap: UNIT_TITLE_MAX,
23715
+ path: ["units", index, "title"],
23716
+ message: `unit_title_too_long: units[${index}]'s title is ${unit.title.length} characters; the cap is ${UNIT_TITLE_MAX}.`
23717
+ });
23718
+ if (unit.body.length > UNIT_BODY_MAX) out.push({
23719
+ refusal: "unit_body_too_long",
23720
+ index,
23721
+ length: unit.body.length,
23722
+ cap: UNIT_BODY_MAX,
23723
+ path: ["units", index, "body"],
23724
+ 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.`
23725
+ });
23726
+ });
23727
+ return out;
23728
+ }
23729
+ function refuseOverCaps(text, units22, field, ctx) {
23730
+ for (const { path, message, ...params } of capRefusals(text, units22, field)) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message, params });
23731
+ }
23732
+ function entryUnit(entry) {
23733
+ if (entry.kind !== "contribution" || !entry.aboutId) return null;
23734
+ const plain = entry.content && "plain" in entry.content ? entry.content.plain : null;
23735
+ if (!plain || typeof plain !== "object") return null;
23736
+ const { title, text } = plain;
23737
+ return typeof title === "string" && typeof text === "string" ? { title, body: text } : null;
23738
+ }
23693
23739
  function sessionSlot(sessionId2) {
23694
23740
  const id22 = sessionId2 ?? sessionId();
23695
23741
  return `session:${id22.slice(0, 8)}`;
@@ -23815,12 +23861,6 @@ async function manageGoals(input, opts = {}) {
23815
23861
  if (!res.ok) await fail("manage_goals", res);
23816
23862
  return await res.json();
23817
23863
  }
23818
- async function claimGoal(goalId, opts = {}) {
23819
- const token = authToken(opts.token) ?? "";
23820
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify(goalId ? { goalId } : {}) }));
23821
- if (!res.ok) await fail("claim_goal", res);
23822
- return await res.json();
23823
- }
23824
23864
  async function getGoal(goalId, opts = {}, read3 = {}) {
23825
23865
  const token = authToken(opts.token) ?? "";
23826
23866
  const query = [read3.history ? "history=1" : "", read3.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
@@ -23831,13 +23871,6 @@ async function getGoal(goalId, opts = {}, read3 = {}) {
23831
23871
  if (!res.ok) await fail("get_goal", res);
23832
23872
  return await res.json();
23833
23873
  }
23834
- async function updateGoal(goalId, input, opts = {}) {
23835
- const token = authToken(opts.token) ?? "";
23836
- const operationId = input.operationId ?? randomUUID2();
23837
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ ...input, operationId }) }));
23838
- if (!res.ok) await fail("update_goal", res);
23839
- return await res.json();
23840
- }
23841
23874
  async function hatch(name, voice = null, opts = {}) {
23842
23875
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/hatch`, {
23843
23876
  method: "POST",
@@ -23949,96 +23982,64 @@ async function acceptTriage(proposalId, body, opts = {}) {
23949
23982
  if (!res.ok) throw new Error(`accept_triage failed: ${res.status} ${await res.text()}`);
23950
23983
  return await res.json();
23951
23984
  }
23952
- function repoFromRemote(url) {
23953
- const said = (url ?? "").trim();
23954
- if (!said) return null;
23955
- const path = said.replace(/^[a-z+]+:\/\/[^/]+\//i, "").replace(/^[^@]+@[^:]+:/, "").replace(/\.git$/i, "").replace(/\/+$/, "");
23956
- const parts = path.split("/").filter(Boolean);
23957
- if (parts.length < 2) return null;
23958
- const [owner, name] = parts.slice(-2);
23959
- if (!owner || !name) return null;
23960
- return `${owner}/${name}`.toLowerCase();
23961
- }
23962
- function currentRepo(cwd = process.cwd()) {
23963
- if (asked !== void 0) return asked;
23964
- try {
23965
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
23966
- cwd,
23967
- encoding: "utf8",
23968
- timeout: 2e3,
23969
- stdio: ["ignore", "pipe", "ignore"]
23970
- });
23971
- asked = repoFromRemote(url);
23972
- } catch {
23973
- asked = null;
23974
- }
23975
- return asked;
23985
+ async function readDelivery(deliveryId, opts = {}) {
23986
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, {
23987
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` },
23988
+ signal: opts.signal
23989
+ }));
23990
+ if (!res.ok) await fail("read_delivery", res);
23991
+ return await res.json();
23976
23992
  }
23977
23993
  async function contact(input, opts = {}) {
23978
23994
  const parsed = ContactSchema.parse(input);
23979
- if (!("asks" in parsed) && !("deliveryId" in parsed)) throw new Error("contact with nothing to send receives: call receiveContact");
23995
+ if (!sendsAnything(parsed)) throw new Error("contact with nothing to send receives: call receiveContact");
23980
23996
  opts.signal?.throwIfAborted();
23981
23997
  const send22 = opts.reach ?? reach;
23982
- const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` };
23983
- let deliveryId;
23984
- let deliveries = [];
23985
- let recorded = [];
23986
- let demoted;
23987
- if ("deliveryId" in parsed) {
23988
- deliveryId = parsed.deliveryId;
23989
- } else {
23990
- const reqAsks = parsed.asks.map((a) => ({
23991
- id: a.id,
23992
- parentId: a.parentId,
23993
- // Derived, not asked for (#2315): the agent's own remote, in one spelling, so a project's
23994
- // work does not grow three trees for "paigy", "mauurda/paigy" and "Paigy". An explicit one
23995
- // still wins — normalised the same way — because a caller that names one knows something
23996
- // this cannot see.
23997
- repo: repoFromRemote(a.repo) ?? a.repo ?? currentRepo() ?? void 0,
23998
- ask: a.ask,
23999
- options: a.options ?? [],
24000
- ...a.select ? { select: a.select } : {},
24001
- ...a.answers ? { answers: a.answers } : {}
24002
- }));
24003
- const res = await ensureAuthed(await send22(`${BACKEND_URL}/api/goals/contact`, {
24004
- method: "POST",
24005
- headers,
24006
- signal: opts.signal,
24007
- body: JSON.stringify({
24008
- operationId: opts.operationId ?? randomUUID3(),
24009
- asks: reqAsks,
24010
- waiting: parsed.waiting,
24011
- channel: parsed.channel === "call" ? "call" : "message"
24012
- })
24013
- }));
24014
- if (!res.ok) await fail("batch contact", res);
24015
- const receipt = await res.json();
24016
- deliveries = receipt.deliveries ?? [];
24017
- if (parsed.channel === "call" && receipt.channel && receipt.channel !== "call") demoted = DEMOTED;
24018
- recorded = receipt.recorded ?? [];
24019
- if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
24020
- if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
24021
- deliveryId = deliveries[0]?.deliveryId ?? "";
24022
- if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
24023
- }
24024
- const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id22, deliveryId: deliveryId2, goalId }) => ({ ...id22 ? { id: id22 } : {}, deliveryId: deliveryId2, goalId }));
24025
- const joinedCard = deliveries[0]?.joinedCard === true;
24026
- const joinedCall = deliveries[0]?.joinedCall === true;
23998
+ const acknowledged = parsed.ackEventIds?.length ? await acknowledge(parsed.ackEventIds, opts) : void 0;
23999
+ const questions = parsed.questions?.map((q) => ({ ...q, questionId: q.questionId ?? randomUUID2() }));
24000
+ const res = await ensureAuthed(await send22(`${BACKEND_URL}/api/goals/contact`, {
24001
+ method: "POST",
24002
+ signal: opts.signal,
24003
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
24004
+ body: JSON.stringify({
24005
+ operationId: opts.operationId ?? randomUUID2(),
24006
+ ...parsed.updates ? { updates: parsed.updates } : {},
24007
+ ...questions ? { questions } : {},
24008
+ ...parsed.answers ? { answers: parsed.answers } : {},
24009
+ ...parsed.withdrawQuestionIds ? { withdrawQuestionIds: parsed.withdrawQuestionIds } : {},
24010
+ ...parsed.questionDependencies ? { questionDependencies: parsed.questionDependencies } : {}
24011
+ })
24012
+ }));
24013
+ if (!res.ok) await fail("contact", res);
24014
+ const receipt = await res.json();
24015
+ const results = receipt.results ?? [];
24016
+ const ok = receipt.ok ?? results.every((r) => r.status !== "failed");
24017
+ const deliveries = receipt.deliveries ?? [];
24018
+ const recorded = results.filter((r) => r.status === "recorded" && r.goalId).map((r) => ({ goalId: r.goalId }));
24019
+ const askedCall = [...parsed.updates ?? [], ...questions ?? []].some((m) => m.userExplicitlyRequested === "call") || !!questions?.some((q) => q.workItBlocks?.length);
24020
+ const demoted = askedCall && receipt.channel && receipt.channel !== "call" ? DEMOTED : void 0;
24021
+ const acked = acknowledged ? { acknowledged } : {};
24022
+ if (!deliveries.length) {
24023
+ return { sent: false, ok, results, ...recorded.length ? { recorded } : {}, message: recorded.length ? NOT_SENT : APPLIED, waitOutcome: "not_waited", ...acked };
24024
+ }
24025
+ const lead = deliveries.find((d) => d.kind === "question") ?? deliveries[0];
24027
24026
  const read3 = async (signal2) => {
24028
- const res = await ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
24029
- if (!res.ok) await fail("read_delivery", res);
24030
- const one = await res.json();
24027
+ const one = await readDelivery(lead.deliveryId, { ...opts, signal: signal2 });
24031
24028
  return {
24032
24029
  ...one,
24033
- ...all.length > 1 ? { deliveries: all } : {},
24030
+ ok,
24031
+ results,
24032
+ ...deliveries.length > 1 ? { deliveries: deliveries.map(({ deliveryId, goalId, kind, questionId }) => ({ deliveryId, goalId, kind, ...questionId ? { questionId } : {} })) } : {},
24034
24033
  ...recorded.length ? { recorded, notSent: NOT_SENT } : {},
24035
- ...joinedCard ? { joinedCard: true } : {},
24036
- ...joinedCall ? { joinedCall: true } : {},
24037
- ...demoted ? { demoted } : {}
24034
+ ...lead.joinedCard ? { joinedCard: true } : {},
24035
+ // IT WENT ONTO A CALL ALREADY HAPPENING (owner, 2026-09-28): the view words it as that call.
24036
+ ...lead.joinedCall ? { joinedCall: true } : {},
24037
+ ...demoted ? { demoted } : {},
24038
+ ...acked
24038
24039
  };
24039
24040
  };
24040
- const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
24041
- if (opts.waits === false) return read3(opts.signal);
24041
+ const settled = (d) => d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution" && !entryUnit(e));
24042
+ if (parsed.wait !== true || opts.waits === false) return { ...await read3(opts.signal), waitOutcome: "not_waited" };
24042
24043
  const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
24043
24044
  const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
24044
24045
  let latest;
@@ -24046,12 +24047,12 @@ async function contact(input, opts = {}) {
24046
24047
  while (true) {
24047
24048
  signal.throwIfAborted();
24048
24049
  latest = await read3(signal);
24049
- if (settled(latest)) return latest;
24050
+ if (settled(latest)) return { ...latest, waitOutcome: "available" };
24050
24051
  await sleep2(5e3, void 0, { signal });
24051
24052
  }
24052
24053
  } catch (error) {
24053
24054
  opts.signal?.throwIfAborted();
24054
- if (window.aborted && latest) return latest;
24055
+ if (window.aborted && latest) return { ...latest, waitOutcome: "expired" };
24055
24056
  throw error;
24056
24057
  }
24057
24058
  }
@@ -24062,6 +24063,15 @@ async function receive(opts = {}) {
24062
24063
  if (!res.ok) await fail("receive", res);
24063
24064
  return await res.json();
24064
24065
  }
24066
+ async function acknowledge(eventIds, opts = {}) {
24067
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/ack`, {
24068
+ method: "POST",
24069
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
24070
+ body: JSON.stringify({ eventIds })
24071
+ }));
24072
+ if (!res.ok) await fail("acknowledge", res);
24073
+ return (await res.json()).results ?? [];
24074
+ }
24065
24075
  function frame(line, topic, event, payload) {
24066
24076
  const ref = String(++line.ref);
24067
24077
  try {
@@ -24223,9 +24233,9 @@ async function subscribeWake(onNudge, opts = {}) {
24223
24233
  };
24224
24234
  }
24225
24235
  function withCodexEnv(text) {
24226
- const head = /^\[mcp_servers\.paigy\][ \t]*(?:#[^\n]*)?\r?\n/m.exec(text);
24227
- if (!head) return null;
24228
- const start = head.index + head[0].length;
24236
+ const head2 = /^\[mcp_servers\.paigy\][ \t]*(?:#[^\n]*)?\r?\n/m.exec(text);
24237
+ if (!head2) return null;
24238
+ const start = head2.index + head2[0].length;
24229
24239
  const next = /^\[/m.exec(text.slice(start));
24230
24240
  const end = next ? start + next.index : text.length;
24231
24241
  const block2 = text.slice(start, end);
@@ -24244,7 +24254,7 @@ ${block2}`;
24244
24254
  }
24245
24255
  return text.slice(0, start) + updated + text.slice(end);
24246
24256
  }
24247
- var import_undici, require2, CODEX_ENV, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, lineage, CLIENT, ignoreOverride, defaultOptions, getDefaultOptions, getRefs, getRelativePath, parseCatchDef, integerDateParser, isJsonSchema7AllOfType, emojiRegex2, zodPatterns, ALPHA_NUMERIC, primitiveMappings, asAnyOf, parseOptionalDef, parsePipelineDef, parseReadonlyDef, selectParser, get$ref, addMeta, zodToJsonSchema, OPTIONS_MIN, OPTIONS_MAX, OptionSchema, OptionInputSchema, NIGHT, AskInputSchema, StartContactSchema, ReceiveContactSchema, ContactSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, UpdateGoalSchema, UpdateGoalToolSchema, changedGoal, goalTitle, goalOutcome, CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange, read, GoalChangeSchema, editNames, ManageGoalsSchema, ManageGoalsToolSchema, MANAGE_GOALS_DESCRIPTION, ClaimGoalSchema, GetGoalSchema, GET_GOAL_DESCRIPTION, UPDATE_GOAL_DESCRIPTION, CLAIM_GOAL_DESCRIPTION, SearchToolSchema, SEARCH_DESCRIPTION, WhoIsWorkingSchema, AGENT_TOOLS, AGENT_TOOL_NAMES, id, key, participant, SourceSchema, ResultRefSchema, NodeRefSchema, DependencySchema, goalState, existingGoalChanges, BrainGoalChangeSchema, option, QuestionChangeSchema, lessonScope, LessonChangeSchema, BrainNextSchema, messageBase, BrainMessageSchema, BrainSearchSchema, BrainResultSchema, str, strs, CompactResultSchema, COMPACT_RESULT_JSON_SCHEMA, LIVE_MS, WORKING_MS, ContextSchema, ParticipantSchema, TransformSchema, VisualSchema, NotifyLevelSchema, SelectShapeSchema, ReceiptEventSchema, AttentionSchema, NotifyRequestFields, NotifyRequestSchema, NotifyStatusSchema, AgentStateSchema, TurnSchema, UserAnswerSchema, IntentSchema, RideAlongSchema, AwaitItemSchema, VoiceKeySchema, AgendaTurnSchema, CLAIM_STALE_MS, InboxItemSchema, APNS_TOKEN_RE, PushTokenSchema, MissedCallSchema, clock, QUIET, BrokerTuningSchema, UserSettingsSchema, HistoryWorkSchema, HistoryEntrySchema, HistoryPageSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, LessonStateSchema, LessonViewSchema, QueueQuestionSchema, QueueReplySchema, QueueItemSchema, QueueDeltaSchema, COLD_AFTER_MS, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, HostDecisionSchema, WakeNudgeSchema, PairingStatusSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenSchema, SupportRequestSchema, NotificationFeedbackKindSchema, SlimOptionSchema, QuestionRowSchema, WorkStateSchema, WorkRowSchema, ComputerRowSchema, AgentRowSchema, SnapshotSchema, CallRecapSchema, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, RevokedError, ApiError, tokenOverride, authToken, AWAIT_WINDOW_MS, asked, NOT_SENT, DEMOTED, HEARTBEAT_MS, RETRY_MS, JOIN, JOIN_TIMEOUT_MS, lines;
24257
+ var import_undici, require2, CODEX_ENV, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, lineage, CLIENT, ignoreOverride, defaultOptions, getDefaultOptions, getRefs, getRelativePath, parseCatchDef, integerDateParser, isJsonSchema7AllOfType, emojiRegex2, zodPatterns, ALPHA_NUMERIC, primitiveMappings, asAnyOf, parseOptionalDef, parsePipelineDef, parseReadonlyDef, selectParser, get$ref, addMeta, zodToJsonSchema, OPTIONS_MIN, OPTIONS_MAX, OptionSchema, OptionInputSchema, NIGHT, ASK_MAX, UNITS_MAX, UNIT_TITLE_MAX, UNIT_BODY_MAX, UnitInputSchema, questionHandle, NodeRef, BlockedAction, reason, actionFits, ACTION_FITS, userExplicitlyRequested, units, UpdateInputSchema, questionFields, refineQuestion, QuestionInputSchema, QuestionWireSchema, AnswerInputSchema, QuestionDependencySchema, sendFields, ContactSchema, sendsAnything, ContactWireSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, UpdateGoalSchema, changedGoal, goalTitle, goalOutcome, CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange, read, GoalChangeSchema, editNames, ManageGoalsSchema, ManageGoalsToolSchema, MANAGE_GOALS_DESCRIPTION, GetGoalSchema, GET_GOAL_DESCRIPTION, SearchToolSchema, SEARCH_DESCRIPTION, CheckActivitySchema, FEEDBACK_TEXT_MAX, SendFeedbackSchema, SEND_FEEDBACK_DESCRIPTION, AGENT_TOOLS, AGENT_TOOL_NAMES, id, key, participant, SourceSchema, ResultRefSchema, NodeRefSchema, DependencySchema, goalState, existingGoalChanges, BrainGoalChangeSchema, option, QuestionChangeSchema, lessonScope, LessonChangeSchema, BrainNextSchema, messageBase, BrainMessageSchema, BrainSearchSchema, BrainResultSchema, str, strs, CompactResultSchema, COMPACT_RESULT_JSON_SCHEMA, str2, strs2, TalkerReplySchema, TALKER_REPLY_JSON_SCHEMA, str3, strs3, FilerReplySchema, FILER_REPLY_JSON_SCHEMA, LIVE_MS, WORKING_MS, ContextSchema, ParticipantSchema, TransformSchema, VisualSchema, NotifyLevelSchema, SelectShapeSchema, ReceiptEventSchema, AttentionSchema, NotifyRequestFields, NotifyRequestSchema, NotifyStatusSchema, AgentStateSchema, TurnSchema, UserAnswerSchema, IntentSchema, RideAlongSchema, AwaitItemSchema, VoiceKeySchema, AgendaTurnSchema, CLAIM_STALE_MS, InboxItemSchema, APNS_TOKEN_RE, PushTokenSchema, MissedCallSchema, clock, QUIET, BrokerTuningSchema, UserSettingsSchema, HistoryWorkSchema, HistoryEntrySchema, HistoryPageSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, LessonStateSchema, LessonViewSchema, QueueQuestionSchema, QueueReplySchema, QueueItemSchema, QueueDeltaSchema, COLD_AFTER_MS, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, HostDecisionSchema, WakeNudgeSchema, PairingStatusSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenSchema, SupportRequestSchema, NotificationFeedbackKindSchema, SlimOptionSchema, QuestionRowSchema, WorkStateSchema, WorkRowSchema, ComputerRowSchema, AgentRowSchema, SnapshotSchema, CallRecapSchema, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, RevokedError, ApiError, tokenOverride, authToken, AWAIT_WINDOW_MS, NOT_SENT, APPLIED, DEMOTED, HEARTBEAT_MS, RETRY_MS, JOIN, JOIN_TIMEOUT_MS, lines;
24248
24258
  var init_dist = __esm({
24249
24259
  "../../packages/sdk/dist/index.js"() {
24250
24260
  "use strict";
@@ -24256,6 +24266,8 @@ var init_dist = __esm({
24256
24266
  init_v3();
24257
24267
  init_zod();
24258
24268
  init_zod();
24269
+ init_zod();
24270
+ init_zod();
24259
24271
  import_undici = __toESM(require_undici(), 1);
24260
24272
  require2 = __sdkCreateRequire(import.meta.url);
24261
24273
  CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "CODEX_HOME", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID", "PAIGY_ON_WAKE"];
@@ -24644,51 +24656,137 @@ var init_dist = __esm({
24644
24656
  // .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
24645
24657
  // the constraints below are what an agent reads when deciding to use these.
24646
24658
  html: external_exports.string().max(16384).describe(
24647
- "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 `image` instead."
24659
+ "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."
24648
24660
  ).optional(),
24649
24661
  image: external_exports.string().url().describe(
24650
- "Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
24662
+ "Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use the HTML preview instead."
24651
24663
  ).optional()
24652
24664
  });
24653
- OptionInputSchema = OptionSchema.omit({ id: true }).extend({
24654
- label: external_exports.string().trim().min(1).max(1e3)
24665
+ OptionInputSchema = external_exports.object({
24666
+ id: external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_.:-]+$/).optional().describe(
24667
+ `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.`
24668
+ ),
24669
+ label: external_exports.string().trim().min(1).max(1e3),
24670
+ hint: OptionSchema.shape.hint,
24671
+ htmlPreview: OptionSchema.shape.html,
24672
+ imgUrl: OptionSchema.shape.image
24655
24673
  }).strict();
24656
24674
  NIGHT = { from: 23, to: 7 };
24657
- AskInputSchema = external_exports.object({
24658
- id: external_exports.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
24659
- parentId: external_exports.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and the question starts a new Goal of yours."),
24660
- repo: external_exports.string().optional().describe("Optional repository context."),
24661
- ask: external_exports.string().trim().min(1).max(1e4).describe(
24662
- "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy sends each ask exactly as you wrote it: a bundled ask arrives as one card. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
24675
+ ASK_MAX = 250;
24676
+ UNITS_MAX = 12;
24677
+ UNIT_TITLE_MAX = 50;
24678
+ UNIT_BODY_MAX = 200;
24679
+ UnitInputSchema = external_exports.object({
24680
+ 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.`),
24681
+ body: external_exports.string().trim().min(1).describe(`The unit itself, at most ${UNIT_BODY_MAX} characters.`)
24682
+ }).strict();
24683
+ questionHandle = external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
24684
+ NodeRef = external_exports.object({
24685
+ type: external_exports.enum(["goal", "question"]),
24686
+ id: external_exports.string().uuid()
24687
+ }).strict().describe("A Goal or a Question, by its id.");
24688
+ BlockedAction = external_exports.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
24689
+ reason = external_exports.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
24690
+ actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
24691
+ ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
24692
+ userExplicitlyRequested = external_exports.enum(["any", "call"]).optional().describe(
24693
+ `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.`
24694
+ );
24695
+ units = external_exports.array(UnitInputSchema).optional().describe(
24696
+ `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.`
24697
+ );
24698
+ UpdateInputSchema = external_exports.object({
24699
+ message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
24700
+ 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."),
24701
+ units,
24702
+ userExplicitlyRequested
24703
+ }).strict().superRefine((u, ctx) => refuseOverCaps(u.message, u.units, "message", ctx));
24704
+ questionFields = {
24705
+ question: external_exports.string().trim().min(1).describe(
24706
+ `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.`
24663
24707
  ),
24664
- options: external_exports.array(OptionInputSchema).min(1).max(6).optional(),
24708
+ 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."),
24709
+ units,
24710
+ options: external_exports.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own."),
24665
24711
  // HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
24666
- // brain_prompts.md §1: structured agent questions need no model). Every card was pick-one while no
24667
- // agent sent this; no read reshapes cards any more, so this is the card's shape, and the
24668
- // description says so plainly.
24669
- select: external_exports.enum(["one", "many"]).optional().describe(
24670
- 'How the options are answered: "many" makes a checklist (they may want several, e.g. independent fixes), "one" (the default) a pick (choosing one rules out the others). Send "many" whenever more than one option could be wanted at once. The person can always answer in their own words as well. Ignored without options.'
24712
+ // brain_prompts.md §1: structured agent questions need no model). The app draws all three.
24713
+ pickMode: external_exports.enum(["one", "many", "rank"]).optional().describe(
24714
+ '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.'
24671
24715
  ),
24672
- answers: external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
24673
- "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. parentId is optional here: the question's own Goal is used. The reply is sent to them as it is; to also ask something new, send that as its own ask."
24674
- )
24675
- }).strict();
24676
- StartContactSchema = external_exports.object({
24677
- asks: external_exports.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; an ask naming no Goal starts a new Goal of yours."),
24678
- waiting: external_exports.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
24679
- channel: external_exports.enum(["notification", "call"]).default("notification")
24680
- }).strict();
24681
- ReceiveContactSchema = external_exports.object({
24682
- wait: external_exports.boolean().optional().describe(
24683
- "Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return 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 in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
24716
+ // WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
24717
+ // blocks work asks for a call (the person's settings and ring cooldowns still decide).
24718
+ 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(
24719
+ "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."
24720
+ ),
24721
+ userExplicitlyRequested
24722
+ };
24723
+ refineQuestion = (q, ctx) => {
24724
+ refuseOverCaps(q.question, q.units, "question", ctx);
24725
+ if (q.pickMode && !q.options) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
24726
+ const ids = (q.options ?? []).map((o) => o.id);
24727
+ if (ids.some((id22) => id22) && (ids.some((id22) => !id22) || new Set(ids).size !== ids.length)) {
24728
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
24729
+ }
24730
+ };
24731
+ QuestionInputSchema = external_exports.object({
24732
+ 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."),
24733
+ ...questionFields
24734
+ }).strict().superRefine(refineQuestion);
24735
+ QuestionWireSchema = external_exports.object({ questionId: external_exports.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
24736
+ AnswerInputSchema = external_exports.object({
24737
+ questionId: questionHandle.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
24738
+ answer: external_exports.object({
24739
+ text: external_exports.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
24740
+ 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).")
24741
+ }).strict(),
24742
+ units
24743
+ }).strict().superRefine((a, ctx) => refuseOverCaps(a.answer.text, a.units, "text", ctx));
24744
+ QuestionDependencySchema = external_exports.object({
24745
+ questionId: questionHandle.describe("A Question you asked."),
24746
+ change: external_exports.enum(["add", "remove"]),
24747
+ blocked: NodeRef,
24748
+ action: BlockedAction,
24749
+ reason: reason.optional()
24750
+ }).strict().refine(actionFits, ACTION_FITS);
24751
+ sendFields = {
24752
+ updates: external_exports.array(UpdateInputSchema).min(1).max(20).optional().describe(
24753
+ "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."
24754
+ ),
24755
+ answers: external_exports.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
24756
+ withdrawQuestionIds: external_exports.array(questionHandle).min(1).max(10).optional().describe(
24757
+ "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."
24684
24758
  ),
24759
+ questionDependencies: external_exports.array(QuestionDependencySchema).min(1).max(20).optional().describe(
24760
+ "Add or remove what a Question you asked holds up, after asking it."
24761
+ )
24762
+ };
24763
+ ContactSchema = external_exports.object({
24764
+ ...sendFields,
24765
+ questions: external_exports.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
24766
+ /** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
24685
24767
  ackEventIds: external_exports.array(external_exports.string().uuid()).min(1).max(100).optional().describe(
24686
- "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 (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
24768
+ "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."
24769
+ ),
24770
+ wait: external_exports.boolean().optional().describe(
24771
+ "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`."
24687
24772
  )
24688
24773
  }).strict();
24689
- ContactSchema = external_exports.union([StartContactSchema, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict(), ReceiveContactSchema]);
24774
+ sendsAnything = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
24775
+ ContactWireSchema = external_exports.object({
24776
+ operationId: external_exports.string().uuid(),
24777
+ ...sendFields,
24778
+ questions: external_exports.array(QuestionWireSchema).min(1).max(20).optional()
24779
+ }).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
24690
24780
  CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
24691
- CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. Send options (or waiting:'hard') when you actually need an answer. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is 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`; claim_goal starts it) and your work gone quiet (`stalled`).";
24781
+ 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.
24782
+
24783
+ 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.
24784
+
24785
+ 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.
24786
+
24787
+ 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.
24788
+
24789
+ 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\`).`;
24692
24790
  CreateGoalSchema = external_exports.object({
24693
24791
  outcome: external_exports.string().trim().min(1).max(1e4),
24694
24792
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -24722,21 +24820,11 @@ var init_dist = __esm({
24722
24820
  state: external_exports.enum(["active", "done", "cancelled"]).optional(),
24723
24821
  progress: external_exports.string().trim().min(1).max(1e4).optional(),
24724
24822
  reviewed: external_exports.literal(true).optional(),
24725
- dueAt: external_exports.string().datetime({ offset: true }).nullable().optional(),
24726
- /** WITHDRAW YOUR OWN QUESTION (#2777, owner 2026-09-30). The question's id as every read shows it
24727
- * (the conversation's `id`, eight characters, or the whole request Entry id), on THIS Goal, asked
24728
- * by you and still open. It is cancelled, not answered: its cards close and nothing rings for it. */
24729
- withdraw: external_exports.array(external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/)).min(1).max(10).optional()
24823
+ dueAt: external_exports.string().datetime({ offset: true }).nullable().optional()
24730
24824
  }).strict().refine((v) => Object.keys(v).length > 0),
24731
24825
  reason: external_exports.string().trim().min(1).max(2e3),
24732
24826
  operationId: external_exports.string().uuid().optional()
24733
24827
  }).strict();
24734
- UpdateGoalToolSchema = external_exports.object({
24735
- goalId: external_exports.string().uuid(),
24736
- revision: UpdateGoalSchema.shape.revision,
24737
- changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
24738
- reason: UpdateGoalSchema.shape.reason
24739
- }).strict();
24740
24828
  changedGoal = external_exports.string().uuid().describe("The Goal to change, as a read shows it.");
24741
24829
  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.");
24742
24830
  goalOutcome = external_exports.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
@@ -24744,7 +24832,7 @@ var init_dist = __esm({
24744
24832
  kind: external_exports.literal("create"),
24745
24833
  title: goalTitle,
24746
24834
  outcome: goalOutcome,
24747
- ownerId: external_exports.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
24835
+ 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."),
24748
24836
  parentGoalId: external_exports.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
24749
24837
  sourceEntryIds: external_exports.array(external_exports.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
24750
24838
  }).strict();
@@ -24754,7 +24842,7 @@ var init_dist = __esm({
24754
24842
  ) }).strict();
24755
24843
  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();
24756
24844
  DeferChange = external_exports.object({ kind: external_exports.literal("defer"), goalId: changedGoal, until: external_exports.string().datetime({ offset: true }).nullable().describe(
24757
- "Postpone it until this instant (its owner is woken then), or null to take the postponement back."
24845
+ "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."
24758
24846
  ) }).strict();
24759
24847
  MoveChange = external_exports.object({ kind: external_exports.literal("move"), goalId: changedGoal, parentGoalId: external_exports.string().uuid().nullable().describe(
24760
24848
  "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
@@ -24789,8 +24877,7 @@ var init_dist = __esm({
24789
24877
  ManageGoalsToolSchema = external_exports.object({
24790
24878
  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.")
24791
24879
  }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
24792
- 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_joined, 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, or null); 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 a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
24793
- ClaimGoalSchema = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
24880
+ 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.";
24794
24881
  GetGoalSchema = external_exports.object({
24795
24882
  goalId: external_exports.string().uuid(),
24796
24883
  /** Every entry in full. Without it the read carries the person's words, open questions and your
@@ -24801,9 +24888,7 @@ var init_dist = __esm({
24801
24888
  * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
24802
24889
  diagnose: external_exports.boolean().optional()
24803
24890
  }).strict();
24804
- GET_GOAL_DESCRIPTION = "Read one Goal without claiming 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.";
24805
- UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). Stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. Returns the Goal as get_goal reads it, at its new revision.";
24806
- CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change it; use manage_goals (assign) when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
24891
+ 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.";
24807
24892
  SearchToolSchema = external_exports.object({
24808
24893
  query: external_exports.string().trim().min(1).max(500),
24809
24894
  types: external_exports.array(external_exports.enum(["entry", "goal", "answer"])).min(1).optional(),
@@ -24811,15 +24896,21 @@ var init_dist = __esm({
24811
24896
  limit: external_exports.number().int().min(1).max(20).optional()
24812
24897
  }).strict();
24813
24898
  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.";
24814
- WhoIsWorkingSchema = external_exports.object({}).strict();
24899
+ CheckActivitySchema = external_exports.object({}).strict();
24900
+ FEEDBACK_TEXT_MAX = 5e4;
24901
+ SendFeedbackSchema = external_exports.object({
24902
+ 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.`),
24903
+ title: external_exports.string().trim().min(1).max(200).optional().describe("One line naming it."),
24904
+ kind: external_exports.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
24905
+ }).strict();
24906
+ 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.";
24815
24907
  AGENT_TOOLS = [
24816
- { 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(WhoIsWorkingSchema) },
24817
24908
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
24818
24909
  { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
24819
- { name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
24820
24910
  { name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
24821
- { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
24822
- { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
24911
+ { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
24912
+ { 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) },
24913
+ { name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) }
24823
24914
  ];
24824
24915
  AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
24825
24916
  id = external_exports.string().uuid();
@@ -24958,13 +25049,76 @@ var init_dist = __esm({
24958
25049
  COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
24959
25050
  zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
24960
25051
  );
25052
+ str2 = external_exports.string();
25053
+ strs2 = external_exports.array(external_exports.string());
25054
+ TalkerReplySchema = external_exports.object({
25055
+ /** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
25056
+ * or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
25057
+ * answered before ("" for none). */
25058
+ messages: external_exports.array(external_exports.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
25059
+ /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
25060
+ then: external_exports.enum(["listen", "hold", "end"]),
25061
+ /** Items the person's words settle: the item's handle, the chosen option IDs, the line handles. */
25062
+ answers: external_exports.array(external_exports.object({ item: str2, options: strs2, lines: strs2 }).strict()),
25063
+ /** The line handles that hold a new instruction from the person. */
25064
+ instruction: strs2,
25065
+ /** The line handles that ask for new work, a change to existing work, or a standing rule: the filer
25066
+ * reads only these (3e). */
25067
+ file: strs2,
25068
+ /** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
25069
+ need: external_exports.object({ kind: external_exports.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
25070
+ /** The handle of the item to explain ("" for none). */
25071
+ explain: str2
25072
+ }).strict();
25073
+ TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
25074
+ zodToJsonSchema(TalkerReplySchema, { $refStrategy: "none" })
25075
+ );
25076
+ str3 = external_exports.string();
25077
+ strs3 = external_exports.array(external_exports.string());
25078
+ FilerReplySchema = external_exports.object({
25079
+ /** Which Goals each new line goes on: a line's handle and Goal handles (or keys of new Goals). */
25080
+ filed: external_exports.array(external_exports.object({ line: str3, goals: strs3 }).strict()),
25081
+ goals: external_exports.array(external_exports.object({
25082
+ op: external_exports.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
25083
+ key: str3,
25084
+ goal: str3,
25085
+ title: str3,
25086
+ outcome: str3,
25087
+ owner: str3,
25088
+ under: str3,
25089
+ until: str3,
25090
+ on: str3,
25091
+ gate: external_exports.enum(["start", "complete", "answer", ""]),
25092
+ why: str3
25093
+ }).strict()),
25094
+ questions: external_exports.array(external_exports.object({
25095
+ op: external_exports.enum(["create", "edit", "assign", "withdraw"]),
25096
+ key: str3,
25097
+ question: str3,
25098
+ text: str3,
25099
+ to: str3,
25100
+ goal: str3,
25101
+ options: external_exports.array(external_exports.object({ id: str3, label: str3 }).strict()),
25102
+ pick: external_exports.enum(["one", "many", "rank", "words", ""]),
25103
+ blocks: str3
25104
+ }).strict()),
25105
+ lessons: external_exports.array(external_exports.object({ op: external_exports.enum(["remember", "revise", "withdraw"]), lesson: str3, text: str3, scope: str3 }).strict()),
25106
+ /** Line handles whose words comment on Paigy itself. */
25107
+ feedback: strs3
25108
+ }).strict();
25109
+ FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
25110
+ zodToJsonSchema(FilerReplySchema, { $refStrategy: "none" })
25111
+ );
24961
25112
  LIVE_MS = 3 * 6e4;
24962
25113
  WORKING_MS = 60 * 6e4;
24963
25114
  ContextSchema = external_exports.object({
24964
25115
  title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
24965
25116
  description: external_exports.array(external_exports.string().min(1)).describe(
24966
25117
  "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."
24967
- )
25118
+ ),
25119
+ /** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
25120
+ * a body on a tap. Absent on an ask sent without units, which renders as before. */
25121
+ units: external_exports.array(external_exports.object({ title: external_exports.string(), body: external_exports.string() })).optional()
24968
25122
  });
24969
25123
  ParticipantSchema = external_exports.object({
24970
25124
  kind: external_exports.enum(["human", "agent"]),
@@ -25039,7 +25193,7 @@ var init_dist = __esm({
25039
25193
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
25040
25194
  * superRefine at the bottom enforces exactly one of the two. */
25041
25195
  context: ContextSchema.optional(),
25042
- options: external_exports.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
25196
+ 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(
25043
25197
  "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)."
25044
25198
  ),
25045
25199
  points: external_exports.array(external_exports.string().min(1)).optional().describe(
@@ -25800,8 +25954,8 @@ var init_dist = __esm({
25800
25954
  repo: external_exports.string().nullable().optional(),
25801
25955
  createdAt: external_exports.string(),
25802
25956
  updatedAt: external_exports.string().nullable().default(null),
25803
- /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
25804
- * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
25957
+ /** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
25958
+ * as progress). `updatedAt` moves for reasons nobody chose — a
25805
25959
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
25806
25960
  lastProgressAt: external_exports.string().nullable().optional(),
25807
25961
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
@@ -25959,7 +26113,7 @@ var init_dist = __esm({
25959
26113
  /** The agent's token id: the row's `recipient`. */
25960
26114
  agent: external_exports.string().uuid(),
25961
26115
  decision: external_exports.enum(["stood_back", "took_over"]),
25962
- /** The work it was about: the Goal `claim_goal` would hand that agent next. */
26116
+ /** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
25963
26117
  goalId: external_exports.string().uuid().nullable().optional(),
25964
26118
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
25965
26119
  seenAt: external_exports.string().datetime().nullable().optional(),
@@ -26198,8 +26352,9 @@ var init_dist = __esm({
26198
26352
  tokenOverride = null;
26199
26353
  authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
26200
26354
  AWAIT_WINDOW_MS = 45e3;
26201
- 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 message saying what is done and anything they need to do or check.";
26202
- 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.";
26355
+ 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.`;
26356
+ APPLIED = "Applied; nothing was sent to the person.";
26357
+ 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.";
26203
26358
  HEARTBEAT_MS = 25e3;
26204
26359
  RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
26205
26360
  JOIN = { config: { broadcast: { self: false } } };
@@ -27102,14 +27257,14 @@ var require_regex = __commonJS({
27102
27257
  var TEST_KANJI = new RegExp("^" + kanji + "$");
27103
27258
  var TEST_NUMERIC = new RegExp("^" + numeric + "$");
27104
27259
  var TEST_ALPHANUMERIC = new RegExp("^[A-Z0-9 $%*+\\-./:]+$");
27105
- exports.testKanji = function testKanji(str3) {
27106
- return TEST_KANJI.test(str3);
27260
+ exports.testKanji = function testKanji(str5) {
27261
+ return TEST_KANJI.test(str5);
27107
27262
  };
27108
- exports.testNumeric = function testNumeric(str3) {
27109
- return TEST_NUMERIC.test(str3);
27263
+ exports.testNumeric = function testNumeric(str5) {
27264
+ return TEST_NUMERIC.test(str5);
27110
27265
  };
27111
- exports.testAlphanumeric = function testAlphanumeric(str3) {
27112
- return TEST_ALPHANUMERIC.test(str3);
27266
+ exports.testAlphanumeric = function testAlphanumeric(str5) {
27267
+ return TEST_ALPHANUMERIC.test(str5);
27113
27268
  };
27114
27269
  }
27115
27270
  });
@@ -27611,13 +27766,13 @@ var require_segments = __commonJS({
27611
27766
  var Regex = require_regex();
27612
27767
  var Utils = require_utils2();
27613
27768
  var dijkstra = require_dijkstra();
27614
- function getStringByteLength(str3) {
27615
- return unescape(encodeURIComponent(str3)).length;
27769
+ function getStringByteLength(str5) {
27770
+ return unescape(encodeURIComponent(str5)).length;
27616
27771
  }
27617
- function getSegments(regex, mode, str3) {
27772
+ function getSegments(regex, mode, str5) {
27618
27773
  const segments = [];
27619
27774
  let result;
27620
- while ((result = regex.exec(str3)) !== null) {
27775
+ while ((result = regex.exec(str5)) !== null) {
27621
27776
  segments.push({
27622
27777
  data: result[0],
27623
27778
  index: result.index,
@@ -30451,13 +30606,13 @@ var require_svg_tag = __commonJS({
30451
30606
  var Utils = require_utils3();
30452
30607
  function getColorAttrib(color, attrib) {
30453
30608
  const alpha = color.a / 255;
30454
- const str3 = attrib + '="' + color.hex + '"';
30455
- return alpha < 1 ? str3 + " " + attrib + '-opacity="' + alpha.toFixed(2).slice(1) + '"' : str3;
30609
+ const str5 = attrib + '="' + color.hex + '"';
30610
+ return alpha < 1 ? str5 + " " + attrib + '-opacity="' + alpha.toFixed(2).slice(1) + '"' : str5;
30456
30611
  }
30457
30612
  function svgCmd(cmd, x, y) {
30458
- let str3 = cmd + x;
30459
- if (typeof y !== "undefined") str3 += " " + y;
30460
- return str3;
30613
+ let str5 = cmd + x;
30614
+ if (typeof y !== "undefined") str5 += " " + y;
30615
+ return str5;
30461
30616
  }
30462
30617
  function qrToPath(data, size, margin) {
30463
30618
  let path = "";
@@ -30989,9 +31144,9 @@ async function available(deps = {}) {
30989
31144
  });
30990
31145
  return [ollama, ...harnesses];
30991
31146
  }
30992
- function refusal(options, asked2) {
31147
+ function refusal(options, asked) {
30993
31148
  const lines2 = [
30994
- asked2 ? `Can't judge with --via ${asked2}: ${options.find((o) => o.provider === asked2)?.hint ?? "it isn't available on this machine"}` : "No local judge available \u2014 triage refuses rather than sending your queue to a cloud model.",
31149
+ 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.",
30995
31150
  "",
30996
31151
  "Triage runs the model where your queue already lives (#1106). Your options:"
30997
31152
  ];
@@ -31002,10 +31157,10 @@ function refusal(options, asked2) {
31002
31157
  lines2.push("", "Then re-run: paigy-harness triage [--via ollama|claude|codex|agy]");
31003
31158
  return lines2.join("\n");
31004
31159
  }
31005
- async function judgeFor(asked2, deps = {}) {
31160
+ async function judgeFor(asked, deps = {}) {
31006
31161
  const options = await available(deps);
31007
- const chosen = asked2 ? options.find((o) => o.provider === asked2) : options.find((o) => o.ready);
31008
- if (!chosen?.ready) return { error: refusal(options, asked2) };
31162
+ const chosen = asked ? options.find((o) => o.provider === asked) : options.find((o) => o.ready);
31163
+ if (!chosen?.ready) return { error: refusal(options, asked) };
31009
31164
  if (chosen.provider === "ollama") {
31010
31165
  const model = chosen.model;
31011
31166
  return {
@@ -31026,9 +31181,9 @@ async function judgeFor(asked2, deps = {}) {
31026
31181
  };
31027
31182
  }
31028
31183
  const runner = RUNNERS[chosen.provider];
31029
- if (!runner) return { error: refusal(options, asked2) };
31184
+ if (!runner) return { error: refusal(options, asked) };
31030
31185
  const bin = which(runner.cli);
31031
- if (!bin) return { error: refusal(options, asked2) };
31186
+ if (!bin) return { error: refusal(options, asked) };
31032
31187
  return {
31033
31188
  provider: chosen.provider,
31034
31189
  model: null,
@@ -31462,7 +31617,7 @@ function onEnter(input, onLine) {
31462
31617
  // src/cli.ts
31463
31618
  init_dist();
31464
31619
  var import_qrcode = __toESM(require_lib(), 1);
31465
- import { randomUUID as randomUUID6 } from "crypto";
31620
+ import { randomUUID as randomUUID5 } from "crypto";
31466
31621
  import { hostname } from "os";
31467
31622
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
31468
31623
  import { execFile as execFile2, execSync } from "child_process";
@@ -31471,7 +31626,7 @@ import { join as join11 } from "path";
31471
31626
 
31472
31627
  // src/run.ts
31473
31628
  init_dist();
31474
- import { randomUUID as randomUUID5 } from "crypto";
31629
+ import { randomUUID as randomUUID4 } from "crypto";
31475
31630
 
31476
31631
  // src/harness/session.ts
31477
31632
  init_dist();
@@ -31759,7 +31914,7 @@ init_catalog();
31759
31914
  import { mkdirSync as mkdirSync2, readdirSync, renameSync, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
31760
31915
  import { join as join4 } from "path";
31761
31916
  import { homedir as homedir3 } from "os";
31762
- import { randomUUID as randomUUID4 } from "crypto";
31917
+ import { randomUUID as randomUUID3 } from "crypto";
31763
31918
  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"`;
31764
31919
  var missing = (error) => error.code === "ENOENT";
31765
31920
  function openInbox(dir) {
@@ -31819,7 +31974,7 @@ function inboxFor(root = INBOX_ROOT) {
31819
31974
  } catch (error) {
31820
31975
  if (!missing(error)) throw error;
31821
31976
  }
31822
- return openInbox(join4(root, `${process.pid}-${randomUUID4().slice(0, 8)}`));
31977
+ return openInbox(join4(root, `${process.pid}-${randomUUID3().slice(0, 8)}`));
31823
31978
  }
31824
31979
 
31825
31980
  // src/harness/session.ts
@@ -33252,6 +33407,8 @@ var zodToJsonSchema2 = (schema, options) => {
33252
33407
  // ../../packages/schema/dist/index.js
33253
33408
  init_zod();
33254
33409
  init_zod();
33410
+ init_zod();
33411
+ init_zod();
33255
33412
  var OPTIONS_MIN2 = 2;
33256
33413
  var OPTIONS_MAX2 = 6;
33257
33414
  var OptionSchema2 = external_exports.object({
@@ -33261,14 +33418,20 @@ var OptionSchema2 = external_exports.object({
33261
33418
  // .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
33262
33419
  // the constraints below are what an agent reads when deciding to use these.
33263
33420
  html: external_exports.string().max(16384).describe(
33264
- "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 `image` instead."
33421
+ "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."
33265
33422
  ).optional(),
33266
33423
  image: external_exports.string().url().describe(
33267
- "Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
33424
+ "Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use the HTML preview instead."
33268
33425
  ).optional()
33269
33426
  });
33270
- var OptionInputSchema2 = OptionSchema2.omit({ id: true }).extend({
33271
- label: external_exports.string().trim().min(1).max(1e3)
33427
+ var OptionInputSchema2 = external_exports.object({
33428
+ id: external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_.:-]+$/).optional().describe(
33429
+ `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.`
33430
+ ),
33431
+ label: external_exports.string().trim().min(1).max(1e3),
33432
+ hint: OptionSchema2.shape.hint,
33433
+ htmlPreview: OptionSchema2.shape.html,
33434
+ imgUrl: OptionSchema2.shape.image
33272
33435
  }).strict();
33273
33436
  var NIGHT2 = { from: 23, to: 7 };
33274
33437
  function draft20202(node) {
@@ -33300,41 +33463,162 @@ function mcpInputSchema2(s) {
33300
33463
  delete schema.$schema;
33301
33464
  return draft20202(schema);
33302
33465
  }
33303
- var AskInputSchema2 = external_exports.object({
33304
- id: external_exports.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
33305
- parentId: external_exports.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and the question starts a new Goal of yours."),
33306
- repo: external_exports.string().optional().describe("Optional repository context."),
33307
- ask: external_exports.string().trim().min(1).max(1e4).describe(
33308
- "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy sends each ask exactly as you wrote it: a bundled ask arrives as one card. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
33466
+ var ASK_MAX2 = 250;
33467
+ var UNITS_MAX2 = 12;
33468
+ var UNIT_TITLE_MAX2 = 50;
33469
+ var UNIT_BODY_MAX2 = 200;
33470
+ var UnitInputSchema2 = external_exports.object({
33471
+ 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.`),
33472
+ body: external_exports.string().trim().min(1).describe(`The unit itself, at most ${UNIT_BODY_MAX2} characters.`)
33473
+ }).strict();
33474
+ function capRefusals2(text, units22, field) {
33475
+ const out = [];
33476
+ const name = field === "text" ? "answer" : field;
33477
+ if (text.length > ASK_MAX2) out.push({
33478
+ refusal: "ask_too_long",
33479
+ length: text.length,
33480
+ cap: ASK_MAX2,
33481
+ path: field === "text" ? ["answer", "text"] : [field],
33482
+ 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.`
33483
+ });
33484
+ const all = units22 ?? [];
33485
+ if (all.length > UNITS_MAX2) out.push({
33486
+ refusal: "too_many_units",
33487
+ length: all.length,
33488
+ cap: UNITS_MAX2,
33489
+ path: ["units"],
33490
+ message: `too_many_units: ${all.length} units; the cap is ${UNITS_MAX2}. Keep only the context this ${name} needs.`
33491
+ });
33492
+ all.forEach((unit, index) => {
33493
+ if (unit.title.length > UNIT_TITLE_MAX2) out.push({
33494
+ refusal: "unit_title_too_long",
33495
+ index,
33496
+ length: unit.title.length,
33497
+ cap: UNIT_TITLE_MAX2,
33498
+ path: ["units", index, "title"],
33499
+ message: `unit_title_too_long: units[${index}]'s title is ${unit.title.length} characters; the cap is ${UNIT_TITLE_MAX2}.`
33500
+ });
33501
+ if (unit.body.length > UNIT_BODY_MAX2) out.push({
33502
+ refusal: "unit_body_too_long",
33503
+ index,
33504
+ length: unit.body.length,
33505
+ cap: UNIT_BODY_MAX2,
33506
+ path: ["units", index, "body"],
33507
+ 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.`
33508
+ });
33509
+ });
33510
+ return out;
33511
+ }
33512
+ function refuseOverCaps2(text, units22, field, ctx) {
33513
+ for (const { path, message, ...params } of capRefusals2(text, units22, field)) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message, params });
33514
+ }
33515
+ var questionHandle2 = external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
33516
+ var NodeRef2 = external_exports.object({
33517
+ type: external_exports.enum(["goal", "question"]),
33518
+ id: external_exports.string().uuid()
33519
+ }).strict().describe("A Goal or a Question, by its id.");
33520
+ var BlockedAction2 = external_exports.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
33521
+ var reason2 = external_exports.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
33522
+ var actionFits2 = (b) => b.blocked.type === "goal" === (b.action !== "answer");
33523
+ var ACTION_FITS2 = "a goal is blocked from start or complete; a question from answer";
33524
+ var userExplicitlyRequested2 = external_exports.enum(["any", "call"]).optional().describe(
33525
+ `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.`
33526
+ );
33527
+ var units2 = external_exports.array(UnitInputSchema2).optional().describe(
33528
+ `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.`
33529
+ );
33530
+ var UpdateInputSchema2 = external_exports.object({
33531
+ message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX2} characters; the context goes in units.`),
33532
+ 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."),
33533
+ units: units2,
33534
+ userExplicitlyRequested: userExplicitlyRequested2
33535
+ }).strict().superRefine((u, ctx) => refuseOverCaps2(u.message, u.units, "message", ctx));
33536
+ var questionFields2 = {
33537
+ question: external_exports.string().trim().min(1).describe(
33538
+ `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.`
33309
33539
  ),
33310
- options: external_exports.array(OptionInputSchema2).min(1).max(6).optional(),
33540
+ 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."),
33541
+ units: units2,
33542
+ options: external_exports.array(OptionInputSchema2).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own."),
33311
33543
  // HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
33312
- // brain_prompts.md §1: structured agent questions need no model). Every card was pick-one while no
33313
- // agent sent this; no read reshapes cards any more, so this is the card's shape, and the
33314
- // description says so plainly.
33315
- select: external_exports.enum(["one", "many"]).optional().describe(
33316
- 'How the options are answered: "many" makes a checklist (they may want several, e.g. independent fixes), "one" (the default) a pick (choosing one rules out the others). Send "many" whenever more than one option could be wanted at once. The person can always answer in their own words as well. Ignored without options.'
33544
+ // brain_prompts.md §1: structured agent questions need no model). The app draws all three.
33545
+ pickMode: external_exports.enum(["one", "many", "rank"]).optional().describe(
33546
+ '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.'
33317
33547
  ),
33318
- answers: external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
33319
- "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. parentId is optional here: the question's own Goal is used. The reply is sent to them as it is; to also ask something new, send that as its own ask."
33320
- )
33321
- }).strict();
33322
- var StartContactSchema2 = external_exports.object({
33323
- asks: external_exports.array(AskInputSchema2).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; an ask naming no Goal starts a new Goal of yours."),
33324
- waiting: external_exports.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
33325
- channel: external_exports.enum(["notification", "call"]).default("notification")
33326
- }).strict();
33327
- var ReceiveContactSchema2 = external_exports.object({
33328
- wait: external_exports.boolean().optional().describe(
33329
- "Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return 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 in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
33548
+ // WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
33549
+ // blocks work asks for a call (the person's settings and ring cooldowns still decide).
33550
+ 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(
33551
+ "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."
33330
33552
  ),
33553
+ userExplicitlyRequested: userExplicitlyRequested2
33554
+ };
33555
+ var refineQuestion2 = (q, ctx) => {
33556
+ refuseOverCaps2(q.question, q.units, "question", ctx);
33557
+ if (q.pickMode && !q.options) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
33558
+ const ids = (q.options ?? []).map((o) => o.id);
33559
+ if (ids.some((id22) => id22) && (ids.some((id22) => !id22) || new Set(ids).size !== ids.length)) {
33560
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
33561
+ }
33562
+ };
33563
+ var QuestionInputSchema2 = external_exports.object({
33564
+ 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."),
33565
+ ...questionFields2
33566
+ }).strict().superRefine(refineQuestion2);
33567
+ var QuestionWireSchema2 = external_exports.object({ questionId: external_exports.string().uuid(), ...questionFields2 }).strict().superRefine(refineQuestion2);
33568
+ var AnswerInputSchema2 = external_exports.object({
33569
+ questionId: questionHandle2.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
33570
+ answer: external_exports.object({
33571
+ text: external_exports.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX2} characters; the context goes in units.`),
33572
+ 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).")
33573
+ }).strict(),
33574
+ units: units2
33575
+ }).strict().superRefine((a, ctx) => refuseOverCaps2(a.answer.text, a.units, "text", ctx));
33576
+ var QuestionDependencySchema2 = external_exports.object({
33577
+ questionId: questionHandle2.describe("A Question you asked."),
33578
+ change: external_exports.enum(["add", "remove"]),
33579
+ blocked: NodeRef2,
33580
+ action: BlockedAction2,
33581
+ reason: reason2.optional()
33582
+ }).strict().refine(actionFits2, ACTION_FITS2);
33583
+ var sendFields2 = {
33584
+ updates: external_exports.array(UpdateInputSchema2).min(1).max(20).optional().describe(
33585
+ "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."
33586
+ ),
33587
+ answers: external_exports.array(AnswerInputSchema2).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
33588
+ withdrawQuestionIds: external_exports.array(questionHandle2).min(1).max(10).optional().describe(
33589
+ "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."
33590
+ ),
33591
+ questionDependencies: external_exports.array(QuestionDependencySchema2).min(1).max(20).optional().describe(
33592
+ "Add or remove what a Question you asked holds up, after asking it."
33593
+ )
33594
+ };
33595
+ var ContactSchema2 = external_exports.object({
33596
+ ...sendFields2,
33597
+ questions: external_exports.array(QuestionInputSchema2).min(1).max(20).optional().describe("Questions for the person, one per object."),
33598
+ /** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
33331
33599
  ackEventIds: external_exports.array(external_exports.string().uuid()).min(1).max(100).optional().describe(
33332
- "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 (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
33600
+ "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."
33601
+ ),
33602
+ wait: external_exports.boolean().optional().describe(
33603
+ "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`."
33333
33604
  )
33334
33605
  }).strict();
33335
- var ContactSchema2 = external_exports.union([StartContactSchema2, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict(), ReceiveContactSchema2]);
33606
+ var sendsAnything2 = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
33607
+ var ContactWireSchema2 = external_exports.object({
33608
+ operationId: external_exports.string().uuid(),
33609
+ ...sendFields2,
33610
+ questions: external_exports.array(QuestionWireSchema2).min(1).max(20).optional()
33611
+ }).strict().refine(sendsAnything2, "a contact sends at least one update, question, answer, withdrawal or question dependency");
33336
33612
  var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
33337
- var CONTACT_DESCRIPTION2 = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. Send options (or waiting:'hard') when you actually need an answer. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is 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`; claim_goal starts it) and your work gone quiet (`stalled`).";
33613
+ 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.
33614
+
33615
+ 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.
33616
+
33617
+ 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.
33618
+
33619
+ 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.
33620
+
33621
+ 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\`).`;
33338
33622
  var CreateGoalSchema2 = external_exports.object({
33339
33623
  outcome: external_exports.string().trim().min(1).max(1e4),
33340
33624
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -33368,21 +33652,11 @@ var UpdateGoalSchema2 = external_exports.object({
33368
33652
  state: external_exports.enum(["active", "done", "cancelled"]).optional(),
33369
33653
  progress: external_exports.string().trim().min(1).max(1e4).optional(),
33370
33654
  reviewed: external_exports.literal(true).optional(),
33371
- dueAt: external_exports.string().datetime({ offset: true }).nullable().optional(),
33372
- /** WITHDRAW YOUR OWN QUESTION (#2777, owner 2026-09-30). The question's id as every read shows it
33373
- * (the conversation's `id`, eight characters, or the whole request Entry id), on THIS Goal, asked
33374
- * by you and still open. It is cancelled, not answered: its cards close and nothing rings for it. */
33375
- withdraw: external_exports.array(external_exports.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/)).min(1).max(10).optional()
33655
+ dueAt: external_exports.string().datetime({ offset: true }).nullable().optional()
33376
33656
  }).strict().refine((v) => Object.keys(v).length > 0),
33377
33657
  reason: external_exports.string().trim().min(1).max(2e3),
33378
33658
  operationId: external_exports.string().uuid().optional()
33379
33659
  }).strict();
33380
- var UpdateGoalToolSchema2 = external_exports.object({
33381
- goalId: external_exports.string().uuid(),
33382
- revision: UpdateGoalSchema2.shape.revision,
33383
- changes: UpdateGoalSchema2.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
33384
- reason: UpdateGoalSchema2.shape.reason
33385
- }).strict();
33386
33660
  var changedGoal2 = external_exports.string().uuid().describe("The Goal to change, as a read shows it.");
33387
33661
  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.");
33388
33662
  var goalOutcome2 = external_exports.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
@@ -33390,7 +33664,7 @@ var CreateChange2 = external_exports.object({
33390
33664
  kind: external_exports.literal("create"),
33391
33665
  title: goalTitle2,
33392
33666
  outcome: goalOutcome2,
33393
- ownerId: external_exports.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
33667
+ 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."),
33394
33668
  parentGoalId: external_exports.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
33395
33669
  sourceEntryIds: external_exports.array(external_exports.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
33396
33670
  }).strict();
@@ -33400,7 +33674,7 @@ var StateChange2 = external_exports.object({ kind: external_exports.literal("sta
33400
33674
  ) }).strict();
33401
33675
  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();
33402
33676
  var DeferChange2 = external_exports.object({ kind: external_exports.literal("defer"), goalId: changedGoal2, until: external_exports.string().datetime({ offset: true }).nullable().describe(
33403
- "Postpone it until this instant (its owner is woken then), or null to take the postponement back."
33677
+ "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."
33404
33678
  ) }).strict();
33405
33679
  var MoveChange2 = external_exports.object({ kind: external_exports.literal("move"), goalId: changedGoal2, parentGoalId: external_exports.string().uuid().nullable().describe(
33406
33680
  "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
@@ -33435,8 +33709,7 @@ var ManageGoalsSchema2 = external_exports.object({
33435
33709
  var ManageGoalsToolSchema2 = external_exports.object({
33436
33710
  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.")
33437
33711
  }).strict().superRefine((v, ctx) => editNames2(v.changes, ctx));
33438
- 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_joined, 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, or null); 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 a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
33439
- var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
33712
+ 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.";
33440
33713
  var GetGoalSchema2 = external_exports.object({
33441
33714
  goalId: external_exports.string().uuid(),
33442
33715
  /** Every entry in full. Without it the read carries the person's words, open questions and your
@@ -33447,9 +33720,7 @@ var GetGoalSchema2 = external_exports.object({
33447
33720
  * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
33448
33721
  diagnose: external_exports.boolean().optional()
33449
33722
  }).strict();
33450
- var GET_GOAL_DESCRIPTION2 = "Read one Goal without claiming 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.";
33451
- var UPDATE_GOAL_DESCRIPTION2 = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). Stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. Returns the Goal as get_goal reads it, at its new revision.";
33452
- var CLAIM_GOAL_DESCRIPTION2 = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change it; use manage_goals (assign) when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
33723
+ 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.";
33453
33724
  var SearchToolSchema2 = external_exports.object({
33454
33725
  query: external_exports.string().trim().min(1).max(500),
33455
33726
  types: external_exports.array(external_exports.enum(["entry", "goal", "answer"])).min(1).optional(),
@@ -33457,15 +33728,21 @@ var SearchToolSchema2 = external_exports.object({
33457
33728
  limit: external_exports.number().int().min(1).max(20).optional()
33458
33729
  }).strict();
33459
33730
  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.";
33460
- var WhoIsWorkingSchema2 = external_exports.object({}).strict();
33731
+ var CheckActivitySchema2 = external_exports.object({}).strict();
33732
+ var FEEDBACK_TEXT_MAX2 = 5e4;
33733
+ var SendFeedbackSchema2 = external_exports.object({
33734
+ 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.`),
33735
+ title: external_exports.string().trim().min(1).max(200).optional().describe("One line naming it."),
33736
+ kind: external_exports.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
33737
+ }).strict();
33738
+ 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.";
33461
33739
  var AGENT_TOOLS2 = [
33462
- { 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(WhoIsWorkingSchema2) },
33463
33740
  { name: "contact", description: CONTACT_DESCRIPTION2, inputSchema: CONTACT_SCHEMA2 },
33464
33741
  { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION2, inputSchema: mcpInputSchema2(ManageGoalsToolSchema2) },
33465
- { name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(ClaimGoalSchema2) },
33466
33742
  { name: "get_goal", description: GET_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(GetGoalSchema2) },
33467
- { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(UpdateGoalToolSchema2) },
33468
- { name: "search", description: SEARCH_DESCRIPTION2, inputSchema: mcpInputSchema2(SearchToolSchema2) }
33743
+ { name: "search", description: SEARCH_DESCRIPTION2, inputSchema: mcpInputSchema2(SearchToolSchema2) },
33744
+ { 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) },
33745
+ { name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION2, inputSchema: mcpInputSchema2(SendFeedbackSchema2) }
33469
33746
  ];
33470
33747
  var AGENT_TOOL_NAMES2 = AGENT_TOOLS2.map((t) => t.name);
33471
33748
  var id2 = external_exports.string().uuid();
@@ -33577,33 +33854,93 @@ var BrainResultSchema2 = external_exports.object({
33577
33854
  next: BrainNextSchema2,
33578
33855
  search: BrainSearchSchema2.optional()
33579
33856
  }).strict();
33580
- var str2 = external_exports.string();
33581
- var strs2 = external_exports.array(external_exports.string());
33857
+ var str4 = external_exports.string();
33858
+ var strs4 = external_exports.array(external_exports.string());
33582
33859
  var CompactResultSchema2 = external_exports.object({
33583
- next: external_exports.object({ say: strs2, then: external_exports.enum(["listen", "hold", "end", "none"]), waitFor: strs2, reason: str2, instructions: strs2 }).strict(),
33584
- messages: external_exports.array(external_exports.object({ key: str2, to: str2, text: str2, about: strs2, entries: strs2, owed: strs2, inviteToCall: external_exports.boolean() }).strict()),
33585
- entries: external_exports.array(external_exports.object({ key: str2, from: str2, on: strs2 }).strict()),
33586
- answers: external_exports.array(external_exports.object({ question: str2, entries: strs2, summary: str2, picked: strs2 }).strict()),
33860
+ next: external_exports.object({ say: strs4, then: external_exports.enum(["listen", "hold", "end", "none"]), waitFor: strs4, reason: str4, instructions: strs4 }).strict(),
33861
+ messages: external_exports.array(external_exports.object({ key: str4, to: str4, text: str4, about: strs4, entries: strs4, owed: strs4, inviteToCall: external_exports.boolean() }).strict()),
33862
+ entries: external_exports.array(external_exports.object({ key: str4, from: str4, on: strs4 }).strict()),
33863
+ answers: external_exports.array(external_exports.object({ question: str4, entries: strs4, summary: str4, picked: strs4 }).strict()),
33587
33864
  changes: external_exports.array(external_exports.object({
33588
- key: str2,
33865
+ key: str4,
33589
33866
  what: external_exports.enum(["goal", "question", "lesson"]),
33590
33867
  op: external_exports.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
33591
- id: str2,
33592
- text: str2,
33593
- title: str2,
33594
- who: str2,
33595
- under: str2,
33868
+ id: str4,
33869
+ text: str4,
33870
+ title: str4,
33871
+ who: str4,
33872
+ under: str4,
33596
33873
  gate: external_exports.enum(["start", "complete", "answer", "none"]),
33597
- options: external_exports.array(external_exports.object({ id: str2, label: str2 }).strict()),
33874
+ options: external_exports.array(external_exports.object({ id: str4, label: str4 }).strict()),
33598
33875
  pick: external_exports.enum(["one", "many", "rank", "words"]),
33599
- entries: strs2
33876
+ entries: strs4
33600
33877
  }).strict()),
33601
- feedback: external_exports.array(strs2),
33602
- search: external_exports.object({ query: str2, within: strs2, goal: str2 }).strict()
33878
+ feedback: external_exports.array(strs4),
33879
+ search: external_exports.object({ query: str4, within: strs4, goal: str4 }).strict()
33603
33880
  }).strict();
33604
33881
  var COMPACT_RESULT_JSON_SCHEMA2 = (({ $schema: _, ...rest }) => rest)(
33605
33882
  zodToJsonSchema2(CompactResultSchema2, { $refStrategy: "none" })
33606
33883
  );
33884
+ var str22 = external_exports.string();
33885
+ var strs22 = external_exports.array(external_exports.string());
33886
+ var TalkerReplySchema2 = external_exports.object({
33887
+ /** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
33888
+ * or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
33889
+ * answered before ("" for none). */
33890
+ messages: external_exports.array(external_exports.object({ key: str22, to: str22, text: str22, about: strs22, blocks: str22 }).strict()),
33891
+ /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
33892
+ then: external_exports.enum(["listen", "hold", "end"]),
33893
+ /** Items the person's words settle: the item's handle, the chosen option IDs, the line handles. */
33894
+ answers: external_exports.array(external_exports.object({ item: str22, options: strs22, lines: strs22 }).strict()),
33895
+ /** The line handles that hold a new instruction from the person. */
33896
+ instruction: strs22,
33897
+ /** The line handles that ask for new work, a change to existing work, or a standing rule: the filer
33898
+ * reads only these (3e). */
33899
+ file: strs22,
33900
+ /** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
33901
+ need: external_exports.object({ kind: external_exports.enum(["search", "item", "agent", ""]), text: str22 }).strict(),
33902
+ /** The handle of the item to explain ("" for none). */
33903
+ explain: str22
33904
+ }).strict();
33905
+ var TALKER_REPLY_JSON_SCHEMA2 = (({ $schema: _, ...rest }) => rest)(
33906
+ zodToJsonSchema2(TalkerReplySchema2, { $refStrategy: "none" })
33907
+ );
33908
+ var str32 = external_exports.string();
33909
+ var strs32 = external_exports.array(external_exports.string());
33910
+ var FilerReplySchema2 = external_exports.object({
33911
+ /** Which Goals each new line goes on: a line's handle and Goal handles (or keys of new Goals). */
33912
+ filed: external_exports.array(external_exports.object({ line: str32, goals: strs32 }).strict()),
33913
+ goals: external_exports.array(external_exports.object({
33914
+ op: external_exports.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
33915
+ key: str32,
33916
+ goal: str32,
33917
+ title: str32,
33918
+ outcome: str32,
33919
+ owner: str32,
33920
+ under: str32,
33921
+ until: str32,
33922
+ on: str32,
33923
+ gate: external_exports.enum(["start", "complete", "answer", ""]),
33924
+ why: str32
33925
+ }).strict()),
33926
+ questions: external_exports.array(external_exports.object({
33927
+ op: external_exports.enum(["create", "edit", "assign", "withdraw"]),
33928
+ key: str32,
33929
+ question: str32,
33930
+ text: str32,
33931
+ to: str32,
33932
+ goal: str32,
33933
+ options: external_exports.array(external_exports.object({ id: str32, label: str32 }).strict()),
33934
+ pick: external_exports.enum(["one", "many", "rank", "words", ""]),
33935
+ blocks: str32
33936
+ }).strict()),
33937
+ lessons: external_exports.array(external_exports.object({ op: external_exports.enum(["remember", "revise", "withdraw"]), lesson: str32, text: str32, scope: str32 }).strict()),
33938
+ /** Line handles whose words comment on Paigy itself. */
33939
+ feedback: strs32
33940
+ }).strict();
33941
+ var FILER_REPLY_JSON_SCHEMA2 = (({ $schema: _, ...rest }) => rest)(
33942
+ zodToJsonSchema2(FilerReplySchema2, { $refStrategy: "none" })
33943
+ );
33607
33944
  function entryWords(entry) {
33608
33945
  const content = entry.content;
33609
33946
  if (content && "sealed" in content) return "";
@@ -33627,7 +33964,10 @@ var ContextSchema2 = external_exports.object({
33627
33964
  title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
33628
33965
  description: external_exports.array(external_exports.string().min(1)).describe(
33629
33966
  "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."
33630
- )
33967
+ ),
33968
+ /** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
33969
+ * a body on a tap. Absent on an ask sent without units, which renders as before. */
33970
+ units: external_exports.array(external_exports.object({ title: external_exports.string(), body: external_exports.string() })).optional()
33631
33971
  });
33632
33972
  var ParticipantSchema2 = external_exports.object({
33633
33973
  kind: external_exports.enum(["human", "agent"]),
@@ -33702,7 +34042,7 @@ var NotifyRequestFields2 = external_exports.object({
33702
34042
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
33703
34043
  * superRefine at the bottom enforces exactly one of the two. */
33704
34044
  context: ContextSchema2.optional(),
33705
- options: external_exports.array(OptionInputSchema2).min(OPTIONS_MIN2).max(OPTIONS_MAX2).optional().describe(
34045
+ 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(
33706
34046
  "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)."
33707
34047
  ),
33708
34048
  points: external_exports.array(external_exports.string().min(1)).optional().describe(
@@ -34463,8 +34803,8 @@ var QueueItemSchema2 = external_exports.object({
34463
34803
  repo: external_exports.string().nullable().optional(),
34464
34804
  createdAt: external_exports.string(),
34465
34805
  updatedAt: external_exports.string().nullable().default(null),
34466
- /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
34467
- * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
34806
+ /** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
34807
+ * as progress). `updatedAt` moves for reasons nobody chose — a
34468
34808
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
34469
34809
  lastProgressAt: external_exports.string().nullable().optional(),
34470
34810
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
@@ -34620,7 +34960,7 @@ var HostDecisionSchema2 = external_exports.object({
34620
34960
  /** The agent's token id: the row's `recipient`. */
34621
34961
  agent: external_exports.string().uuid(),
34622
34962
  decision: external_exports.enum(["stood_back", "took_over"]),
34623
- /** The work it was about: the Goal `claim_goal` would hand that agent next. */
34963
+ /** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
34624
34964
  goalId: external_exports.string().uuid().nullable().optional(),
34625
34965
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
34626
34966
  seenAt: external_exports.string().datetime().nullable().optional(),
@@ -34849,6 +35189,32 @@ function sameTail(a, b) {
34849
35189
  // src/paigy/bridge.ts
34850
35190
  init_dist();
34851
35191
 
35192
+ // src/paigy/cut.ts
35193
+ function cut(text, title = "Details") {
35194
+ const all = text.trim();
35195
+ if (all.length <= ASK_MAX2) return { main: all };
35196
+ const main2 = head(all, ASK_MAX2);
35197
+ const units3 = [];
35198
+ let rest = all.slice(main2.length).trim();
35199
+ while (rest && units3.length < UNITS_MAX2) {
35200
+ const last = units3.length === UNITS_MAX2 - 1;
35201
+ const body = last && rest.length > UNIT_BODY_MAX2 ? `${head(rest, UNIT_BODY_MAX2 - 1).trimEnd()}\u2026` : head(rest, UNIT_BODY_MAX2);
35202
+ units3.push({ title: units3.length ? `${title} (${units3.length + 1})` : title, body: body.trim() });
35203
+ rest = last ? "" : rest.slice(body.length).trim();
35204
+ }
35205
+ return { main: main2.trim(), units: units3 };
35206
+ }
35207
+ function head(text, max) {
35208
+ if (text.length <= max) return text;
35209
+ const window = text.slice(0, max + 1);
35210
+ for (const boundary of [/\n\s*\n/g, /[.!?](\s)/g, /\s/g]) {
35211
+ let at = -1;
35212
+ for (const m of window.matchAll(boundary)) if (m.index > max / 3 && m.index < max) at = m.index + (boundary.source.startsWith("[") ? 1 : 0);
35213
+ if (at > 0) return text.slice(0, at);
35214
+ }
35215
+ return text.slice(0, max);
35216
+ }
35217
+
34852
35218
  // src/paigy/limit.ts
34853
35219
  var LIMIT_PREFIXES = [
34854
35220
  "You've hit your",
@@ -34977,10 +35343,19 @@ var LIMITS = episodes();
34977
35343
  // src/paigy/bridge.ts
34978
35344
  function railFor(opts) {
34979
35345
  return {
34980
- claim: (goalId) => claimGoal(goalId, opts),
35346
+ next: async () => {
35347
+ const waiting = await receive(opts);
35348
+ const goalId = waiting.claimable?.goalId ?? waiting.review?.[0]?.goalId;
35349
+ return goalId ? getGoal(goalId, opts) : { state: "idle", message: "" };
35350
+ },
34981
35351
  read: (goalId) => getGoal(goalId, opts),
34982
- update: (goalId, input) => updateGoal(goalId, input, opts),
34983
- // One read and back: the pump owns the waiting, so a Call must not hold a window here.
35352
+ complete: async (goalId, revision) => {
35353
+ const done = await manageGoals({ changes: [{ kind: "state", goalId, state: "completed", ...revision !== void 0 ? { revision } : {} }] }, opts);
35354
+ const result = done.results[0];
35355
+ if (result?.status !== "applied") throw new Error(`manage_goals failed: ${result?.error ?? "no result"}`);
35356
+ },
35357
+ ack: (eventIds) => acknowledge(eventIds, opts),
35358
+ // One read and back: the pump owns the waiting, so no wait is held here.
34984
35359
  contact: (input) => contact(input, { ...opts, waits: false }),
34985
35360
  create: async (input) => {
34986
35361
  const made = await manageGoals({ changes: [{ kind: "create", ...input }] }, opts);
@@ -35009,13 +35384,18 @@ function decisionFrom(result) {
35009
35384
  }
35010
35385
  async function ask(state, rail, input) {
35011
35386
  if (!state.goal) return null;
35387
+ const { main: main2, units: units3 } = cut(input.ask);
35012
35388
  try {
35013
35389
  const sent = await rail.contact({
35014
- asks: [{ ask: input.ask, parentId: state.goal.id, options: input.options }],
35015
- waiting: "hard",
35016
- channel: "call"
35390
+ questions: [{
35391
+ question: main2,
35392
+ ...units3 ? { units: units3 } : {},
35393
+ goalId: state.goal.id,
35394
+ options: input.options,
35395
+ workItBlocks: [{ blocked: { type: "goal", id: state.goal.id }, action: "complete", reason: "The agent cannot go on without this approval." }]
35396
+ }]
35017
35397
  });
35018
- if ("recorded" in sent) return null;
35398
+ if ("sent" in sent) return null;
35019
35399
  const entryId = sent.requestEntryIds[0];
35020
35400
  if (!entryId) return null;
35021
35401
  (state.seen ??= /* @__PURE__ */ new Set()).add(entryId);
@@ -35033,57 +35413,54 @@ async function askPermission(event, state, rail) {
35033
35413
  ]
35034
35414
  }));
35035
35415
  }
35036
- async function finish(result, state, rail, opts) {
35416
+ async function finish(state, rail, opts) {
35037
35417
  const goal = state.goal;
35038
35418
  if (!goal) return;
35039
- const progress = result?.trim().slice(0, 1e4) || void 0;
35040
- let revision = goal.revision;
35041
35419
  try {
35042
35420
  const held = await rail.read(goal.id);
35043
- revision = held.revision ?? revision;
35044
35421
  const owed = state.noted?.some((b) => b.goals.includes(goal.id));
35045
35422
  if (held.state === "done" || held.state === "cancelled") {
35046
35423
  if (!owed) state.goal = void 0;
35047
35424
  return;
35048
35425
  }
35049
35426
  if (!held.openDecisionNeedIds?.length && !owed) {
35050
- await rail.update(goal.id, { revision, changes: { state: "done", ...progress ? { progress } : {} }, reason: "The agent finished its turn." });
35427
+ await rail.complete(goal.id, held.revision ?? goal.revision);
35051
35428
  state.goal = void 0;
35052
- return;
35053
35429
  }
35054
35430
  } catch (error) {
35055
35431
  opts?.log?.(`\u26A0 the Goal did not close, still holding it: ${error.message}`);
35056
35432
  }
35057
- if (!progress) return;
35058
- try {
35059
- await rail.update(goal.id, { revision, changes: { progress }, reason: "The agent's turn ended; its Goal is not closed yet." });
35060
- } catch (error) {
35061
- opts?.log?.(`\u26A0 could not keep the turn as progress: ${error.message}`);
35062
- }
35063
35433
  }
35064
35434
  async function cannotWork(report, state, rail, opts) {
35065
35435
  state.resting = true;
35066
- const say = (parentId) => rail.contact({ asks: [{ ask: report, ...parentId ? { parentId } : {} }], waiting: "none", channel: "notification" });
35436
+ const { main: main2, units: units3 } = cut(report);
35437
+ const say = (goalId) => rail.contact({ updates: [{ message: main2, ...units3 ? { units: units3 } : {}, goalIds: [goalId] }] });
35438
+ const own = async () => (await rail.create({ title: "Agent cannot work", outcome: main2 })).goalId;
35067
35439
  try {
35068
35440
  if (!state.goal) {
35069
- const claimed = await rail.claim().catch(() => void 0);
35070
- if (claimed?.goalId && claimed.revision !== void 0) state.goal = { id: claimed.goalId, revision: claimed.revision };
35441
+ const next = await rail.next().catch(() => void 0);
35442
+ if (next?.goalId && next.revision !== void 0) state.goal = { id: next.goalId, revision: next.revision };
35071
35443
  }
35072
35444
  if (!state.goal) {
35073
- await say();
35445
+ await say(await own());
35074
35446
  return;
35075
35447
  }
35076
35448
  try {
35077
35449
  await say(state.goal.id);
35078
35450
  } catch (error) {
35079
- if (!/goal_parent_invalid/.test(error.message)) throw error;
35080
- opts?.log?.(`\u26A0 the held Goal ${state.goal.id.slice(0, 8)} refused the notice (goal_parent_invalid) \u2014 saying it unplaced`);
35081
- await say();
35451
+ if (!/goal_not_found/.test(error.message)) throw error;
35452
+ 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`);
35453
+ await say(await own());
35082
35454
  }
35083
35455
  } catch (error) {
35084
35456
  opts?.log?.(`\u26A0 could not say the agent cannot work: ${error.message}`);
35085
35457
  }
35086
35458
  }
35459
+ async function callForResume(text, state, rail) {
35460
+ const { main: main2, units: units3 } = cut(text);
35461
+ const goalId = state.goal?.id ?? (await rail.create({ title: "Usage limit reset", outcome: main2 })).goalId;
35462
+ return rail.contact({ updates: [{ message: main2, ...units3 ? { units: units3 } : {}, goalIds: [goalId], userExplicitlyRequested: "call" }] });
35463
+ }
35087
35464
  function conversationFull(result) {
35088
35465
  return /prompt is too long/i.test(result ?? "");
35089
35466
  }
@@ -35110,14 +35487,14 @@ async function afterTurn(event, state, rail, opts) {
35110
35487
  ${event.result?.trim() || "No result reported."}`, state, rail, opts);
35111
35488
  return void 0;
35112
35489
  }
35113
- await finish(event.result, state, rail, opts);
35490
+ await finish(state, rail, opts);
35114
35491
  state.typed = void 0;
35115
35492
  state.resting = true;
35116
35493
  return void 0;
35117
35494
  }
35118
35495
 
35119
35496
  // src/paigy/conversation.ts
35120
- 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).";
35497
+ 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).";
35121
35498
  var SAFETY_MS = 6e4;
35122
35499
  var ASK_BEAT_MS = 5e3;
35123
35500
  function beat() {
@@ -35217,18 +35594,23 @@ async function gather(state, rail, held) {
35217
35594
  if (!looks.has(goalId)) looks.set(goalId, { via, ...title ? { title } : {}, ...since !== void 0 ? { since } : {} });
35218
35595
  };
35219
35596
  const waiting = await rail.waiting().catch(() => null);
35220
- const listed = /* @__PURE__ */ new Set();
35597
+ const listed = /* @__PURE__ */ new Map();
35598
+ const list = (goalId, eventId) => {
35599
+ const ids = listed.get(goalId) ?? [];
35600
+ if (eventId && !ids.includes(eventId)) ids.push(eventId);
35601
+ listed.set(goalId, ids);
35602
+ };
35221
35603
  const announced = state.announced ??= /* @__PURE__ */ new Set();
35222
35604
  if (waiting) {
35223
35605
  for (const id3 of announced) if (!(waiting.assigned ?? []).some((g) => g.goalId === id3)) announced.delete(id3);
35224
35606
  }
35225
35607
  for (const g of waiting?.assigned ?? []) if (g.goalId !== state.goal.id && !announced.has(g.goalId)) look2(g.goalId, "assigned", g.title);
35226
35608
  for (const g of waiting?.review ?? []) {
35227
- listed.add(g.goalId);
35609
+ list(g.goalId, g.eventId);
35228
35610
  look2(g.goalId, "waiting work", g.title);
35229
35611
  }
35230
35612
  for (const d of waiting?.deliveries ?? []) for (const id3 of d.goalIds) {
35231
- listed.add(id3);
35613
+ list(id3, d.deliveryId);
35232
35614
  look2(id3, "waiting work");
35233
35615
  }
35234
35616
  const moved = movedBelow(held, state);
@@ -35253,7 +35635,7 @@ async function gather(state, rail, held) {
35253
35635
  for (const { goal, since } of moved) if (read3.has(goal.goalId)) state.below.goals.set(goal.goalId, { since, at: goal.updatedAt ?? null });
35254
35636
  return { heard, listed };
35255
35637
  }
35256
- var GIVEN = "This Goal was given to you, and nobody has started it. Carry on with what you're doing unless it changes that; claim_goal with its id when you take it up.";
35638
+ 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.";
35257
35639
  var says = (h) => h.words.length > 0 || h.via === "assigned";
35258
35640
  function whatItIs(h) {
35259
35641
  const outcome2 = h.brief.outcome?.trim() ?? "";
@@ -35283,7 +35665,10 @@ async function hear(session, state, rail, held, opts) {
35283
35665
  if (opts?.tally) opts.tally.announced += goals.length;
35284
35666
  }
35285
35667
  }
35286
- for (const h of heard) if (listed.has(h.goalId)) await acknowledge(h.goalId, h.brief, state, rail, log);
35668
+ for (const h of heard) {
35669
+ const events = listed.get(h.goalId);
35670
+ if (events) await acknowledge2(h.goalId, h.brief, events, state, rail, log);
35671
+ }
35287
35672
  }
35288
35673
  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.";
35289
35674
  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.";
@@ -35329,15 +35714,16 @@ function settle(session, state, inbox, log) {
35329
35714
  send2(session, state, [...seen.length ? [SEEN_MID_TURN, ...seen.map((b) => b.words)] : [], ...unseen.map((b) => b.words)].join("\n\n"));
35330
35715
  log?.(`\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)`);
35331
35716
  }
35332
- async function acknowledge(goalId, brief, state, rail, log) {
35717
+ async function acknowledge2(goalId, brief, events, state, rail, log) {
35718
+ const ids = [.../* @__PURE__ */ new Set([...brief.reviewEventId ? [brief.reviewEventId] : [], ...events])];
35719
+ if (!ids.length) return;
35333
35720
  const acked = state.acked ??= /* @__PURE__ */ new Map();
35334
- if (acked.has(goalId) && acked.get(goalId) === brief.revision) return;
35335
- acked.set(goalId, brief.revision);
35721
+ const key3 = ids.join(",");
35722
+ if (acked.get(goalId) === key3) return;
35723
+ acked.set(goalId, key3);
35336
35724
  try {
35337
- if (brief.revision === void 0) throw new Error("review_revision_missing");
35338
- const done = await rail.update(goalId, { revision: brief.revision, changes: { reviewed: true }, reason: "Read by the harness session; the person's words on it are typed in." });
35339
- acked.set(goalId, done.revision);
35340
- if (state.goal?.id === goalId && done.revision !== void 0) state.goal.revision = done.revision;
35725
+ const refused = (await rail.ack(ids)).filter((a) => a.refused);
35726
+ if (refused.length) log?.(`\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`);
35341
35727
  } catch (error) {
35342
35728
  log?.(`\u26A0 could not acknowledge ${goalId.slice(0, 8)}: ${error.message} \u2014 its words are typed in; it stays listed until acknowledged`);
35343
35729
  }
@@ -35363,13 +35749,13 @@ async function look(session, state, rail, opts) {
35363
35749
  if (state.resting && state.noted?.length && !state.asks?.size) return settle(session, state, opts?.inbox, opts?.log);
35364
35750
  if (!state.goal) {
35365
35751
  if (!state.resting) return;
35366
- let brief2 = await rail.claim();
35752
+ let brief2 = await rail.next();
35367
35753
  if (!brief2.goalId || brief2.revision === void 0) {
35368
35754
  if (opts?.initialPrompt?.trim()) {
35369
35755
  const prompt = opts.initialPrompt.trim();
35370
35756
  opts.initialPrompt = void 0;
35371
35757
  const admitted = await rail.create({ title: prompt.split(/\s+/).slice(0, 5).join(" ").slice(0, 80), outcome: prompt });
35372
- brief2 = await rail.claim();
35758
+ brief2 = await rail.next();
35373
35759
  if (!brief2.goalId || brief2.revision === void 0) {
35374
35760
  state.goal = { id: admitted.goalId, revision: admitted.revision };
35375
35761
  send2(session, state, [HARNESS_INSTRUCTION, named(admitted.goalId, void 0, { outcome: prompt }), prompt].join("\n\n"));
@@ -35391,7 +35777,7 @@ async function look(session, state, rail, opts) {
35391
35777
  const earlier = left ? `(${left} earlier entries on this Goal are left out or shortened; get_goal with history: true reads them in full.)` : "";
35392
35778
  const fullText = [outcome2, earlier, ...said].filter(Boolean).join("\n\n");
35393
35779
  send2(session, state, [HARNESS_INSTRUCTION, named(brief2.goalId, void 0, brief2), fullText].filter(Boolean).join("\n\n"));
35394
- if (ended(brief2.state)) await acknowledge(brief2.goalId, brief2, state, rail, opts?.log);
35780
+ if (ended(brief2.state)) await acknowledge2(brief2.goalId, brief2, [], state, rail, opts?.log);
35395
35781
  return;
35396
35782
  }
35397
35783
  const brief = await rail.read(state.goal.id);
@@ -35479,7 +35865,7 @@ function runHarness(opts) {
35479
35865
  const clock4 = beat();
35480
35866
  let session = null;
35481
35867
  const speaksAs = opts.session ?? opts.resume ?? sessionId();
35482
- const instance = randomUUID5();
35868
+ const instance = randomUUID4();
35483
35869
  const rail = railFor({ token: opts.token, reach: reachAs(speaksAs, instance) });
35484
35870
  let inbox;
35485
35871
  if (opts.harness === "claude") {
@@ -35493,7 +35879,7 @@ function runHarness(opts) {
35493
35879
  function runningAgain() {
35494
35880
  state.carriedOn = false;
35495
35881
  opts.log("\u25B6 running again after the limit");
35496
- LIMITS.running(name, (text) => rail.contact({ asks: [{ ask: text, ...state.goal ? { parentId: state.goal.id } : {} }], waiting: "none", channel: "call" }), opts.log);
35882
+ LIMITS.running(name, (text) => callForResume(text, state, rail), opts.log);
35497
35883
  }
35498
35884
  async function handle(event) {
35499
35885
  if (event.kind === "error") {
@@ -35675,7 +36061,7 @@ function listRepos(deps) {
35675
36061
 
35676
36062
  // src/holder.ts
35677
36063
  init_dist();
35678
- import { execFileSync as execFileSync3 } from "child_process";
36064
+ import { execFileSync as execFileSync2 } from "child_process";
35679
36065
  import { readFileSync as readFileSync5, readdirSync as readdirSync3 } from "fs";
35680
36066
  import { homedir as homedir7 } from "os";
35681
36067
  import { join as join7 } from "path";
@@ -35690,7 +36076,7 @@ function isAlive(pid) {
35690
36076
  function psStart(pid, zone) {
35691
36077
  try {
35692
36078
  const env = zone === "UTC" ? { ...process.env, TZ: "UTC", LC_ALL: "C" } : { ...process.env, LC_ALL: "C" };
35693
- return execFileSync3("ps", ["-o", "lstart=", "-p", String(pid)], { env, encoding: "utf8", timeout: 2e3 }).trim() || null;
36079
+ return execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], { env, encoding: "utf8", timeout: 2e3 }).trim() || null;
35694
36080
  } catch {
35695
36081
  return null;
35696
36082
  }
@@ -35745,7 +36131,7 @@ function localHolderPids(deps = {}) {
35745
36131
  }
35746
36132
  function psParent(pid) {
35747
36133
  try {
35748
- const out = execFileSync3("ps", ["-o", "ppid=", "-p", String(pid)], { encoding: "utf8", timeout: 2e3 }).trim();
36134
+ const out = execFileSync2("ps", ["-o", "ppid=", "-p", String(pid)], { encoding: "utf8", timeout: 2e3 }).trim();
35749
36135
  const n = Number(out);
35750
36136
  return Number.isInteger(n) && n > 0 ? n : null;
35751
36137
  } catch {
@@ -35869,7 +36255,7 @@ function ownerSession(slot, deps = {}) {
35869
36255
  const prefix = m[1];
35870
36256
  const root = deps.projectsDir ?? join8(homedir8(), ".claude", "projects");
35871
36257
  const list = deps.list ?? ((dir) => readdirSync4(dir));
35872
- const head = deps.head ?? readHead;
36258
+ const head2 = deps.head ?? readHead;
35873
36259
  let folders;
35874
36260
  try {
35875
36261
  folders = list(root);
@@ -35887,7 +36273,7 @@ function ownerSession(slot, deps = {}) {
35887
36273
  if (!file) continue;
35888
36274
  let text;
35889
36275
  try {
35890
- text = head(join8(root, folder, file));
36276
+ text = head2(join8(root, folder, file));
35891
36277
  } catch {
35892
36278
  continue;
35893
36279
  }
@@ -36829,7 +37215,7 @@ async function main() {
36829
37215
  let launched;
36830
37216
  if (parsed.launch) {
36831
37217
  overrideToken(readSlot("Desktop") || null);
36832
- const session = randomUUID6();
37218
+ const session = randomUUID5();
36833
37219
  const minted = await hatch(parsed.launch, parsed.voice ?? null, {
36834
37220
  reach: reachAs(session),
36835
37221
  brief: parsed.prompt,