@paigy/harness 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1148 -1570
- package/dist/main.js +957 -1352
- package/package.json +12 -13
package/dist/cli.js
CHANGED
|
@@ -746,12 +746,12 @@ function isValidCidr(ip, version) {
|
|
|
746
746
|
}
|
|
747
747
|
return false;
|
|
748
748
|
}
|
|
749
|
-
function floatSafeRemainder(val,
|
|
749
|
+
function floatSafeRemainder(val, step2) {
|
|
750
750
|
const valDecCount = (val.toString().split(".")[1] || "").length;
|
|
751
|
-
const stepDecCount = (
|
|
751
|
+
const stepDecCount = (step2.toString().split(".")[1] || "").length;
|
|
752
752
|
const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount;
|
|
753
753
|
const valInt = Number.parseInt(val.toFixed(decCount).replace(".", ""));
|
|
754
|
-
const stepInt = Number.parseInt(
|
|
754
|
+
const stepInt = Number.parseInt(step2.toFixed(decCount).replace(".", ""));
|
|
755
755
|
return valInt % stepInt / 10 ** decCount;
|
|
756
756
|
}
|
|
757
757
|
function deepPartialify(schema) {
|
|
@@ -6492,7 +6492,7 @@ var require_connect = __commonJS({
|
|
|
6492
6492
|
const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions);
|
|
6493
6493
|
timeout = timeout == null ? 1e4 : timeout;
|
|
6494
6494
|
allowH2 = allowH2 != null ? allowH2 : false;
|
|
6495
|
-
return function
|
|
6495
|
+
return function connect2({ hostname: hostname2, host, protocol, port, servername, localAddress, httpSocket }, callback) {
|
|
6496
6496
|
let socket;
|
|
6497
6497
|
if (protocol === "https:") {
|
|
6498
6498
|
if (!tls) {
|
|
@@ -11578,7 +11578,7 @@ var require_client = __commonJS({
|
|
|
11578
11578
|
strictContentLength,
|
|
11579
11579
|
maxCachedSessions,
|
|
11580
11580
|
maxRedirections,
|
|
11581
|
-
connect:
|
|
11581
|
+
connect: connect3,
|
|
11582
11582
|
maxRequestsPerClient,
|
|
11583
11583
|
localAddress,
|
|
11584
11584
|
maxResponseSize,
|
|
@@ -11629,7 +11629,7 @@ var require_client = __commonJS({
|
|
|
11629
11629
|
if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) {
|
|
11630
11630
|
throw new InvalidArgumentError("bodyTimeout must be a positive integer or zero");
|
|
11631
11631
|
}
|
|
11632
|
-
if (
|
|
11632
|
+
if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
|
|
11633
11633
|
throw new InvalidArgumentError("connect must be a function or an object");
|
|
11634
11634
|
}
|
|
11635
11635
|
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
|
@@ -11653,15 +11653,15 @@ var require_client = __commonJS({
|
|
|
11653
11653
|
if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== "number" || maxConcurrentStreams < 1)) {
|
|
11654
11654
|
throw new InvalidArgumentError("maxConcurrentStreams must be a positive integer, greater than 0");
|
|
11655
11655
|
}
|
|
11656
|
-
if (typeof
|
|
11657
|
-
|
|
11656
|
+
if (typeof connect3 !== "function") {
|
|
11657
|
+
connect3 = buildConnector({
|
|
11658
11658
|
...tls,
|
|
11659
11659
|
maxCachedSessions,
|
|
11660
11660
|
allowH2,
|
|
11661
11661
|
socketPath,
|
|
11662
11662
|
timeout: connectTimeout,
|
|
11663
11663
|
...autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
|
|
11664
|
-
...
|
|
11664
|
+
...connect3
|
|
11665
11665
|
});
|
|
11666
11666
|
}
|
|
11667
11667
|
if (interceptors?.Client && Array.isArray(interceptors.Client)) {
|
|
@@ -11676,7 +11676,7 @@ var require_client = __commonJS({
|
|
|
11676
11676
|
this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })];
|
|
11677
11677
|
}
|
|
11678
11678
|
this[kUrl] = util2.parseOrigin(url);
|
|
11679
|
-
this[kConnector] =
|
|
11679
|
+
this[kConnector] = connect3;
|
|
11680
11680
|
this[kPipelining] = pipelining != null ? pipelining : 1;
|
|
11681
11681
|
this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize;
|
|
11682
11682
|
this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout;
|
|
@@ -11730,7 +11730,7 @@ var require_client = __commonJS({
|
|
|
11730
11730
|
}
|
|
11731
11731
|
/* istanbul ignore: only used for test */
|
|
11732
11732
|
[kConnect](cb) {
|
|
11733
|
-
|
|
11733
|
+
connect2(this);
|
|
11734
11734
|
this.once("connect", cb);
|
|
11735
11735
|
}
|
|
11736
11736
|
[kDispatch](opts, handler) {
|
|
@@ -11794,7 +11794,7 @@ var require_client = __commonJS({
|
|
|
11794
11794
|
assert(client[kSize] === 0);
|
|
11795
11795
|
}
|
|
11796
11796
|
}
|
|
11797
|
-
async function
|
|
11797
|
+
async function connect2(client) {
|
|
11798
11798
|
assert(!client[kConnecting]);
|
|
11799
11799
|
assert(!client[kHTTPContext]);
|
|
11800
11800
|
let { host, hostname: hostname2, protocol, port } = client[kUrl];
|
|
@@ -11965,7 +11965,7 @@ var require_client = __commonJS({
|
|
|
11965
11965
|
return;
|
|
11966
11966
|
}
|
|
11967
11967
|
if (!client[kHTTPContext]) {
|
|
11968
|
-
|
|
11968
|
+
connect2(client);
|
|
11969
11969
|
return;
|
|
11970
11970
|
}
|
|
11971
11971
|
if (client[kHTTPContext].destroyed) {
|
|
@@ -12258,7 +12258,7 @@ var require_pool = __commonJS({
|
|
|
12258
12258
|
constructor(origin, {
|
|
12259
12259
|
connections,
|
|
12260
12260
|
factory = defaultFactory,
|
|
12261
|
-
connect,
|
|
12261
|
+
connect: connect2,
|
|
12262
12262
|
connectTimeout,
|
|
12263
12263
|
tls,
|
|
12264
12264
|
maxCachedSessions,
|
|
@@ -12274,25 +12274,25 @@ var require_pool = __commonJS({
|
|
|
12274
12274
|
if (typeof factory !== "function") {
|
|
12275
12275
|
throw new InvalidArgumentError("factory must be a function.");
|
|
12276
12276
|
}
|
|
12277
|
-
if (
|
|
12277
|
+
if (connect2 != null && typeof connect2 !== "function" && typeof connect2 !== "object") {
|
|
12278
12278
|
throw new InvalidArgumentError("connect must be a function or an object");
|
|
12279
12279
|
}
|
|
12280
|
-
if (typeof
|
|
12281
|
-
|
|
12280
|
+
if (typeof connect2 !== "function") {
|
|
12281
|
+
connect2 = buildConnector({
|
|
12282
12282
|
...tls,
|
|
12283
12283
|
maxCachedSessions,
|
|
12284
12284
|
allowH2,
|
|
12285
12285
|
socketPath,
|
|
12286
12286
|
timeout: connectTimeout,
|
|
12287
12287
|
...autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
|
|
12288
|
-
...
|
|
12288
|
+
...connect2
|
|
12289
12289
|
});
|
|
12290
12290
|
}
|
|
12291
12291
|
super(options);
|
|
12292
12292
|
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) ? options.interceptors.Pool : [];
|
|
12293
12293
|
this[kConnections] = connections || null;
|
|
12294
12294
|
this[kUrl] = util2.parseOrigin(origin);
|
|
12295
|
-
this[kOptions] = { ...util2.deepClone(options), connect, allowH2 };
|
|
12295
|
+
this[kOptions] = { ...util2.deepClone(options), connect: connect2, allowH2 };
|
|
12296
12296
|
this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0;
|
|
12297
12297
|
this[kFactory] = factory;
|
|
12298
12298
|
this.on("connectionError", (origin2, targets, error) => {
|
|
@@ -12487,22 +12487,22 @@ var require_agent = __commonJS({
|
|
|
12487
12487
|
return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts);
|
|
12488
12488
|
}
|
|
12489
12489
|
var Agent = class extends DispatcherBase {
|
|
12490
|
-
constructor({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
|
12490
|
+
constructor({ factory = defaultFactory, maxRedirections = 0, connect: connect2, ...options } = {}) {
|
|
12491
12491
|
if (typeof factory !== "function") {
|
|
12492
12492
|
throw new InvalidArgumentError("factory must be a function.");
|
|
12493
12493
|
}
|
|
12494
|
-
if (
|
|
12494
|
+
if (connect2 != null && typeof connect2 !== "function" && typeof connect2 !== "object") {
|
|
12495
12495
|
throw new InvalidArgumentError("connect must be a function or an object");
|
|
12496
12496
|
}
|
|
12497
12497
|
if (!Number.isInteger(maxRedirections) || maxRedirections < 0) {
|
|
12498
12498
|
throw new InvalidArgumentError("maxRedirections must be a positive number");
|
|
12499
12499
|
}
|
|
12500
12500
|
super(options);
|
|
12501
|
-
if (
|
|
12502
|
-
|
|
12501
|
+
if (connect2 && typeof connect2 !== "function") {
|
|
12502
|
+
connect2 = { ...connect2 };
|
|
12503
12503
|
}
|
|
12504
12504
|
this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent) ? options.interceptors.Agent : [createRedirectInterceptor({ maxRedirections })];
|
|
12505
|
-
this[kOptions] = { ...util2.deepClone(options), connect };
|
|
12505
|
+
this[kOptions] = { ...util2.deepClone(options), connect: connect2 };
|
|
12506
12506
|
this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0;
|
|
12507
12507
|
this[kMaxRedirections] = maxRedirections;
|
|
12508
12508
|
this[kFactory] = factory;
|
|
@@ -12597,16 +12597,16 @@ var require_proxy_agent = __commonJS({
|
|
|
12597
12597
|
}
|
|
12598
12598
|
var Http1ProxyWrapper = class extends DispatcherBase {
|
|
12599
12599
|
#client;
|
|
12600
|
-
constructor(proxyUrl, { headers = {}, connect, factory }) {
|
|
12600
|
+
constructor(proxyUrl, { headers = {}, connect: connect2, factory }) {
|
|
12601
12601
|
super();
|
|
12602
12602
|
if (!proxyUrl) {
|
|
12603
12603
|
throw new InvalidArgumentError("Proxy URL is mandatory");
|
|
12604
12604
|
}
|
|
12605
12605
|
this[kProxyHeaders] = headers;
|
|
12606
12606
|
if (factory) {
|
|
12607
|
-
this.#client = factory(proxyUrl, { connect });
|
|
12607
|
+
this.#client = factory(proxyUrl, { connect: connect2 });
|
|
12608
12608
|
} else {
|
|
12609
|
-
this.#client = new Client(proxyUrl, { connect });
|
|
12609
|
+
this.#client = new Client(proxyUrl, { connect: connect2 });
|
|
12610
12610
|
}
|
|
12611
12611
|
}
|
|
12612
12612
|
[kDispatch](opts, handler) {
|
|
@@ -12668,7 +12668,7 @@ var require_proxy_agent = __commonJS({
|
|
|
12668
12668
|
} else if (username && password) {
|
|
12669
12669
|
this[kProxyHeaders]["proxy-authorization"] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString("base64")}`;
|
|
12670
12670
|
}
|
|
12671
|
-
const
|
|
12671
|
+
const connect2 = buildConnector({ ...opts.proxyTls });
|
|
12672
12672
|
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls });
|
|
12673
12673
|
const agentFactory = opts.factory || defaultAgentFactory;
|
|
12674
12674
|
const factory = (origin2, options) => {
|
|
@@ -12676,13 +12676,13 @@ var require_proxy_agent = __commonJS({
|
|
|
12676
12676
|
if (!this[kTunnelProxy] && protocol2 === "http:" && this[kProxy].protocol === "http:") {
|
|
12677
12677
|
return new Http1ProxyWrapper(this[kProxy].uri, {
|
|
12678
12678
|
headers: this[kProxyHeaders],
|
|
12679
|
-
connect,
|
|
12679
|
+
connect: connect2,
|
|
12680
12680
|
factory: agentFactory
|
|
12681
12681
|
});
|
|
12682
12682
|
}
|
|
12683
12683
|
return agentFactory(origin2, options);
|
|
12684
12684
|
};
|
|
12685
|
-
this[kClient] = clientFactory(url, { connect });
|
|
12685
|
+
this[kClient] = clientFactory(url, { connect: connect2 });
|
|
12686
12686
|
this[kAgent] = new Agent({
|
|
12687
12687
|
...opts,
|
|
12688
12688
|
factory,
|
|
@@ -14379,10 +14379,10 @@ var require_api_connect = __commonJS({
|
|
|
14379
14379
|
}
|
|
14380
14380
|
}
|
|
14381
14381
|
};
|
|
14382
|
-
function
|
|
14382
|
+
function connect2(opts, callback) {
|
|
14383
14383
|
if (callback === void 0) {
|
|
14384
14384
|
return new Promise((resolve3, reject) => {
|
|
14385
|
-
|
|
14385
|
+
connect2.call(this, opts, (err, data) => {
|
|
14386
14386
|
return err ? reject(err) : resolve3(data);
|
|
14387
14387
|
});
|
|
14388
14388
|
});
|
|
@@ -14398,7 +14398,7 @@ var require_api_connect = __commonJS({
|
|
|
14398
14398
|
queueMicrotask(() => callback(err, { opaque }));
|
|
14399
14399
|
}
|
|
14400
14400
|
}
|
|
14401
|
-
module.exports =
|
|
14401
|
+
module.exports = connect2;
|
|
14402
14402
|
}
|
|
14403
14403
|
});
|
|
14404
14404
|
|
|
@@ -21050,19 +21050,19 @@ var require_connection = __commonJS({
|
|
|
21050
21050
|
ws[kReadyState] = states.CLOSING;
|
|
21051
21051
|
} else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) {
|
|
21052
21052
|
ws[kSentClose] = sentCloseFrameState.PROCESSING;
|
|
21053
|
-
const
|
|
21053
|
+
const frame3 = new WebsocketFrameSend();
|
|
21054
21054
|
if (code !== void 0 && reason === void 0) {
|
|
21055
|
-
|
|
21056
|
-
|
|
21055
|
+
frame3.frameData = Buffer.allocUnsafe(2);
|
|
21056
|
+
frame3.frameData.writeUInt16BE(code, 0);
|
|
21057
21057
|
} else if (code !== void 0 && reason !== void 0) {
|
|
21058
|
-
|
|
21059
|
-
|
|
21060
|
-
|
|
21058
|
+
frame3.frameData = Buffer.allocUnsafe(2 + reasonByteLength);
|
|
21059
|
+
frame3.frameData.writeUInt16BE(code, 0);
|
|
21060
|
+
frame3.frameData.write(reason, 2, "utf-8");
|
|
21061
21061
|
} else {
|
|
21062
|
-
|
|
21062
|
+
frame3.frameData = emptyBuffer;
|
|
21063
21063
|
}
|
|
21064
21064
|
const socket = ws[kResponse].socket;
|
|
21065
|
-
socket.write(
|
|
21065
|
+
socket.write(frame3.createFrame(opcodes.CLOSE));
|
|
21066
21066
|
ws[kSentClose] = sentCloseFrameState.SENT;
|
|
21067
21067
|
ws[kReadyState] = states.CLOSING;
|
|
21068
21068
|
} else {
|
|
@@ -21545,8 +21545,8 @@ var require_receiver = __commonJS({
|
|
|
21545
21545
|
return false;
|
|
21546
21546
|
} else if (opcode === opcodes.PING) {
|
|
21547
21547
|
if (!this.ws[kReceivedClose]) {
|
|
21548
|
-
const
|
|
21549
|
-
this.ws[kResponse].socket.write(
|
|
21548
|
+
const frame3 = new WebsocketFrameSend(body);
|
|
21549
|
+
this.ws[kResponse].socket.write(frame3.createFrame(opcodes.PONG));
|
|
21550
21550
|
if (channels.ping.hasSubscribers) {
|
|
21551
21551
|
channels.ping.publish({
|
|
21552
21552
|
payload: body
|
|
@@ -21596,14 +21596,14 @@ var require_sender = __commonJS({
|
|
|
21596
21596
|
}
|
|
21597
21597
|
add(item, cb, hint) {
|
|
21598
21598
|
if (hint !== sendHints.blob) {
|
|
21599
|
-
const
|
|
21599
|
+
const frame3 = createFrame(item, hint);
|
|
21600
21600
|
if (!this.#running) {
|
|
21601
|
-
this.#socket.write(
|
|
21601
|
+
this.#socket.write(frame3, cb);
|
|
21602
21602
|
} else {
|
|
21603
21603
|
const node2 = {
|
|
21604
21604
|
promise: null,
|
|
21605
21605
|
callback: cb,
|
|
21606
|
-
frame:
|
|
21606
|
+
frame: frame3
|
|
21607
21607
|
};
|
|
21608
21608
|
this.#queue.push(node2);
|
|
21609
21609
|
}
|
|
@@ -21685,7 +21685,7 @@ var require_websocket = __commonJS({
|
|
|
21685
21685
|
var { types } = __require("util");
|
|
21686
21686
|
var { ErrorEvent, CloseEvent } = require_events();
|
|
21687
21687
|
var { SendQueue } = require_sender();
|
|
21688
|
-
var
|
|
21688
|
+
var WebSocket2 = class _WebSocket extends EventTarget {
|
|
21689
21689
|
#events = {
|
|
21690
21690
|
open: null,
|
|
21691
21691
|
error: null,
|
|
@@ -21947,11 +21947,11 @@ var require_websocket = __commonJS({
|
|
|
21947
21947
|
fireEvent("open", this);
|
|
21948
21948
|
}
|
|
21949
21949
|
};
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
Object.defineProperties(
|
|
21950
|
+
WebSocket2.CONNECTING = WebSocket2.prototype.CONNECTING = states.CONNECTING;
|
|
21951
|
+
WebSocket2.OPEN = WebSocket2.prototype.OPEN = states.OPEN;
|
|
21952
|
+
WebSocket2.CLOSING = WebSocket2.prototype.CLOSING = states.CLOSING;
|
|
21953
|
+
WebSocket2.CLOSED = WebSocket2.prototype.CLOSED = states.CLOSED;
|
|
21954
|
+
Object.defineProperties(WebSocket2.prototype, {
|
|
21955
21955
|
CONNECTING: staticPropertyDescriptors,
|
|
21956
21956
|
OPEN: staticPropertyDescriptors,
|
|
21957
21957
|
CLOSING: staticPropertyDescriptors,
|
|
@@ -21975,7 +21975,7 @@ var require_websocket = __commonJS({
|
|
|
21975
21975
|
configurable: true
|
|
21976
21976
|
}
|
|
21977
21977
|
});
|
|
21978
|
-
Object.defineProperties(
|
|
21978
|
+
Object.defineProperties(WebSocket2, {
|
|
21979
21979
|
CONNECTING: staticPropertyDescriptors,
|
|
21980
21980
|
OPEN: staticPropertyDescriptors,
|
|
21981
21981
|
CLOSING: staticPropertyDescriptors,
|
|
@@ -22039,7 +22039,7 @@ var require_websocket = __commonJS({
|
|
|
22039
22039
|
closeWebSocketConnection(this, code);
|
|
22040
22040
|
}
|
|
22041
22041
|
module.exports = {
|
|
22042
|
-
WebSocket
|
|
22042
|
+
WebSocket: WebSocket2
|
|
22043
22043
|
};
|
|
22044
22044
|
}
|
|
22045
22045
|
});
|
|
@@ -22743,6 +22743,8 @@ var require_undici = __commonJS({
|
|
|
22743
22743
|
// ../../packages/sdk/dist/index.js
|
|
22744
22744
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
22745
22745
|
import { createHash, randomUUID } from "crypto";
|
|
22746
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
22747
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
22746
22748
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
22747
22749
|
import { closeSync, existsSync as existsSync2, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
22748
22750
|
import { homedir as homedir2 } from "os";
|
|
@@ -23619,6 +23621,13 @@ function draft2020(node) {
|
|
|
23619
23621
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
23620
23622
|
if (node && typeof node === "object") {
|
|
23621
23623
|
const o = node;
|
|
23624
|
+
if (Array.isArray(o.items)) {
|
|
23625
|
+
o.prefixItems = o.items;
|
|
23626
|
+
if ("additionalItems" in o) {
|
|
23627
|
+
o.items = o.additionalItems;
|
|
23628
|
+
delete o.additionalItems;
|
|
23629
|
+
} else delete o.items;
|
|
23630
|
+
}
|
|
23622
23631
|
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
23623
23632
|
if (typeof o[excl] === "boolean") {
|
|
23624
23633
|
if (o[excl] === true && typeof o[lim] === "number") {
|
|
@@ -23637,41 +23646,6 @@ function mcpInputSchema(s) {
|
|
|
23637
23646
|
delete schema.$schema;
|
|
23638
23647
|
return draft2020(schema);
|
|
23639
23648
|
}
|
|
23640
|
-
function contactSchemaFrom(fields) {
|
|
23641
|
-
const surface = external_exports.object({
|
|
23642
|
-
ask: fields.ask.describe(
|
|
23643
|
-
`What to tell the user, or what you need to find out from them. Plain prose \u2014 as long as it needs to be (up to 10k characters); Paigy splits it into topics and reads back a few sentences at a time, so do NOT compress a briefing into one line. May be spoken aloud on a call, so write natural speech and name things (not IDs). Contact at exactly two moments: BLOCKED on a decision only they can make, or DONE (one short report \u2014 what shipped, how you verified it, what you flagged). DONE IS SAID ONCE: "all set", "nothing open on my end", "that thread is complete" are the same report in new words, and each one reaches them separately (live 2026-08-12: three of them in three minutes). After the first, you are finished speaking; if they acknowledge it, stop rather than confirming the acknowledgement. Progress is never a contact: set_work_state carries it, and working narration stays in your own terminal \u2014 the user sees you're working without being interrupted by it.`
|
|
23644
|
-
),
|
|
23645
|
-
waiting: fields.waiting.describe(
|
|
23646
|
-
"What happens to your work while you wait. 'none': you're just informing them. 'soft': you'd like an answer but can keep working. 'hard': you are STOPPED until they answer \u2014 reaches them urgently and escalates to a real phone call if unanswered."
|
|
23647
|
-
),
|
|
23648
|
-
options: fields.options.describe(
|
|
23649
|
-
`The choices the user picks from, when you have them \u2014 ${OPTIONS_MIN} to ${OPTIONS_MAX}, drawn from your own sentence.`
|
|
23650
|
-
),
|
|
23651
|
-
channel: fields.channel.describe(
|
|
23652
|
-
"Relay how the user explicitly said to reach them ('call me' \u2192 'call', 'just message/text me' \u2192 'message'), or use 'call' when promoting the same quiet ask after it becomes a substantial blocker. Omit otherwise; Paigy picks."
|
|
23653
|
-
),
|
|
23654
|
-
parentId: fields.parentId.describe(
|
|
23655
|
-
"To continue an earlier conversation, pass the parentId a previous contact or reply returned. Omit to start a new one."
|
|
23656
|
-
),
|
|
23657
|
-
workId: fields.workId.describe(
|
|
23658
|
-
"The durable Work this contact advances. Pass the workId from check_replies or a prior reply when asking for a decision that blocks that work."
|
|
23659
|
-
),
|
|
23660
|
-
goalId: fields.goalId.describe(
|
|
23661
|
-
"The target Goal this contact advances. Use goalId for the Goal model; do not combine it with workId."
|
|
23662
|
-
),
|
|
23663
|
-
// THE WAIT, CONTINUED (owner, 2026-09-06: "await should have been folded into contact").
|
|
23664
|
-
// A contact that rang holds its first window itself; the host caps one tool call at
|
|
23665
|
-
// ~60 s, so keeping the line is another contact — with ONLY this field. Nothing is sent.
|
|
23666
|
-
wait: external_exports.string().uuid().optional().describe(
|
|
23667
|
-
"KEEP WAITING on a live call: the notificationId a previous contact returned. Send it ALONE \u2014 no ask, nothing new goes to the user; contact just holds the next ~45 s window and returns the outcome in `wait`."
|
|
23668
|
-
)
|
|
23669
|
-
});
|
|
23670
|
-
const out = mcpInputSchema(surface);
|
|
23671
|
-
delete out.required;
|
|
23672
|
-
out.anyOf = [{ required: ["ask"] }, { required: ["wait"] }];
|
|
23673
|
-
return out;
|
|
23674
|
-
}
|
|
23675
23649
|
function normalizeWaiting(req) {
|
|
23676
23650
|
if (!req.waiting) return req;
|
|
23677
23651
|
const { waiting, ...rest } = req;
|
|
@@ -23727,220 +23701,6 @@ function deriveAsk(req) {
|
|
|
23727
23701
|
...needs?.length ? { points: needs } : {}
|
|
23728
23702
|
};
|
|
23729
23703
|
}
|
|
23730
|
-
function utf8ToBytes(s) {
|
|
23731
|
-
const out = [];
|
|
23732
|
-
for (let i = 0; i < s.length; i++) {
|
|
23733
|
-
const c = s.charCodeAt(i);
|
|
23734
|
-
if (c < 128) out.push(c);
|
|
23735
|
-
else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
|
|
23736
|
-
else if (c >= 55296 && c <= 56319) {
|
|
23737
|
-
const c2 = s.charCodeAt(i + 1);
|
|
23738
|
-
if (c2 >= 56320 && c2 <= 57343) {
|
|
23739
|
-
i++;
|
|
23740
|
-
const cp = 65536 + ((c & 1023) << 10) + (c2 & 1023);
|
|
23741
|
-
out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
23742
|
-
} else out.push(239, 191, 189);
|
|
23743
|
-
} else if (c >= 56320 && c <= 57343) out.push(239, 191, 189);
|
|
23744
|
-
else out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
23745
|
-
}
|
|
23746
|
-
return Uint8Array.from(out);
|
|
23747
|
-
}
|
|
23748
|
-
function bytesToUtf8(b) {
|
|
23749
|
-
let out = "";
|
|
23750
|
-
for (let i = 0; i < b.length; ) {
|
|
23751
|
-
const c = b[i++];
|
|
23752
|
-
if (c < 128) out += String.fromCharCode(c);
|
|
23753
|
-
else if (c < 224) out += String.fromCharCode((c & 31) << 6 | b[i++] & 63);
|
|
23754
|
-
else if (c < 240) out += String.fromCharCode((c & 15) << 12 | (b[i++] & 63) << 6 | b[i++] & 63);
|
|
23755
|
-
else {
|
|
23756
|
-
const cp = (c & 7) << 18 | (b[i++] & 63) << 12 | (b[i++] & 63) << 6 | b[i++] & 63;
|
|
23757
|
-
const u = cp - 65536;
|
|
23758
|
-
out += String.fromCharCode(55296 + (u >> 10), 56320 + (u & 1023));
|
|
23759
|
-
}
|
|
23760
|
-
}
|
|
23761
|
-
return out;
|
|
23762
|
-
}
|
|
23763
|
-
function toB64(bytes) {
|
|
23764
|
-
let out = "";
|
|
23765
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
23766
|
-
const b0 = bytes[i];
|
|
23767
|
-
const has1 = i + 1 < bytes.length;
|
|
23768
|
-
const has2 = i + 2 < bytes.length;
|
|
23769
|
-
const b1 = has1 ? bytes[i + 1] : 0;
|
|
23770
|
-
const b2 = has2 ? bytes[i + 2] : 0;
|
|
23771
|
-
out += B64[b0 >> 2] + B64[(b0 & 3) << 4 | b1 >> 4];
|
|
23772
|
-
out += has1 ? B64[(b1 & 15) << 2 | b2 >> 6] : "=";
|
|
23773
|
-
out += has2 ? B64[b2 & 63] : "=";
|
|
23774
|
-
}
|
|
23775
|
-
return out;
|
|
23776
|
-
}
|
|
23777
|
-
function fromB64Lenient(s) {
|
|
23778
|
-
const clean = s.replace(/[^A-Za-z0-9+/]/g, "");
|
|
23779
|
-
const out = [];
|
|
23780
|
-
for (let i = 0; i < clean.length; i += 4) {
|
|
23781
|
-
const c0 = B64.indexOf(clean[i]);
|
|
23782
|
-
const c1 = B64.indexOf(clean[i + 1] ?? "A");
|
|
23783
|
-
const c2 = clean[i + 2] !== void 0 ? B64.indexOf(clean[i + 2]) : -1;
|
|
23784
|
-
const c3 = clean[i + 3] !== void 0 ? B64.indexOf(clean[i + 3]) : -1;
|
|
23785
|
-
out.push(c0 << 2 | c1 >> 4);
|
|
23786
|
-
if (c2 >= 0) out.push((c1 & 15) << 4 | c2 >> 2);
|
|
23787
|
-
if (c3 >= 0) out.push((c2 & 3) << 6 | c3);
|
|
23788
|
-
}
|
|
23789
|
-
return Uint8Array.from(out);
|
|
23790
|
-
}
|
|
23791
|
-
function fromB64(s) {
|
|
23792
|
-
const bytes = fromB64Lenient(s);
|
|
23793
|
-
if (toB64(bytes) !== s) throw new Error("non-canonical base64");
|
|
23794
|
-
return bytes;
|
|
23795
|
-
}
|
|
23796
|
-
function concat(parts) {
|
|
23797
|
-
let n = 0;
|
|
23798
|
-
for (const p of parts) n += p.length;
|
|
23799
|
-
const out = new Uint8Array(n);
|
|
23800
|
-
let o = 0;
|
|
23801
|
-
for (const p of parts) {
|
|
23802
|
-
out.set(p, o);
|
|
23803
|
-
o += p.length;
|
|
23804
|
-
}
|
|
23805
|
-
return out;
|
|
23806
|
-
}
|
|
23807
|
-
function u16be(n) {
|
|
23808
|
-
return Uint8Array.of(n >>> 8 & 255, n & 255);
|
|
23809
|
-
}
|
|
23810
|
-
function u32be(n) {
|
|
23811
|
-
return Uint8Array.of(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
23812
|
-
}
|
|
23813
|
-
function readU32be(b, o) {
|
|
23814
|
-
return (b[o] << 24 | b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]) >>> 0;
|
|
23815
|
-
}
|
|
23816
|
-
function lp(b) {
|
|
23817
|
-
return concat([u32be(b.length), b]);
|
|
23818
|
-
}
|
|
23819
|
-
function eqCt(a, b) {
|
|
23820
|
-
if (a.length !== b.length) return false;
|
|
23821
|
-
let d = 0;
|
|
23822
|
-
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
|
|
23823
|
-
return d === 0;
|
|
23824
|
-
}
|
|
23825
|
-
function ensureCsprng() {
|
|
23826
|
-
if (ok) return;
|
|
23827
|
-
let a;
|
|
23828
|
-
let b;
|
|
23829
|
-
try {
|
|
23830
|
-
a = import_tweetnacl.default.randomBytes(32);
|
|
23831
|
-
b = import_tweetnacl.default.randomBytes(32);
|
|
23832
|
-
} catch {
|
|
23833
|
-
throw new Error("paigy-crypto: no secure RNG available \u2014 refusing to generate keys (fail-closed)");
|
|
23834
|
-
}
|
|
23835
|
-
if (a.every((x) => x === 0) || eqCt(a, b)) {
|
|
23836
|
-
throw new Error("paigy-crypto: RNG self-test failed (constant/low-entropy output)");
|
|
23837
|
-
}
|
|
23838
|
-
ok = true;
|
|
23839
|
-
}
|
|
23840
|
-
function randomBytes(n) {
|
|
23841
|
-
ensureCsprng();
|
|
23842
|
-
return import_tweetnacl.default.randomBytes(n);
|
|
23843
|
-
}
|
|
23844
|
-
function keyId(publicKeyB64) {
|
|
23845
|
-
return toB64(import_tweetnacl2.default.hash(fromB64(publicKeyB64)).slice(0, 8));
|
|
23846
|
-
}
|
|
23847
|
-
function credentialBody(c) {
|
|
23848
|
-
return concat([
|
|
23849
|
-
lp(utf8ToBytes("paigy-device-credential-v1")),
|
|
23850
|
-
lp(utf8ToBytes(c.deviceId)),
|
|
23851
|
-
lp(utf8ToBytes(c.kind)),
|
|
23852
|
-
lp(fromB64(c.x25519Pub)),
|
|
23853
|
-
lp(fromB64(c.ed25519Pub))
|
|
23854
|
-
]);
|
|
23855
|
-
}
|
|
23856
|
-
function verifyDeviceCredential(cred, uikPublicB64) {
|
|
23857
|
-
const { sig, ...fields } = cred;
|
|
23858
|
-
try {
|
|
23859
|
-
return import_tweetnacl2.default.sign.detached.verify(credentialBody(fields), fromB64(sig), fromB64(uikPublicB64));
|
|
23860
|
-
} catch {
|
|
23861
|
-
return false;
|
|
23862
|
-
}
|
|
23863
|
-
}
|
|
23864
|
-
function padme(L) {
|
|
23865
|
-
if (L < 2) return L;
|
|
23866
|
-
const E = Math.floor(Math.log2(L));
|
|
23867
|
-
const S = Math.floor(Math.log2(E)) + 1;
|
|
23868
|
-
const lastBits = E - S;
|
|
23869
|
-
if (lastBits <= 0) return L;
|
|
23870
|
-
const mask = (1 << lastBits) - 1;
|
|
23871
|
-
return L + mask & ~mask;
|
|
23872
|
-
}
|
|
23873
|
-
function canonicalHeader(msgId, hdr) {
|
|
23874
|
-
const parts = [
|
|
23875
|
-
u16be(ENVELOPE_VERSION),
|
|
23876
|
-
lp(utf8ToBytes(ENVELOPE_ALG)),
|
|
23877
|
-
lp(msgId),
|
|
23878
|
-
lp(utf8ToBytes(hdr.field)),
|
|
23879
|
-
lp(utf8ToBytes(hdr.kind)),
|
|
23880
|
-
lp(utf8ToBytes(hdr.senderRole)),
|
|
23881
|
-
u32be(hdr.seq),
|
|
23882
|
-
u32be(hdr.recipientKeyIds.length),
|
|
23883
|
-
...hdr.recipientKeyIds.map((k) => lp(utf8ToBytes(k)))
|
|
23884
|
-
];
|
|
23885
|
-
return concat(parts);
|
|
23886
|
-
}
|
|
23887
|
-
function seal(input) {
|
|
23888
|
-
ensureCsprng();
|
|
23889
|
-
if (input.recipients.length === 0) throw new Error("seal: no recipients");
|
|
23890
|
-
const kind = input.kind ?? "";
|
|
23891
|
-
const recs = input.recipients.map((pub) => ({ pub, id: keyId(pub) })).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
23892
|
-
const hdr = {
|
|
23893
|
-
field: input.field,
|
|
23894
|
-
kind,
|
|
23895
|
-
senderRole: input.senderRole,
|
|
23896
|
-
recipientKeyIds: recs.map((r) => r.id),
|
|
23897
|
-
seq: input.seq
|
|
23898
|
-
};
|
|
23899
|
-
const msgIdBytes = input.msgId ? fromB64(input.msgId) : randomBytes(16);
|
|
23900
|
-
const cek = randomBytes(32);
|
|
23901
|
-
const canon = canonicalHeader(msgIdBytes, hdr);
|
|
23902
|
-
const plaintext = concat([u32be(canon.length), canon, u32be(input.body.length), input.body]);
|
|
23903
|
-
const padded = new Uint8Array(padme(plaintext.length));
|
|
23904
|
-
padded.set(plaintext);
|
|
23905
|
-
const nonce = randomBytes(24);
|
|
23906
|
-
const ct = import_tweetnacl3.default.secretbox(padded, nonce, cek);
|
|
23907
|
-
const recipients = recs.map((r) => {
|
|
23908
|
-
const eph = import_tweetnacl3.default.box.keyPair();
|
|
23909
|
-
const wnonce = randomBytes(24);
|
|
23910
|
-
const wrap = import_tweetnacl3.default.box(cek, wnonce, fromB64(r.pub), eph.secretKey);
|
|
23911
|
-
return { keyId: r.id, epk: toB64(eph.publicKey), wnonce: toB64(wnonce), wrap: toB64(wrap) };
|
|
23912
|
-
});
|
|
23913
|
-
return {
|
|
23914
|
-
v: ENVELOPE_VERSION,
|
|
23915
|
-
alg: ENVELOPE_ALG,
|
|
23916
|
-
msgId: toB64(msgIdBytes),
|
|
23917
|
-
hdr,
|
|
23918
|
-
recipients,
|
|
23919
|
-
nonce: toB64(nonce),
|
|
23920
|
-
ct: toB64(ct)
|
|
23921
|
-
};
|
|
23922
|
-
}
|
|
23923
|
-
function open(envelope, myKeyId, mySecretKeyB64) {
|
|
23924
|
-
if (envelope.v !== ENVELOPE_VERSION) throw new Error(`unsupported envelope version ${String(envelope.v)}`);
|
|
23925
|
-
if (envelope.alg !== ENVELOPE_ALG) throw new Error(`unsupported alg ${String(envelope.alg)}`);
|
|
23926
|
-
const rec = envelope.recipients.find((r) => r.keyId === myKeyId);
|
|
23927
|
-
if (!rec) throw new Error("not a recipient of this envelope");
|
|
23928
|
-
const cek = import_tweetnacl3.default.box.open(fromB64(rec.wrap), fromB64(rec.wnonce), fromB64(rec.epk), fromB64(mySecretKeyB64));
|
|
23929
|
-
if (!cek) throw new Error("CEK unwrap failed (wrong key or tampered)");
|
|
23930
|
-
const padded = import_tweetnacl3.default.secretbox.open(fromB64(envelope.ct), fromB64(envelope.nonce), cek);
|
|
23931
|
-
if (!padded) throw new Error("body authentication failed (tampered ciphertext)");
|
|
23932
|
-
if (padded.length < 8) throw new Error("malformed sealed plaintext");
|
|
23933
|
-
const canonLen = readU32be(padded, 0);
|
|
23934
|
-
if (canonLen < 0 || 8 + canonLen > padded.length) throw new Error("malformed sealed plaintext");
|
|
23935
|
-
const sealedCanon = padded.slice(4, 4 + canonLen);
|
|
23936
|
-
const bodyLen = readU32be(padded, 4 + canonLen);
|
|
23937
|
-
const bodyStart = 8 + canonLen;
|
|
23938
|
-
if (bodyStart + bodyLen > padded.length) throw new Error("malformed sealed plaintext");
|
|
23939
|
-
const body = padded.slice(bodyStart, bodyStart + bodyLen);
|
|
23940
|
-
const expected = canonicalHeader(fromB64(envelope.msgId), envelope.hdr);
|
|
23941
|
-
if (!eqCt(sealedCanon, expected)) throw new Error("header mismatch (tampered metadata)");
|
|
23942
|
-
return { header: envelope.hdr, body };
|
|
23943
|
-
}
|
|
23944
23704
|
function sessionSlot(sessionId2) {
|
|
23945
23705
|
const id = sessionId2 ?? sessionId();
|
|
23946
23706
|
return `session:${id.slice(0, 8)}`;
|
|
@@ -24044,126 +23804,33 @@ function readKeyFile() {
|
|
|
24044
23804
|
return null;
|
|
24045
23805
|
}
|
|
24046
23806
|
}
|
|
24047
|
-
async function fetchRoster(token) {
|
|
24048
|
-
const res = await reach(`${BACKEND_URL}/api/roster`, { headers: { authorization: `Bearer ${token}` } });
|
|
24049
|
-
if (!res.ok) return null;
|
|
24050
|
-
return await res.json().catch(() => null);
|
|
24051
|
-
}
|
|
24052
|
-
function verifiedRecipients(roster, pinnedUikPub) {
|
|
24053
|
-
return roster.devices.filter((cred) => verifyDeviceCredential(cred, pinnedUikPub)).map((cred) => cred.x25519Pub);
|
|
24054
|
-
}
|
|
24055
|
-
function sealFields(content, recipients) {
|
|
24056
|
-
if (recipients.length === 0) throw new Error("sealFields: no verified recipients");
|
|
24057
|
-
const seq = Math.floor(Date.now() / 1e3);
|
|
24058
|
-
const out = {};
|
|
24059
|
-
for (const field of ["context", "options", "visuals"]) {
|
|
24060
|
-
if (content[field] === void 0) continue;
|
|
24061
|
-
out[field] = seal({
|
|
24062
|
-
body: utf8ToBytes(JSON.stringify(content[field])),
|
|
24063
|
-
field,
|
|
24064
|
-
senderRole: "agent",
|
|
24065
|
-
seq,
|
|
24066
|
-
recipients
|
|
24067
|
-
});
|
|
24068
|
-
}
|
|
24069
|
-
return out;
|
|
24070
|
-
}
|
|
24071
|
-
function openSealedAnswer(sealed, keypair) {
|
|
24072
|
-
const myKeyId = keyId(keypair.x25519.publicKey);
|
|
24073
|
-
const { header, body } = open(sealed.envelope, myKeyId, keypair.x25519.secretKey);
|
|
24074
|
-
if (header.field !== "answer" || header.senderRole !== "user") {
|
|
24075
|
-
throw new Error("sealed answer has the wrong envelope field/role \u2014 refusing (possible replay/misattribution)");
|
|
24076
|
-
}
|
|
24077
|
-
const parsed = UserAnswerSchema.safeParse(JSON.parse(bytesToUtf8(body)));
|
|
24078
|
-
if (!parsed.success) throw new Error("sealed answer body is not a valid UserAnswer");
|
|
24079
|
-
const answer = parsed.data;
|
|
24080
|
-
const bodyIsIgnored = answer.kind === "ignored";
|
|
24081
|
-
if (sealed.ignored !== bodyIsIgnored) {
|
|
24082
|
-
throw new Error("sealed answer tamper: `ignored` hint disagrees with the decrypted body");
|
|
24083
|
-
}
|
|
24084
|
-
return answer;
|
|
24085
|
-
}
|
|
24086
23807
|
function ensureAuthed(res) {
|
|
24087
23808
|
if (res.status === 401) throw new UnpairedError();
|
|
24088
23809
|
return res;
|
|
24089
23810
|
}
|
|
24090
|
-
async function
|
|
24091
|
-
|
|
24092
|
-
if (!keyFile?.e2ee) return req;
|
|
24093
|
-
req = deriveAsk(req);
|
|
24094
|
-
if (!keyFile.uikPub) {
|
|
24095
|
-
throw new Error("E2EE pairing is missing its pinned identity key (key.json.uikPub) \u2014 re-pair; refusing to send plaintext.");
|
|
24096
|
-
}
|
|
24097
|
-
const roster = await (deps.fetchRoster ?? fetchRoster)(token);
|
|
24098
|
-
if (!roster) {
|
|
24099
|
-
throw new Error("E2EE pairing has no device roster to seal to \u2014 refusing to send plaintext. Approve a device on your phone, then retry.");
|
|
24100
|
-
}
|
|
24101
|
-
const recipients = verifiedRecipients(roster, keyFile.uikPub);
|
|
24102
|
-
if (recipients.length === 0) {
|
|
24103
|
-
throw new Error("No roster device verified against the pinned identity key \u2014 refusing to send plaintext (a server-injected device is dropped).");
|
|
24104
|
-
}
|
|
24105
|
-
const sealedContext = {
|
|
24106
|
-
...req.context,
|
|
24107
|
-
...req.repo !== void 0 ? { repo: req.repo } : {},
|
|
24108
|
-
...req.branch !== void 0 ? { branch: req.branch } : {}
|
|
24109
|
-
};
|
|
24110
|
-
const envelope = sealFields({ context: sealedContext, options: req.options, visuals: req.visuals }, recipients);
|
|
24111
|
-
const { context: _c, options: _o, visuals: _v, repo: _r, branch: _b, ...meta } = req;
|
|
24112
|
-
return { ...meta, envelope };
|
|
23811
|
+
async function fail(what, res) {
|
|
23812
|
+
throw new ApiError(what, res.status, await res.text());
|
|
24113
23813
|
}
|
|
24114
|
-
async function
|
|
23814
|
+
async function claimGoal(goalId, opts = {}) {
|
|
24115
23815
|
const token = authToken(opts.token) ?? "";
|
|
24116
|
-
|
|
24117
|
-
|
|
24118
|
-
const shaped = deriveAsk(req);
|
|
24119
|
-
if (!shaped.context || !shaped.select) throw new Error("Goal-scoped contact requires a shaped request or ask");
|
|
24120
|
-
if (shaped.channel === "call" || shaped.urgency === "call") {
|
|
24121
|
-
throw new Error("Goal-scoped call contact is not available yet; use a message contact");
|
|
24122
|
-
}
|
|
24123
|
-
if (shaped.urgency && shaped.urgency !== "inbox") throw new Error("Goal-scoped contact currently supports inbox delivery only");
|
|
24124
|
-
if (shaped.points?.length) throw new Error("Goal-scoped multi-part contact is not available yet; send one decision at a time");
|
|
24125
|
-
const content = [shaped.context.title, ...shaped.context.description].filter(Boolean).join("\n\n");
|
|
24126
|
-
const res2 = ensureAuthed(await reach(`${BACKEND_URL}/api/goals/${encodeURIComponent(req.goalId)}/contact`, {
|
|
24127
|
-
method: "POST",
|
|
24128
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" },
|
|
24129
|
-
body: JSON.stringify({
|
|
24130
|
-
operationId: randomUUID2(),
|
|
24131
|
-
threadId: shaped.parentId ?? null,
|
|
24132
|
-
content,
|
|
24133
|
-
select: shaped.select,
|
|
24134
|
-
options: shaped.options ?? [],
|
|
24135
|
-
blocking: shaped.blocking,
|
|
24136
|
-
channel: "message"
|
|
24137
|
-
})
|
|
24138
|
-
}));
|
|
24139
|
-
if (!res2.ok) throw new Error(`goal contact failed: ${res2.status} ${await res2.text()}`);
|
|
24140
|
-
return await res2.json();
|
|
24141
|
-
}
|
|
24142
|
-
const body = await sealForE2ee(req, token);
|
|
24143
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notify`, {
|
|
24144
|
-
method: "POST",
|
|
24145
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
24146
|
-
body: JSON.stringify(body)
|
|
24147
|
-
}));
|
|
24148
|
-
if (!res.ok) throw new Error(`notify failed: ${res.status} ${await res.text()}`);
|
|
23816
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify(goalId ? { goalId } : {}) }));
|
|
23817
|
+
if (!res.ok) await fail("claim_goal", res);
|
|
24149
23818
|
return await res.json();
|
|
24150
23819
|
}
|
|
24151
|
-
async function
|
|
24152
|
-
const token = authToken(opts.token);
|
|
24153
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/
|
|
24154
|
-
|
|
24155
|
-
|
|
24156
|
-
"x-paigy-await-ack": "1"
|
|
24157
|
-
}
|
|
23820
|
+
async function getGoal(goalId, opts = {}) {
|
|
23821
|
+
const token = authToken(opts.token) ?? "";
|
|
23822
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, {
|
|
23823
|
+
method: "GET",
|
|
23824
|
+
headers: { authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }
|
|
24158
23825
|
}));
|
|
24159
|
-
if (!res.ok)
|
|
23826
|
+
if (!res.ok) await fail("get_goal", res);
|
|
24160
23827
|
return await res.json();
|
|
24161
23828
|
}
|
|
24162
|
-
async function
|
|
24163
|
-
const
|
|
24164
|
-
|
|
24165
|
-
}));
|
|
24166
|
-
if (!res.ok)
|
|
23829
|
+
async function updateGoal(goalId, input, opts = {}) {
|
|
23830
|
+
const token = authToken(opts.token) ?? "";
|
|
23831
|
+
const operationId = input.operationId ?? randomUUID2();
|
|
23832
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
|
|
23833
|
+
if (!res.ok) await fail("update_goal", res);
|
|
24167
23834
|
return await res.json();
|
|
24168
23835
|
}
|
|
24169
23836
|
function overrideToken(secret) {
|
|
@@ -24208,6 +23875,15 @@ async function heartbeat(runtime, opts = {}) {
|
|
|
24208
23875
|
}));
|
|
24209
23876
|
if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
|
|
24210
23877
|
}
|
|
23878
|
+
async function registerDelivery(mode, opts = {}) {
|
|
23879
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
|
|
23880
|
+
method: "POST",
|
|
23881
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
23882
|
+
body: JSON.stringify({ mode })
|
|
23883
|
+
}));
|
|
23884
|
+
if (!res.ok) throw new Error(`register_delivery failed: ${res.status} ${await res.text()}`);
|
|
23885
|
+
return await res.json();
|
|
23886
|
+
}
|
|
24211
23887
|
async function listNotes(opts = {}) {
|
|
24212
23888
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notes`, {
|
|
24213
23889
|
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
@@ -24240,138 +23916,153 @@ async function acceptTriage(proposalId, body, opts = {}) {
|
|
|
24240
23916
|
if (!res.ok) throw new Error(`accept_triage failed: ${res.status} ${await res.text()}`);
|
|
24241
23917
|
return await res.json();
|
|
24242
23918
|
}
|
|
24243
|
-
function
|
|
24244
|
-
|
|
24245
|
-
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
23919
|
+
async function contact(input, opts = {}) {
|
|
23920
|
+
const parsed = ContactSchema.parse(input);
|
|
23921
|
+
opts.signal?.throwIfAborted();
|
|
23922
|
+
const send2 = opts.reach ?? reach;
|
|
23923
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
23924
|
+
let deliveryId;
|
|
23925
|
+
if ("deliveryId" in parsed) deliveryId = parsed.deliveryId;
|
|
23926
|
+
else {
|
|
23927
|
+
if (!opts.reach && readKeyFile()?.e2ee) throw new Error("Goal contact does not support E2EE yet; refusing to send plaintext.");
|
|
23928
|
+
const shaped = deriveAsk(NotifyRequestSchema.parse({ ask: parsed.ask, waiting: parsed.waiting, options: parsed.options }));
|
|
23929
|
+
const res = ensureAuthed(await send2(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
|
|
23930
|
+
method: "POST",
|
|
23931
|
+
headers,
|
|
23932
|
+
signal: opts.signal,
|
|
23933
|
+
body: JSON.stringify({ operationId: opts.operationId ?? randomUUID3(), threadId: parsed.threadId ?? null, content: parsed.ask, select: shaped.select, options: shaped.options ?? [], blocking: parsed.waiting === "hard", channel: parsed.channel === "call" ? "call" : "message" })
|
|
23934
|
+
}));
|
|
23935
|
+
if (!res.ok) await fail("goal contact", res);
|
|
23936
|
+
const receipt = await res.json();
|
|
23937
|
+
deliveryId = receipt.deliveryId ?? receipt.callId ?? receipt.notificationId ?? "";
|
|
23938
|
+
if (!deliveryId) throw new Error("Goal contact returned no Delivery identity");
|
|
23939
|
+
}
|
|
23940
|
+
const read = async (signal2) => {
|
|
23941
|
+
const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
23942
|
+
if (!res.ok) await fail("read_delivery", res);
|
|
23943
|
+
const delivery = await res.json();
|
|
23944
|
+
return delivery.kind === "notification" ? { ...delivery, message: `${delivery.message}
|
|
23945
|
+
Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answers. Do not poll this Notification.` } : delivery;
|
|
23946
|
+
};
|
|
23947
|
+
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
23948
|
+
if (opts.waits === false) return read(opts.signal);
|
|
23949
|
+
const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
|
|
23950
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
23951
|
+
let latest;
|
|
23952
|
+
try {
|
|
23953
|
+
while (true) {
|
|
23954
|
+
signal.throwIfAborted();
|
|
23955
|
+
latest = await read(signal);
|
|
23956
|
+
if (settled(latest)) return latest;
|
|
23957
|
+
await sleep2(5e3, void 0, { signal });
|
|
23958
|
+
}
|
|
23959
|
+
} catch (error) {
|
|
23960
|
+
opts.signal?.throwIfAborted();
|
|
23961
|
+
if (window.aborted && latest) return latest;
|
|
23962
|
+
throw error;
|
|
24250
23963
|
}
|
|
24251
|
-
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
24252
|
-
}
|
|
24253
|
-
function decodeReply(item, deps = {}) {
|
|
24254
|
-
const { delivery: _delivery, ...withoutDelivery } = item;
|
|
24255
|
-
const enriched = { ...withoutDelivery };
|
|
24256
|
-
if (!enriched.sealed) return enriched;
|
|
24257
|
-
const answer = decryptAnswer(enriched, deps);
|
|
24258
|
-
const { sealed: _sealed, ...plaintext } = enriched;
|
|
24259
|
-
return { ...plaintext, answer };
|
|
24260
23964
|
}
|
|
24261
|
-
function
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
}
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
throw new Error(
|
|
24268
|
-
"Paigy returned invalid reply-delivery metadata; the answer was not acknowledged and remains recoverable."
|
|
24269
|
-
);
|
|
24270
|
-
}
|
|
24271
|
-
return delivery;
|
|
23965
|
+
async function checkReplies(opts = {}) {
|
|
23966
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
|
|
23967
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
23968
|
+
}));
|
|
23969
|
+
if (!res.ok) await fail("check_replies", res);
|
|
23970
|
+
return await res.json();
|
|
24272
23971
|
}
|
|
24273
|
-
|
|
23972
|
+
function frame(line, topic, event, payload) {
|
|
24274
23973
|
try {
|
|
24275
|
-
|
|
23974
|
+
line.ws?.send(JSON.stringify({ topic, event, payload, ref: String(++line.ref) }));
|
|
24276
23975
|
} catch {
|
|
24277
|
-
return "";
|
|
24278
23976
|
}
|
|
24279
23977
|
}
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
|
|
23978
|
+
function stop(line) {
|
|
23979
|
+
line.open = false;
|
|
23980
|
+
clearInterval(line.heart);
|
|
23981
|
+
clearTimeout(line.retry);
|
|
23982
|
+
}
|
|
23983
|
+
function connect(line) {
|
|
23984
|
+
const base = line.url.replace(/^http/, "ws").replace(/\/+$/, "");
|
|
23985
|
+
const ws = line.ws = new import_undici.WebSocket(`${base}/realtime/v1/websocket?apikey=${encodeURIComponent(line.anonKey)}&vsn=1.0.0`);
|
|
23986
|
+
ws.addEventListener("open", () => {
|
|
23987
|
+
line.open = true;
|
|
23988
|
+
line.attempt = 0;
|
|
23989
|
+
for (const topic of line.topics.keys()) frame(line, `realtime:${topic}`, "phx_join", JOIN);
|
|
23990
|
+
line.heart = setInterval(() => frame(line, "phoenix", "heartbeat", {}), HEARTBEAT_MS);
|
|
23991
|
+
});
|
|
23992
|
+
ws.addEventListener("message", (e) => {
|
|
23993
|
+
let m;
|
|
24286
23994
|
try {
|
|
24287
|
-
|
|
24288
|
-
|
|
24289
|
-
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
}));
|
|
24295
|
-
if (res.ok) return;
|
|
24296
|
-
const detail2 = await responseDetail(res);
|
|
24297
|
-
if (res.status === 409) throw new ReplyLeaseExpiredError();
|
|
24298
|
-
const retryable = res.status === 408 || res.status === 425 || res.status === 429 || res.status >= 500;
|
|
24299
|
-
throw new ReplyAckError(
|
|
24300
|
-
`Paigy reply acknowledgement failed: ${res.status}${detail2 ? ` ${detail2}` : ""}`,
|
|
24301
|
-
retryable
|
|
24302
|
-
);
|
|
24303
|
-
} catch (error) {
|
|
24304
|
-
if (error instanceof UnpairedError || error instanceof ReplyLeaseExpiredError || error instanceof ReplyAckError && !error.retryable) {
|
|
24305
|
-
throw error;
|
|
24306
|
-
}
|
|
24307
|
-
lastError = error;
|
|
24308
|
-
if (attempt + 1 < attempts) {
|
|
24309
|
-
await doSleep(backoffMs * 2 ** attempt);
|
|
24310
|
-
}
|
|
23995
|
+
m = JSON.parse(String(e.data));
|
|
23996
|
+
} catch {
|
|
23997
|
+
return;
|
|
23998
|
+
}
|
|
23999
|
+
if (m.event !== "broadcast" || !m.topic?.startsWith("realtime:")) return;
|
|
24000
|
+
for (const l of line.topics.get(m.topic.slice("realtime:".length)) ?? []) {
|
|
24001
|
+
if (m.payload?.event === l.event) l.onMessage(m.payload.payload);
|
|
24311
24002
|
}
|
|
24312
|
-
}
|
|
24313
|
-
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
24314
|
-
throw new Error(
|
|
24315
|
-
`Could not acknowledge the Paigy reply after ${attempts} attempts (${detail}). The answer was not returned to the agent and remains recoverable after the lease expires.`
|
|
24316
|
-
);
|
|
24317
|
-
}
|
|
24318
|
-
async function receiveReply(item, token, opts = {}) {
|
|
24319
|
-
const decoded = decodeReply(item, opts);
|
|
24320
|
-
const delivery = deliveryOf(item);
|
|
24321
|
-
if (!delivery) {
|
|
24322
|
-
if (opts.requireDelivery) {
|
|
24323
|
-
throw new Error("Paigy returned a catch-up reply without a delivery lease; the answer was not exposed.");
|
|
24324
|
-
}
|
|
24325
|
-
return decoded;
|
|
24326
|
-
}
|
|
24327
|
-
await acknowledge(token, delivery.leaseId, {
|
|
24328
|
-
attempts: opts.ackAttempts,
|
|
24329
|
-
backoffMs: opts.ackBackoffMs,
|
|
24330
|
-
doSleep: opts.ackSleep
|
|
24331
24003
|
});
|
|
24332
|
-
|
|
24004
|
+
let gone = false;
|
|
24005
|
+
const again = () => {
|
|
24006
|
+
if (gone) return;
|
|
24007
|
+
gone = true;
|
|
24008
|
+
stop(line);
|
|
24009
|
+
if (!line.topics.size) return;
|
|
24010
|
+
line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
|
|
24011
|
+
};
|
|
24012
|
+
ws.addEventListener("close", again);
|
|
24013
|
+
ws.addEventListener("error", again);
|
|
24333
24014
|
}
|
|
24334
|
-
|
|
24335
|
-
const
|
|
24336
|
-
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
24340
|
-
|
|
24341
|
-
|
|
24342
|
-
|
|
24343
|
-
|
|
24344
|
-
|
|
24345
|
-
|
|
24015
|
+
function subscribeRealtime(args) {
|
|
24016
|
+
const key = `${args.url} ${args.anonKey}`;
|
|
24017
|
+
let line = lines.get(key);
|
|
24018
|
+
if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
|
|
24019
|
+
let heard = line.topics.get(args.topic);
|
|
24020
|
+
if (!heard) {
|
|
24021
|
+
line.topics.set(args.topic, heard = /* @__PURE__ */ new Set());
|
|
24022
|
+
if (line.open) frame(line, `realtime:${args.topic}`, "phx_join", JOIN);
|
|
24023
|
+
}
|
|
24024
|
+
const me = { event: args.event, onMessage: args.onMessage };
|
|
24025
|
+
heard.add(me);
|
|
24026
|
+
if (!line.ws) connect(line);
|
|
24346
24027
|
return {
|
|
24347
|
-
|
|
24348
|
-
|
|
24349
|
-
|
|
24028
|
+
close: () => {
|
|
24029
|
+
if (!heard.delete(me) || heard.size) return;
|
|
24030
|
+
line.topics.delete(args.topic);
|
|
24031
|
+
if (line.topics.size) {
|
|
24032
|
+
if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
|
|
24033
|
+
return;
|
|
24034
|
+
}
|
|
24035
|
+
lines.delete(key);
|
|
24036
|
+
stop(line);
|
|
24037
|
+
try {
|
|
24038
|
+
line.ws?.close();
|
|
24039
|
+
} catch {
|
|
24040
|
+
}
|
|
24041
|
+
}
|
|
24350
24042
|
};
|
|
24351
24043
|
}
|
|
24352
|
-
async function
|
|
24353
|
-
const
|
|
24354
|
-
const
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
24044
|
+
async function subscribeWake(onNudge, opts = {}) {
|
|
24045
|
+
const cfg = await registerDelivery("self_hosted", opts);
|
|
24046
|
+
const { url, anonKey } = cfg.realtime;
|
|
24047
|
+
const channel = wakeChannel(cfg.tokenId);
|
|
24048
|
+
const ch = subscribeRealtime({
|
|
24049
|
+
url,
|
|
24050
|
+
anonKey,
|
|
24051
|
+
topic: channel,
|
|
24052
|
+
event: WAKE_EVENT,
|
|
24053
|
+
onMessage: (payload) => onNudge(payload ?? {})
|
|
24054
|
+
});
|
|
24055
|
+
return {
|
|
24056
|
+
tokenId: cfg.tokenId,
|
|
24057
|
+
channel,
|
|
24058
|
+
close: async () => {
|
|
24059
|
+
ch.close();
|
|
24060
|
+
await registerDelivery("poll", opts).catch(() => {
|
|
24061
|
+
});
|
|
24364
24062
|
}
|
|
24365
|
-
|
|
24366
|
-
if (!res.ok) {
|
|
24367
|
-
throw new Error(`${operation} failed: ${res.status} ${await res.text()}`);
|
|
24368
|
-
}
|
|
24369
|
-
return await res.json();
|
|
24370
|
-
}
|
|
24371
|
-
function setTaskState(notificationId, state, opts = {}) {
|
|
24372
|
-
return patchState(`/api/notify/${encodeURIComponent(notificationId)}/state`, state, opts, "set_task_state");
|
|
24063
|
+
};
|
|
24373
24064
|
}
|
|
24374
|
-
var require2, __create2, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2, __copyProps2, __toESM2, require_nacl_fast, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, SESSION_ID, 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,
|
|
24065
|
+
var import_undici, require2, __create2, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2, __copyProps2, __toESM2, require_nacl_fast, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, SESSION_ID, 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, StartContactSchema, ContactSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, CreateGoalToolSchema, CREATE_GOAL_DESCRIPTION, UpdateGoalSchema, UpdateGoalToolSchema, ClaimGoalSchema, GetGoalSchema, GET_GOAL_DESCRIPTION, UPDATE_GOAL_DESCRIPTION, CLAIM_GOAL_DESCRIPTION, CHECK_REPLIES_DESCRIPTION, CheckRepliesSchema, GetThreadSchema, GET_THREAD_DESCRIPTION, SearchThreadsSchema, SEARCH_THREADS_DESCRIPTION, AGENT_TOOLS, AGENT_TOOL_NAMES, ContextSchema, ParticipantSchema, TransformSchema, OptionSchema, VisualSchema, NotifyLevelSchema, SelectShapeSchema, ReceiptEventSchema, AttentionSchema, NotifyRequestFields, NotifyRequestSchema, NotifyStatusSchema, AgentStateSchema, TurnSchema, UserAnswerSchema, IntentSchema, RideAlongSchema, AwaitItemSchema, CallbackTriggerSchema, NotifyResponseSchema, NotifyPlanUnitSchema, NotifyPlanSchema, UserResponseSchema, VoiceKeySchema, AgendaTurnSchema, CLAIM_STALE_MS, InboxItemSchema, SnoozeRequestSchema, APNS_TOKEN_RE, PushTokenSchema, MissedCallSchema, BrokerTuningSchema, UserSettingsSchema, HistoryItemSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, MoveRingSchema, MoveSchema, CreateRequestSchema, QueueQuestionSchema, QueueItemSchema, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, CreateNoteSchema, RecordDecisionSchema, AssignNoteSchema, TriageVerdictSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, StatusSchema, EnvelopeRecipientSchema, EnvelopeHeaderSchema, EnvelopeSchema, SealedAnswerSchema, DeviceCredentialSchema, DeviceRosterSchema, WakeNudgeSchema, PairingStatusSchema, PairingRevealSchema, DeviceCodeRequestSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenRequestSchema, DeviceTokenSchema, DeviceCommitRequestSchema, DevicePeerCommitSchema, SupportRequestSchema, NotificationFeedbackKindSchema, NotificationFeedbackSchema, FeedbackResolutionSchema, FeedbackOutcomeSchema, import_tweetnacl, import_tweetnacl2, import_tweetnacl3, import_tweetnacl4, import_tweetnacl5, AGENT_NAME, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, ApiError, AWAIT_WINDOW_MS, tokenOverride, authToken, HEARTBEAT_MS, RETRY_MS, JOIN, lines;
|
|
24375
24066
|
var init_dist = __esm({
|
|
24376
24067
|
"../../packages/sdk/dist/index.js"() {
|
|
24377
24068
|
"use strict";
|
|
@@ -24380,6 +24071,8 @@ var init_dist = __esm({
|
|
|
24380
24071
|
init_v3();
|
|
24381
24072
|
init_v3();
|
|
24382
24073
|
init_v3();
|
|
24074
|
+
init_zod();
|
|
24075
|
+
import_undici = __toESM(require_undici(), 1);
|
|
24383
24076
|
require2 = __sdkCreateRequire(import.meta.url);
|
|
24384
24077
|
__create2 = Object.create;
|
|
24385
24078
|
__defProp2 = Object.defineProperty;
|
|
@@ -24710,17 +24403,17 @@ var init_dist = __esm({
|
|
|
24710
24403
|
}
|
|
24711
24404
|
var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]);
|
|
24712
24405
|
function crypto_stream_salsa20_xor(c, cpos, m, mpos, b, n, k) {
|
|
24713
|
-
var
|
|
24406
|
+
var z4 = new Uint8Array(16), x = new Uint8Array(64);
|
|
24714
24407
|
var u, i;
|
|
24715
|
-
for (i = 0; i < 16; i++)
|
|
24716
|
-
for (i = 0; i < 8; i++)
|
|
24408
|
+
for (i = 0; i < 16; i++) z4[i] = 0;
|
|
24409
|
+
for (i = 0; i < 8; i++) z4[i] = n[i];
|
|
24717
24410
|
while (b >= 64) {
|
|
24718
|
-
crypto_core_salsa20(x,
|
|
24411
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
24719
24412
|
for (i = 0; i < 64; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
|
24720
24413
|
u = 1;
|
|
24721
24414
|
for (i = 8; i < 16; i++) {
|
|
24722
|
-
u = u + (
|
|
24723
|
-
|
|
24415
|
+
u = u + (z4[i] & 255) | 0;
|
|
24416
|
+
z4[i] = u & 255;
|
|
24724
24417
|
u >>>= 8;
|
|
24725
24418
|
}
|
|
24726
24419
|
b -= 64;
|
|
@@ -24728,30 +24421,30 @@ var init_dist = __esm({
|
|
|
24728
24421
|
mpos += 64;
|
|
24729
24422
|
}
|
|
24730
24423
|
if (b > 0) {
|
|
24731
|
-
crypto_core_salsa20(x,
|
|
24424
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
24732
24425
|
for (i = 0; i < b; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
|
24733
24426
|
}
|
|
24734
24427
|
return 0;
|
|
24735
24428
|
}
|
|
24736
24429
|
function crypto_stream_salsa20(c, cpos, b, n, k) {
|
|
24737
|
-
var
|
|
24430
|
+
var z4 = new Uint8Array(16), x = new Uint8Array(64);
|
|
24738
24431
|
var u, i;
|
|
24739
|
-
for (i = 0; i < 16; i++)
|
|
24740
|
-
for (i = 0; i < 8; i++)
|
|
24432
|
+
for (i = 0; i < 16; i++) z4[i] = 0;
|
|
24433
|
+
for (i = 0; i < 8; i++) z4[i] = n[i];
|
|
24741
24434
|
while (b >= 64) {
|
|
24742
|
-
crypto_core_salsa20(x,
|
|
24435
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
24743
24436
|
for (i = 0; i < 64; i++) c[cpos + i] = x[i];
|
|
24744
24437
|
u = 1;
|
|
24745
24438
|
for (i = 8; i < 16; i++) {
|
|
24746
|
-
u = u + (
|
|
24747
|
-
|
|
24439
|
+
u = u + (z4[i] & 255) | 0;
|
|
24440
|
+
z4[i] = u & 255;
|
|
24748
24441
|
u >>>= 8;
|
|
24749
24442
|
}
|
|
24750
24443
|
b -= 64;
|
|
24751
24444
|
cpos += 64;
|
|
24752
24445
|
}
|
|
24753
24446
|
if (b > 0) {
|
|
24754
|
-
crypto_core_salsa20(x,
|
|
24447
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
24755
24448
|
for (i = 0; i < b; i++) c[cpos + i] = x[i];
|
|
24756
24449
|
}
|
|
24757
24450
|
return 0;
|
|
@@ -25631,12 +25324,12 @@ var init_dist = __esm({
|
|
|
25631
25324
|
for (a = 0; a < 16; a++) o[a] = c[a];
|
|
25632
25325
|
}
|
|
25633
25326
|
function crypto_scalarmult(q, n, p) {
|
|
25634
|
-
var
|
|
25327
|
+
var z4 = new Uint8Array(32);
|
|
25635
25328
|
var x = new Float64Array(80), r, i;
|
|
25636
25329
|
var a = gf(), b = gf(), c = gf(), d = gf(), e = gf(), f = gf();
|
|
25637
|
-
for (i = 0; i < 31; i++)
|
|
25638
|
-
|
|
25639
|
-
|
|
25330
|
+
for (i = 0; i < 31; i++) z4[i] = n[i];
|
|
25331
|
+
z4[31] = n[31] & 127 | 64;
|
|
25332
|
+
z4[0] &= 248;
|
|
25640
25333
|
unpack25519(x, p);
|
|
25641
25334
|
for (i = 0; i < 16; i++) {
|
|
25642
25335
|
b[i] = x[i];
|
|
@@ -25644,7 +25337,7 @@ var init_dist = __esm({
|
|
|
25644
25337
|
}
|
|
25645
25338
|
a[0] = d[0] = 1;
|
|
25646
25339
|
for (i = 254; i >= 0; --i) {
|
|
25647
|
-
r =
|
|
25340
|
+
r = z4[i >>> 3] >>> (i & 7) & 1;
|
|
25648
25341
|
sel25519(a, b, r);
|
|
25649
25342
|
sel25519(c, d, r);
|
|
25650
25343
|
A(e, a, c);
|
|
@@ -27014,21 +26707,54 @@ var init_dist = __esm({
|
|
|
27014
26707
|
};
|
|
27015
26708
|
OPTIONS_MIN = 2;
|
|
27016
26709
|
OPTIONS_MAX = 6;
|
|
27017
|
-
|
|
27018
|
-
|
|
27019
|
-
|
|
27020
|
-
|
|
27021
|
-
|
|
27022
|
-
|
|
27023
|
-
|
|
27024
|
-
|
|
27025
|
-
|
|
27026
|
-
|
|
26710
|
+
StartContactSchema = external_exports.object({
|
|
26711
|
+
goalIds: external_exports.tuple([external_exports.string().uuid()]),
|
|
26712
|
+
// THE ASK IS THE QUESTION, not a bulletin with a question at the end (2026-09-16).
|
|
26713
|
+
//
|
|
26714
|
+
// A DecisionNeed is settled only by an answer in the shape THIS ask declares. Bundle news,
|
|
26715
|
+
// findings and a decision into one, and the person answers whichever part engaged them —
|
|
26716
|
+
// which settles nothing, leaves the need open, and gets them asked again. On call 619c5a92
|
|
26717
|
+
// one ask carried a briefing, a side question ("the ring should show the agent's name now —
|
|
26718
|
+
// what did you actually see?") and a one-of-four about how far an erase reaches. The owner
|
|
26719
|
+
// answered the side question. The need stayed open; the card asked again. Of the 45 call
|
|
26720
|
+
// asks carrying a decision, 27 are over 600 characters.
|
|
26721
|
+
//
|
|
26722
|
+
// Sending them separately costs nothing, because a CALL contact joins the call already
|
|
26723
|
+
// happening (`goal/store.ts`, JOIN BEFORE MINTING) — several contacts on one Goal arrive as
|
|
26724
|
+
// one call, planned as its turns, and each keeps its own settleable need.
|
|
26725
|
+
ask: external_exports.string().trim().min(1).max(1e4).describe(
|
|
26726
|
+
"The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
|
|
26727
|
+
),
|
|
26728
|
+
waiting: external_exports.enum(["none", "hard"]).default("none"),
|
|
26729
|
+
channel: external_exports.enum(["notification", "call"]).default("notification"),
|
|
26730
|
+
options: external_exports.array(external_exports.object({ label: external_exports.string().trim().min(1).max(1e3), image: external_exports.string().url().optional(), html: external_exports.string().max(16384).optional() }).strict()).min(2).max(6).optional(),
|
|
26731
|
+
threadId: external_exports.string().uuid().optional().describe("Continue an existing Thread: the threadId a prior Delivery returned. Omit to start a new Thread.")
|
|
26732
|
+
}).strict();
|
|
26733
|
+
ContactSchema = external_exports.union([StartContactSchema, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict()]);
|
|
26734
|
+
CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
26735
|
+
CONTACT_DESCRIPTION = "Contact the user about exactly one existing Goal: pass goalIds:[goalId], ask, channel:'notification'|'call', and waiting:'none'|'hard'. Options supply choices. Notification returns immediately; collect durable answers with claim_goal/get_goal. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Continuation sends nothing and rereads the same durable evidence, including previously read answers. Entries retain authorship and provenance; accepted decisions are separate from quoted speech. Call state open does not mean ringing. Unsupported: soft waiting, multiple Goals/questions, outcome admission, and re-presentation of an existing request. Create a Goal explicitly first; never resend a pending ask to continue waiting.";
|
|
27027
26736
|
CreateGoalSchema = external_exports.object({
|
|
27028
26737
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
27029
26738
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
27030
|
-
idempotencyKey: external_exports.string().trim().min(1).max(200)
|
|
26739
|
+
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
26740
|
+
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
26741
|
+
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
26742
|
+
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
26743
|
+
* the writer refuses a thread belonging to another account. */
|
|
26744
|
+
contextThreadId: external_exports.string().uuid().optional(),
|
|
26745
|
+
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
26746
|
+
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
26747
|
+
* worked"). It could not have been: this door took no parent, so the only route was
|
|
26748
|
+
* create -> claim -> `update_goal`, three calls with a lease in the middle, and every agent
|
|
26749
|
+
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
26750
|
+
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
26751
|
+
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
26752
|
+
parentGoalId: external_exports.string().uuid().optional()
|
|
27031
26753
|
});
|
|
26754
|
+
CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
26755
|
+
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
26756
|
+
}).strict();
|
|
26757
|
+
CREATE_GOAL_DESCRIPTION = "Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.";
|
|
27032
26758
|
UpdateGoalSchema = external_exports.object({
|
|
27033
26759
|
revision: external_exports.number().int().positive(),
|
|
27034
26760
|
changes: external_exports.object({
|
|
@@ -27044,17 +26770,33 @@ var init_dist = __esm({
|
|
|
27044
26770
|
reason: external_exports.string().trim().min(1).max(2e3),
|
|
27045
26771
|
operationId: external_exports.string().uuid().optional()
|
|
27046
26772
|
}).strict();
|
|
27047
|
-
UpdateGoalToolSchema = UpdateGoalSchema.extend({ goalId: external_exports.string().uuid() }).strict();
|
|
26773
|
+
UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: external_exports.string().uuid() }).strict();
|
|
27048
26774
|
ClaimGoalSchema = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
27049
|
-
|
|
27050
|
-
|
|
27051
|
-
|
|
27052
|
-
|
|
27053
|
-
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27057
|
-
|
|
26775
|
+
GetGoalSchema = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
26776
|
+
GET_GOAL_DESCRIPTION = "Read the current authorized Goal brief: state, owner, blockers, open decisions, progress, and the next operation. Foreign or sibling-owned Goals are not disclosed.";
|
|
26777
|
+
UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, and review acknowledgement are explicit; stale revisions are rejected. Returns the new revision and a prose summary.";
|
|
26778
|
+
CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns a Goal-scoped brief, current revision, blockers, and the next valid operation. Claiming creates or renews the execution lease.";
|
|
26779
|
+
CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 each with its durable Entries, accepted decisions and open decision needs, in the same shape a contact read returns. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it with contact({deliveryId}). Your runnable and review-pending Goals come from claim_goal, not from here.";
|
|
26780
|
+
CheckRepliesSchema = external_exports.object({}).strict();
|
|
26781
|
+
GetThreadSchema = external_exports.object({
|
|
26782
|
+
parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
26783
|
+
}).strict();
|
|
26784
|
+
GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
|
|
26785
|
+
SearchThreadsSchema = external_exports.object({
|
|
26786
|
+
q: external_exports.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
|
|
26787
|
+
}).strict();
|
|
26788
|
+
SEARCH_THREADS_DESCRIPTION = `Search your PAST conversations before asking \u2014 "have we discussed this before?". Full-text over your own threads (the asks you sent + the user's answers); returns ranked threads with highlighted snippets, NOT rows: { hits: [{ parentId, at, agentLabel, matches: [{ notificationId, role, snippet }] }] }. The loop this exists for: search first \u2192 get_thread the best hit to rehydrate it \u2192 THEN continue or contact, so you answer with receipts ("last week you said ship it") instead of re-asking. Read-only, safe to call anytime; scoped to your own account's threads.`;
|
|
26789
|
+
AGENT_TOOLS = [
|
|
26790
|
+
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
26791
|
+
{ name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
|
|
26792
|
+
{ name: "get_thread", description: GET_THREAD_DESCRIPTION, inputSchema: mcpInputSchema(GetThreadSchema) },
|
|
26793
|
+
{ name: "search_threads", description: SEARCH_THREADS_DESCRIPTION, inputSchema: mcpInputSchema(SearchThreadsSchema) },
|
|
26794
|
+
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
26795
|
+
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
26796
|
+
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
26797
|
+
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
26798
|
+
];
|
|
26799
|
+
AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
27058
26800
|
ContextSchema = external_exports.object({
|
|
27059
26801
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
27060
26802
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -27285,16 +27027,6 @@ var init_dist = __esm({
|
|
|
27285
27027
|
});
|
|
27286
27028
|
NotifyStatusSchema = external_exports.enum(["pending", "answered", "ignored"]);
|
|
27287
27029
|
AgentStateSchema = external_exports.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
27288
|
-
SetTaskStateSchema = external_exports.object({
|
|
27289
|
-
state: external_exports.enum(["in_progress", "completed", "needs_input"])
|
|
27290
|
-
});
|
|
27291
|
-
SetWorkStateSchema = external_exports.object({
|
|
27292
|
-
workId: external_exports.string().uuid().optional(),
|
|
27293
|
-
notificationId: external_exports.string().optional(),
|
|
27294
|
-
state: SetTaskStateSchema.shape.state
|
|
27295
|
-
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
27296
|
-
message: "exactly one of workId or notificationId is required"
|
|
27297
|
-
});
|
|
27298
27030
|
TurnSchema = external_exports.object({
|
|
27299
27031
|
prompt: external_exports.string(),
|
|
27300
27032
|
reply: external_exports.string()
|
|
@@ -27424,108 +27156,6 @@ var init_dist = __esm({
|
|
|
27424
27156
|
})
|
|
27425
27157
|
]);
|
|
27426
27158
|
CallbackTriggerSchema = external_exports.enum(["on_done", "on_blocked", "scheduled"]);
|
|
27427
|
-
ScheduleCallbackSchema = external_exports.object({
|
|
27428
|
-
parentId: external_exports.string().describe("The thread to call back on (from a prior contact / reply / request)."),
|
|
27429
|
-
goalId: external_exports.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
|
|
27430
|
-
trigger: CallbackTriggerSchema,
|
|
27431
|
-
dueInSeconds: external_exports.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
|
|
27432
|
-
note: external_exports.string().optional().describe("What to tell the user when you follow up.")
|
|
27433
|
-
});
|
|
27434
|
-
PendingRepliesSchema = external_exports.object({
|
|
27435
|
-
replies: external_exports.array(
|
|
27436
|
-
external_exports.object({
|
|
27437
|
-
parentId: external_exports.string(),
|
|
27438
|
-
notificationId: external_exports.string(),
|
|
27439
|
-
workId: external_exports.string().uuid().optional(),
|
|
27440
|
-
decisionId: external_exports.string().uuid().optional(),
|
|
27441
|
-
answer: UserAnswerSchema,
|
|
27442
|
-
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the
|
|
27443
|
-
* pairing is E2EE — the agent opens it and re-derives the real answer. Absent =
|
|
27444
|
-
* plaintext answer (in `answer`). See AwaitItemSchema's reply variant. */
|
|
27445
|
-
sealed: external_exports.lazy(() => SealedAnswerSchema).optional(),
|
|
27446
|
-
/** The call record rendered for THIS agent: the raw words the shaped answer was
|
|
27447
|
-
* mapped from, filtered to its own claims. See AwaitItemSchema's reply variant. */
|
|
27448
|
-
transcript: external_exports.string().optional(),
|
|
27449
|
-
/** Coverage report (#396): which declared `points` this answer addressed. */
|
|
27450
|
-
covered: external_exports.array(external_exports.string()).optional()
|
|
27451
|
-
})
|
|
27452
|
-
),
|
|
27453
|
-
pending: external_exports.array(
|
|
27454
|
-
external_exports.object({ parentId: external_exports.string(), notificationId: external_exports.string(), createdAt: external_exports.string() })
|
|
27455
|
-
),
|
|
27456
|
-
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
27457
|
-
* sees for this session's identity. From inside a session there was no way to find out —
|
|
27458
|
-
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
27459
|
-
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
27460
|
-
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
27461
|
-
you: external_exports.object({ name: external_exports.string(), device: external_exports.string().nullable(), tokenId: external_exports.string() }).optional(),
|
|
27462
|
-
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
27463
|
-
* contact on the same parentId. Keeps reappearing until you call
|
|
27464
|
-
* set_work_state on its workId (or notificationId once while bootstrapping). */
|
|
27465
|
-
requests: external_exports.array(
|
|
27466
|
-
external_exports.object({
|
|
27467
|
-
parentId: external_exports.string(),
|
|
27468
|
-
notificationId: external_exports.string(),
|
|
27469
|
-
workId: external_exports.string().uuid().optional(),
|
|
27470
|
-
text: external_exports.string(),
|
|
27471
|
-
createdAt: external_exports.string(),
|
|
27472
|
-
/** The user seeded this request with a past conversation — call get_thread on it
|
|
27473
|
-
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
27474
|
-
contextParentId: external_exports.string().optional(),
|
|
27475
|
-
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
27476
|
-
* the account — the name here — which has not been seen since it landed, so nobody
|
|
27477
|
-
* came for it. Handed to you because you are the session that is here. Take it like
|
|
27478
|
-
* any request (set_work_state claims it, reply with contact on its parentId), and say
|
|
27479
|
-
* whose it was, because the user chose that agent on purpose. */
|
|
27480
|
-
stranded: external_exports.string().optional()
|
|
27481
|
-
})
|
|
27482
|
-
),
|
|
27483
|
-
/** Durable outcomes currently owned by this agent. This is the Work-native queue; flat
|
|
27484
|
-
* notification lists remain during migration so old clients keep their existing view. */
|
|
27485
|
-
work: external_exports.array(external_exports.object({
|
|
27486
|
-
workId: external_exports.string().uuid(),
|
|
27487
|
-
parentId: external_exports.string().optional(),
|
|
27488
|
-
objective: external_exports.string().nullable(),
|
|
27489
|
-
state: external_exports.enum(["active", "blocked", "waiting_external"]),
|
|
27490
|
-
blockedOn: external_exports.array(external_exports.string().uuid()),
|
|
27491
|
-
updatedAt: external_exports.string()
|
|
27492
|
-
})).optional(),
|
|
27493
|
-
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
27494
|
-
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
27495
|
-
* fulfill one by calling contact on its parentId. */
|
|
27496
|
-
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
27497
|
-
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
27498
|
-
also: external_exports.array(RideAlongSchema).optional(),
|
|
27499
|
-
owedCallbacks: external_exports.array(
|
|
27500
|
-
external_exports.object({ parentId: external_exports.string(), trigger: CallbackTriggerSchema, note: external_exports.string() })
|
|
27501
|
-
),
|
|
27502
|
-
/** Work (either direction) you reported in_progress a while ago and never reported
|
|
27503
|
-
* completed — likely left half-done by this session or a prior one that crashed or
|
|
27504
|
-
* went idle. Report a real state (set_work_state) or continue the work. */
|
|
27505
|
-
stalled: external_exports.array(
|
|
27506
|
-
external_exports.object({ parentId: external_exports.string(), notificationId: external_exports.string(), title: external_exports.string().nullable(), startedAt: external_exports.string() })
|
|
27507
|
-
),
|
|
27508
|
-
/** The queue rail (#614, pending/design.md): the same replies + requests, grouped by
|
|
27509
|
-
* thread and ordered oldest-thread-first, so you work ONE thread at a time — fold all of
|
|
27510
|
-
* a thread's `items` into a single turn rather than interleaving threads. `busy` = the
|
|
27511
|
-
* thread already has a turn in progress (younger than the stall cutoff); let it finish and
|
|
27512
|
-
* ride the next turn. `items` are that thread's replies/requests in arrival order; the
|
|
27513
|
-
* full payload for each is in the flat `replies`/`requests` arrays (matched by
|
|
27514
|
-
* notificationId). Derived, never stored — a crashed agent recomputes it exactly. */
|
|
27515
|
-
threads: external_exports.array(
|
|
27516
|
-
external_exports.object({
|
|
27517
|
-
parentId: external_exports.string(),
|
|
27518
|
-
busy: external_exports.boolean(),
|
|
27519
|
-
items: external_exports.array(
|
|
27520
|
-
external_exports.object({
|
|
27521
|
-
kind: external_exports.enum(["reply", "request"]),
|
|
27522
|
-
notificationId: external_exports.string(),
|
|
27523
|
-
at: external_exports.string()
|
|
27524
|
-
})
|
|
27525
|
-
)
|
|
27526
|
-
})
|
|
27527
|
-
)
|
|
27528
|
-
});
|
|
27529
27159
|
NotifyResponseSchema = external_exports.object({
|
|
27530
27160
|
notificationId: external_exports.string(),
|
|
27531
27161
|
workId: external_exports.string().uuid().optional(),
|
|
@@ -27588,6 +27218,13 @@ var init_dist = __esm({
|
|
|
27588
27218
|
});
|
|
27589
27219
|
VoiceKeySchema = external_exports.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
27590
27220
|
AgendaTurnSchema = external_exports.object({
|
|
27221
|
+
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
27222
|
+
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
27223
|
+
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
27224
|
+
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
27225
|
+
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
27226
|
+
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
27227
|
+
id: external_exports.string().optional(),
|
|
27591
27228
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
27592
27229
|
* the planner declares duplicates instead of asking them twice. */
|
|
27593
27230
|
coveredIds: external_exports.array(external_exports.string()).optional(),
|
|
@@ -27746,7 +27383,21 @@ var init_dist = __esm({
|
|
|
27746
27383
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
27747
27384
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
27748
27385
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
27749
|
-
agentX25519: external_exports.string().optional()
|
|
27386
|
+
agentX25519: external_exports.string().optional(),
|
|
27387
|
+
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
27388
|
+
* that Delivery's kind, the request Entry, the Goals it answers for, the exact DecisionNeed (none
|
|
27389
|
+
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
27390
|
+
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
27391
|
+
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
27392
|
+
communication: external_exports.object({
|
|
27393
|
+
deliveryId: external_exports.string(),
|
|
27394
|
+
kind: external_exports.enum(["notification", "call"]),
|
|
27395
|
+
entryId: external_exports.string(),
|
|
27396
|
+
goalIds: external_exports.array(external_exports.string()),
|
|
27397
|
+
decisionNeedId: external_exports.string().optional(),
|
|
27398
|
+
sealed: external_exports.boolean(),
|
|
27399
|
+
goalState: external_exports.string().optional()
|
|
27400
|
+
}).optional()
|
|
27750
27401
|
});
|
|
27751
27402
|
SnoozeRequestSchema = external_exports.object({
|
|
27752
27403
|
requestId: external_exports.string(),
|
|
@@ -27982,26 +27633,54 @@ var init_dist = __esm({
|
|
|
27982
27633
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
27983
27634
|
contextParentId: external_exports.string().optional()
|
|
27984
27635
|
});
|
|
27985
|
-
|
|
27986
|
-
/**
|
|
27987
|
-
|
|
27988
|
-
/**
|
|
27989
|
-
|
|
27990
|
-
/**
|
|
27991
|
-
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
/**
|
|
27995
|
-
|
|
27996
|
-
|
|
27997
|
-
|
|
27998
|
-
|
|
27999
|
-
|
|
28000
|
-
|
|
28001
|
-
|
|
28002
|
-
|
|
28003
|
-
|
|
28004
|
-
|
|
27636
|
+
QueueQuestionSchema = external_exports.object({
|
|
27637
|
+
/** The decision need's id — what an answer is accepted against. */
|
|
27638
|
+
id: external_exports.string(),
|
|
27639
|
+
/** The words that were asked, from the request Entry that asked them. */
|
|
27640
|
+
question: external_exports.string(),
|
|
27641
|
+
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
27642
|
+
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
27643
|
+
* can answer. */
|
|
27644
|
+
deliveryId: external_exports.string().nullable().default(null),
|
|
27645
|
+
/** The Entry the ruling is about. */
|
|
27646
|
+
aboutId: external_exports.string().nullable().default(null),
|
|
27647
|
+
/** Empty for a free-text question. */
|
|
27648
|
+
options: external_exports.array(OptionSchema).default([]),
|
|
27649
|
+
select: external_exports.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
27650
|
+
askedAt: external_exports.string(),
|
|
27651
|
+
/** Null while the question is open — which is how the page tells the two apart. */
|
|
27652
|
+
answeredAt: external_exports.string().nullable().default(null),
|
|
27653
|
+
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
27654
|
+
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
27655
|
+
* for a settled question whose reply carried nothing readable. */
|
|
27656
|
+
answer: external_exports.string().nullable().default(null)
|
|
27657
|
+
});
|
|
27658
|
+
QueueItemSchema = external_exports.object({
|
|
27659
|
+
id: external_exports.string(),
|
|
27660
|
+
/** One-line headline — the first sentence of the outcome. */
|
|
27661
|
+
title: external_exports.string(),
|
|
27662
|
+
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
27663
|
+
intent: external_exports.string(),
|
|
27664
|
+
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
27665
|
+
state: external_exports.string(),
|
|
27666
|
+
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
27667
|
+
assignee: external_exports.string().nullable().default(null),
|
|
27668
|
+
/** What the agent last said it was doing; null if it has said nothing. */
|
|
27669
|
+
progress: external_exports.string().nullable().default(null),
|
|
27670
|
+
reviewPending: external_exports.boolean().default(false),
|
|
27671
|
+
dueAt: external_exports.string().nullable().default(null),
|
|
27672
|
+
/** The Goal this one was opened under; null at the root. */
|
|
27673
|
+
parentGoalId: external_exports.string().nullable().default(null),
|
|
27674
|
+
/** Goals opened under this one — only those the same list holds. */
|
|
27675
|
+
childGoalIds: external_exports.array(external_exports.string()).default([]),
|
|
27676
|
+
/** Goals this one waits on (start or finish gates). */
|
|
27677
|
+
dependencyGoalIds: external_exports.array(external_exports.string()).default([]),
|
|
27678
|
+
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
27679
|
+
blocked: external_exports.boolean().default(false),
|
|
27680
|
+
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
27681
|
+
questions: external_exports.array(QueueQuestionSchema).default([]),
|
|
27682
|
+
createdAt: external_exports.string(),
|
|
27683
|
+
updatedAt: external_exports.string().nullable().default(null)
|
|
28005
27684
|
});
|
|
28006
27685
|
NoteSourceSchema = external_exports.enum(["app", "call"]);
|
|
28007
27686
|
NoteStatusSchema = external_exports.enum(["open", "assigned", "in_progress", "done"]);
|
|
@@ -28130,6 +27809,8 @@ var init_dist = __esm({
|
|
|
28130
27809
|
failed: external_exports.array(external_exports.object({ noteId: external_exports.string(), reason: external_exports.string() }))
|
|
28131
27810
|
});
|
|
28132
27811
|
DeliveryModeSchema = external_exports.enum(["poll", "self_hosted"]);
|
|
27812
|
+
WAKE_EVENT = "wake";
|
|
27813
|
+
wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
28133
27814
|
RegisterDeliverySchema = external_exports.object({ mode: DeliveryModeSchema });
|
|
28134
27815
|
OAuthStartSchema = external_exports.object({
|
|
28135
27816
|
provider: external_exports.enum(["cma"]),
|
|
@@ -28138,15 +27819,29 @@ var init_dist = __esm({
|
|
|
28138
27819
|
DeliveryConfigSchema = external_exports.object({
|
|
28139
27820
|
tokenId: external_exports.string(),
|
|
28140
27821
|
mode: DeliveryModeSchema,
|
|
28141
|
-
/** null when the
|
|
28142
|
-
*
|
|
27822
|
+
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
27823
|
+
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
27824
|
+
* carries credentials; only a `poll` registration can come back with null here. */
|
|
28143
27825
|
realtime: external_exports.object({ url: external_exports.string(), anonKey: external_exports.string() }).nullable()
|
|
28144
27826
|
});
|
|
28145
27827
|
StatusSchema = external_exports.object({
|
|
28146
27828
|
name: external_exports.string(),
|
|
28147
27829
|
sessionMode: external_exports.enum(["default", "all_calls", "silent"]),
|
|
28148
27830
|
/** A phone is registered for push/ring (any push token on the account). */
|
|
28149
|
-
phone: external_exports.boolean()
|
|
27831
|
+
phone: external_exports.boolean(),
|
|
27832
|
+
/** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
|
|
27833
|
+
* it never picked up. THE SAME NUMBER the harness's wake gate reads
|
|
27834
|
+
* (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
|
|
27835
|
+
* zero while the sweep sees one is two ideas of "waiting".
|
|
27836
|
+
*
|
|
27837
|
+
* Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
|
|
27838
|
+
* that nobody spawned, so a terminal session only learns of work by asking. The harness
|
|
27839
|
+
* used to paper over that by spawning a SECOND process on the identity; now it stands
|
|
27840
|
+
* back, correctly, and the person sitting at the terminal is the one who can act. A
|
|
27841
|
+
* coffee-beans request sat unread for three days.
|
|
27842
|
+
*
|
|
27843
|
+
* Optional: an older API sends no field, and the statusline then renders exactly as before. */
|
|
27844
|
+
waiting: external_exports.number().int().nonnegative().optional()
|
|
28150
27845
|
});
|
|
28151
27846
|
EnvelopeRecipientSchema = external_exports.object({
|
|
28152
27847
|
keyId: external_exports.string(),
|
|
@@ -28312,24 +28007,11 @@ var init_dist = __esm({
|
|
|
28312
28007
|
message: external_exports.string(),
|
|
28313
28008
|
childIds: external_exports.array(external_exports.string()).optional()
|
|
28314
28009
|
});
|
|
28315
|
-
CONTACT_SCHEMA = contactSchemaFrom({
|
|
28316
|
-
ask: NotifyRequestFields.shape.ask,
|
|
28317
|
-
waiting: NotifyRequestFields.shape.waiting,
|
|
28318
|
-
options: NotifyRequestFields.shape.options,
|
|
28319
|
-
channel: NotifyRequestFields.shape.channel,
|
|
28320
|
-
parentId: NotifyRequestFields.shape.parentId,
|
|
28321
|
-
workId: NotifyRequestFields.shape.workId,
|
|
28322
|
-
goalId: NotifyRequestFields.shape.goalId
|
|
28323
|
-
});
|
|
28324
28010
|
import_tweetnacl = __toESM2(require_nacl_fast(), 1);
|
|
28325
28011
|
import_tweetnacl2 = __toESM2(require_nacl_fast(), 1);
|
|
28326
28012
|
import_tweetnacl3 = __toESM2(require_nacl_fast(), 1);
|
|
28327
28013
|
import_tweetnacl4 = __toESM2(require_nacl_fast(), 1);
|
|
28328
28014
|
import_tweetnacl5 = __toESM2(require_nacl_fast(), 1);
|
|
28329
|
-
B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
28330
|
-
ok = false;
|
|
28331
|
-
ENVELOPE_VERSION = 1;
|
|
28332
|
-
ENVELOPE_ALG = "x25519-xsalsa20poly1305";
|
|
28333
28015
|
AGENT_NAME = process.env.PAIGY_AGENT || sessionSlot();
|
|
28334
28016
|
TOKEN_PATH = join2(homedir2(), ".paigy", "token.json");
|
|
28335
28017
|
KEY_PATH = join2(homedir2(), ".paigy", "key.json");
|
|
@@ -28340,25 +28022,28 @@ var init_dist = __esm({
|
|
|
28340
28022
|
this.name = "UnpairedError";
|
|
28341
28023
|
}
|
|
28342
28024
|
};
|
|
28343
|
-
|
|
28344
|
-
|
|
28345
|
-
|
|
28346
|
-
|
|
28347
|
-
|
|
28348
|
-
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
28352
|
-
|
|
28353
|
-
|
|
28354
|
-
|
|
28355
|
-
|
|
28356
|
-
super(message);
|
|
28357
|
-
this.retryable = retryable;
|
|
28358
|
-
this.name = "ReplyAckError";
|
|
28025
|
+
ApiError = class extends Error {
|
|
28026
|
+
status;
|
|
28027
|
+
body;
|
|
28028
|
+
constructor(what, status, text) {
|
|
28029
|
+
super(`${what} failed: ${status} ${text}`);
|
|
28030
|
+
this.name = "ApiError";
|
|
28031
|
+
this.status = status;
|
|
28032
|
+
let body = void 0;
|
|
28033
|
+
try {
|
|
28034
|
+
body = JSON.parse(text);
|
|
28035
|
+
} catch {
|
|
28036
|
+
}
|
|
28037
|
+
this.body = body;
|
|
28359
28038
|
}
|
|
28360
|
-
retryable;
|
|
28361
28039
|
};
|
|
28040
|
+
AWAIT_WINDOW_MS = 45e3;
|
|
28041
|
+
tokenOverride = null;
|
|
28042
|
+
authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
28043
|
+
HEARTBEAT_MS = 25e3;
|
|
28044
|
+
RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
28045
|
+
JOIN = { config: { broadcast: { self: false } } };
|
|
28046
|
+
lines = /* @__PURE__ */ new Map();
|
|
28362
28047
|
}
|
|
28363
28048
|
});
|
|
28364
28049
|
|
|
@@ -29661,11 +29346,11 @@ var require_dijkstra = __commonJS({
|
|
|
29661
29346
|
var predecessors = {};
|
|
29662
29347
|
var costs = {};
|
|
29663
29348
|
costs[s] = 0;
|
|
29664
|
-
var
|
|
29665
|
-
|
|
29349
|
+
var open = dijkstra.PriorityQueue.make();
|
|
29350
|
+
open.push(s, 0);
|
|
29666
29351
|
var closest, u, v, cost_of_s_to_u, adjacent_nodes, cost_of_e, cost_of_s_to_u_plus_cost_of_e, cost_of_s_to_v, first_visit;
|
|
29667
|
-
while (!
|
|
29668
|
-
closest =
|
|
29352
|
+
while (!open.empty()) {
|
|
29353
|
+
closest = open.pop();
|
|
29669
29354
|
u = closest.value;
|
|
29670
29355
|
cost_of_s_to_u = closest.cost;
|
|
29671
29356
|
adjacent_nodes = graph[u] || {};
|
|
@@ -29677,7 +29362,7 @@ var require_dijkstra = __commonJS({
|
|
|
29677
29362
|
first_visit = typeof costs[v] === "undefined";
|
|
29678
29363
|
if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) {
|
|
29679
29364
|
costs[v] = cost_of_s_to_u_plus_cost_of_e;
|
|
29680
|
-
|
|
29365
|
+
open.push(v, cost_of_s_to_u_plus_cost_of_e);
|
|
29681
29366
|
predecessors[v] = u;
|
|
29682
29367
|
}
|
|
29683
29368
|
}
|
|
@@ -32969,17 +32654,17 @@ async function available(deps = {}) {
|
|
|
32969
32654
|
return [ollama, ...harnesses];
|
|
32970
32655
|
}
|
|
32971
32656
|
function refusal(options, asked) {
|
|
32972
|
-
const
|
|
32657
|
+
const lines2 = [
|
|
32973
32658
|
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.",
|
|
32974
32659
|
"",
|
|
32975
32660
|
"Triage runs the model where your queue already lives (#1106). Your options:"
|
|
32976
32661
|
];
|
|
32977
32662
|
for (const o of options) {
|
|
32978
|
-
|
|
32979
|
-
if (!o.ready && o.hint)
|
|
32663
|
+
lines2.push(` ${o.ready ? "\u2713" : "\u2717"} ${o.provider.padEnd(7)} ${PRIVACY[o.provider]}`);
|
|
32664
|
+
if (!o.ready && o.hint) lines2.push(` ${o.hint}`);
|
|
32980
32665
|
}
|
|
32981
|
-
|
|
32982
|
-
return
|
|
32666
|
+
lines2.push("", "Then re-run: paigy-harness triage [--via ollama|claude|codex|agy]");
|
|
32667
|
+
return lines2.join("\n");
|
|
32983
32668
|
}
|
|
32984
32669
|
async function judgeFor(asked, deps = {}) {
|
|
32985
32670
|
const options = await available(deps);
|
|
@@ -33128,8 +32813,8 @@ function agentSignals(notes, connections, now) {
|
|
|
33128
32813
|
}
|
|
33129
32814
|
function noteSignals(notes, connections, now) {
|
|
33130
32815
|
const nameOf = new Map(connections.map((c) => [c.id, c.name]));
|
|
33131
|
-
const
|
|
33132
|
-
return
|
|
32816
|
+
const open = notes.filter((n) => OPEN_STATUSES.includes(n.status));
|
|
32817
|
+
return open.map((n) => {
|
|
33133
32818
|
const superseded = supersededBy(n, notes);
|
|
33134
32819
|
const echo = doneEcho(n, notes);
|
|
33135
32820
|
const token = assigneeToken(n);
|
|
@@ -33377,27 +33062,27 @@ function suggested(run) {
|
|
|
33377
33062
|
return run.close.length + run.stale.length + run.assign.reduce((n, a) => n + a.notes.length, 0);
|
|
33378
33063
|
}
|
|
33379
33064
|
function renderTable(run) {
|
|
33380
|
-
const
|
|
33065
|
+
const lines2 = [];
|
|
33381
33066
|
const width = Math.max(
|
|
33382
33067
|
24,
|
|
33383
33068
|
...[...run.close, ...run.stale, ...run.assign.flatMap((a) => a.notes)].map((n) => Math.min(n.title.length, 48))
|
|
33384
33069
|
);
|
|
33385
33070
|
const row = (n) => ` ${n.title.slice(0, width).padEnd(width)} ${n.why}`;
|
|
33386
33071
|
if (run.close.length) {
|
|
33387
|
-
|
|
33388
|
-
|
|
33072
|
+
lines2.push(`Paigy thinks you should CLOSE ${plural(run.close.length, "note")}:`);
|
|
33073
|
+
lines2.push(...run.close.map(row), "");
|
|
33389
33074
|
}
|
|
33390
33075
|
if (run.stale.length) {
|
|
33391
|
-
|
|
33392
|
-
|
|
33076
|
+
lines2.push(`Paigy thinks ${plural(run.stale.length, "note")} ${run.stale.length === 1 ? "has" : "have"} gone STALE:`);
|
|
33077
|
+
lines2.push(...run.stale.map(row), "");
|
|
33393
33078
|
}
|
|
33394
33079
|
for (const group of run.assign) {
|
|
33395
|
-
|
|
33396
|
-
|
|
33080
|
+
lines2.push(`Paigy thinks you should ASSIGN ${plural(group.notes.length, "note")} to ${group.agentName}:`);
|
|
33081
|
+
lines2.push(...group.notes.map(row), "");
|
|
33397
33082
|
}
|
|
33398
33083
|
const acted = suggested(run);
|
|
33399
|
-
|
|
33400
|
-
return
|
|
33084
|
+
lines2.push(`Reviewed ${plural(run.reviewed, "open note")} \xB7 ${plural(acted, "suggestion")} \xB7 ${run.reviewed - acted} left alone`);
|
|
33085
|
+
return lines2.join("\n");
|
|
33401
33086
|
}
|
|
33402
33087
|
async function confirm(question) {
|
|
33403
33088
|
if (!process.stdin.isTTY) return false;
|
|
@@ -33595,8 +33280,301 @@ import { execSync } from "child_process";
|
|
|
33595
33280
|
import { homedir as homedir7 } from "os";
|
|
33596
33281
|
import { join as join5 } from "path";
|
|
33597
33282
|
|
|
33598
|
-
// src/
|
|
33599
|
-
|
|
33283
|
+
// src/harness/session.ts
|
|
33284
|
+
import { spawn } from "child_process";
|
|
33285
|
+
import { existsSync as existsSync3 } from "fs";
|
|
33286
|
+
import { homedir as homedir3 } from "os";
|
|
33287
|
+
import { resolve, delimiter as delimiter2 } from "path";
|
|
33288
|
+
|
|
33289
|
+
// src/harness/acp.ts
|
|
33290
|
+
var none = { events: [], writes: [] };
|
|
33291
|
+
function optionFor(options, decision) {
|
|
33292
|
+
const want = decision.allow ? "allow_once" : "reject_once";
|
|
33293
|
+
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
33294
|
+
}
|
|
33295
|
+
function createAcpDriver(opts) {
|
|
33296
|
+
return new AcpDriver(opts.cwd, opts.mode);
|
|
33297
|
+
}
|
|
33298
|
+
var AcpDriver = class {
|
|
33299
|
+
constructor(cwd, mode) {
|
|
33300
|
+
this.cwd = cwd;
|
|
33301
|
+
this.mode = mode;
|
|
33302
|
+
}
|
|
33303
|
+
cwd;
|
|
33304
|
+
mode;
|
|
33305
|
+
nextId = 1;
|
|
33306
|
+
initId;
|
|
33307
|
+
sessionNewId;
|
|
33308
|
+
promptId;
|
|
33309
|
+
sessionId;
|
|
33310
|
+
queued = [];
|
|
33311
|
+
/** Options of each unanswered permission request, keyed by its JSON-RPC id. */
|
|
33312
|
+
pending = /* @__PURE__ */ new Map();
|
|
33313
|
+
/** Where the REPORT starts in `text` — everything before the LAST tool call is working
|
|
33314
|
+
* narration ("Now the API endpoints." → runs a tool), and it used to ship: the chunks
|
|
33315
|
+
* concatenate with no separator, so the owner's phone got "…find the repo.Now I have
|
|
33316
|
+
* the full picture. Writing the migration.Now…" as the opening paragraph of a finished
|
|
33317
|
+
* task (live, 2026-08-11 — "looks like a working log"). The narration's audience is the
|
|
33318
|
+
* terminal and host.log; what the agent composed AFTER its last tool call is the part
|
|
33319
|
+
* addressed to a human, and that is what leaves the machine. */
|
|
33320
|
+
reportFrom = 0;
|
|
33321
|
+
/** The turn being streamed: text accumulates, tools append, both flush on stopReason. */
|
|
33322
|
+
text = "";
|
|
33323
|
+
tools = [];
|
|
33324
|
+
/** The opening frame. Everything after is driven by responses in `handleLine`. */
|
|
33325
|
+
open() {
|
|
33326
|
+
this.initId = this.nextId++;
|
|
33327
|
+
return [
|
|
33328
|
+
frame2({
|
|
33329
|
+
id: this.initId,
|
|
33330
|
+
method: "initialize",
|
|
33331
|
+
params: {
|
|
33332
|
+
protocolVersion: 2,
|
|
33333
|
+
// We are not an editor: no file services offered, the agent uses its own.
|
|
33334
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
|
|
33335
|
+
clientInfo: { name: "paigy-desktop", version: "0.0.0" }
|
|
33336
|
+
}
|
|
33337
|
+
})
|
|
33338
|
+
];
|
|
33339
|
+
}
|
|
33340
|
+
/** Queue a prompt; it goes out when the session exists and no turn is running. */
|
|
33341
|
+
send(text) {
|
|
33342
|
+
this.queued.push(text);
|
|
33343
|
+
return this.flush();
|
|
33344
|
+
}
|
|
33345
|
+
/** Answer a PermissionEvent (ask mode). The id is the request's JSON-RPC id as a string. */
|
|
33346
|
+
respond(id, decision) {
|
|
33347
|
+
const request = this.pending.get(id);
|
|
33348
|
+
if (!request) return [];
|
|
33349
|
+
this.pending.delete(id);
|
|
33350
|
+
const optionId = optionFor(request.options, decision);
|
|
33351
|
+
return [
|
|
33352
|
+
optionId ? frame2({ id: request.id, result: { outcome: { outcome: "selected", optionId } } }) : frame2({ id: request.id, result: { outcome: { outcome: "cancelled" } } })
|
|
33353
|
+
];
|
|
33354
|
+
}
|
|
33355
|
+
/** Cancel anything still blocked — called on stop so the process can exit cleanly. */
|
|
33356
|
+
close() {
|
|
33357
|
+
const writes = [...this.pending.values()].map(
|
|
33358
|
+
(r) => frame2({ id: r.id, result: { outcome: { outcome: "cancelled" } } })
|
|
33359
|
+
);
|
|
33360
|
+
this.pending.clear();
|
|
33361
|
+
return writes;
|
|
33362
|
+
}
|
|
33363
|
+
handleLine(line) {
|
|
33364
|
+
const trimmed = line.trim();
|
|
33365
|
+
if (!trimmed) return none;
|
|
33366
|
+
let msg;
|
|
33367
|
+
try {
|
|
33368
|
+
msg = JSON.parse(trimmed);
|
|
33369
|
+
} catch {
|
|
33370
|
+
return none;
|
|
33371
|
+
}
|
|
33372
|
+
if (msg.method !== void 0) {
|
|
33373
|
+
return msg.id !== void 0 ? this.handleRequest(msg) : this.handleNotification(msg);
|
|
33374
|
+
}
|
|
33375
|
+
if (msg.id !== void 0) return this.handleResponse(msg);
|
|
33376
|
+
return none;
|
|
33377
|
+
}
|
|
33378
|
+
// ── responses to our requests ──
|
|
33379
|
+
handleResponse(msg) {
|
|
33380
|
+
if (msg.id === this.initId) {
|
|
33381
|
+
this.initId = void 0;
|
|
33382
|
+
if (msg.error) {
|
|
33383
|
+
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}` }], writes: [] };
|
|
33384
|
+
}
|
|
33385
|
+
this.sessionNewId = this.nextId++;
|
|
33386
|
+
return {
|
|
33387
|
+
events: [],
|
|
33388
|
+
writes: [frame2({ id: this.sessionNewId, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } })]
|
|
33389
|
+
};
|
|
33390
|
+
}
|
|
33391
|
+
if (msg.id === this.sessionNewId) {
|
|
33392
|
+
this.sessionNewId = void 0;
|
|
33393
|
+
const sessionId2 = msg.result?.sessionId;
|
|
33394
|
+
if (!sessionId2) {
|
|
33395
|
+
return {
|
|
33396
|
+
events: [{ kind: "error", message: `session/new failed: ${msg.error?.message ?? "no sessionId"}` }],
|
|
33397
|
+
writes: []
|
|
33398
|
+
};
|
|
33399
|
+
}
|
|
33400
|
+
this.sessionId = sessionId2;
|
|
33401
|
+
const value = this.mode === "bypass" ? "bypassPermissions" : "default";
|
|
33402
|
+
const writes = [
|
|
33403
|
+
frame2({
|
|
33404
|
+
id: this.nextId++,
|
|
33405
|
+
method: "session/set_config_option",
|
|
33406
|
+
params: { sessionId: sessionId2, configId: "mode", value }
|
|
33407
|
+
}),
|
|
33408
|
+
frame2({ id: this.nextId++, method: "session/set_mode", params: { sessionId: sessionId2, modeId: value } })
|
|
33409
|
+
];
|
|
33410
|
+
writes.push(...this.flush());
|
|
33411
|
+
return { events: [], writes };
|
|
33412
|
+
}
|
|
33413
|
+
if (msg.id === this.promptId) {
|
|
33414
|
+
this.promptId = void 0;
|
|
33415
|
+
const events = [];
|
|
33416
|
+
const report = this.text.slice(this.reportFrom).trim() || this.text.trim();
|
|
33417
|
+
if (report || this.tools.length) {
|
|
33418
|
+
events.push({
|
|
33419
|
+
kind: "turn",
|
|
33420
|
+
role: "agent",
|
|
33421
|
+
text: report,
|
|
33422
|
+
...this.tools.length ? { tools: [...this.tools] } : {}
|
|
33423
|
+
});
|
|
33424
|
+
}
|
|
33425
|
+
const result = report;
|
|
33426
|
+
const failed = msg.error !== void 0 || msg.result?.stopReason === "refusal";
|
|
33427
|
+
this.text = "";
|
|
33428
|
+
this.tools = [];
|
|
33429
|
+
this.reportFrom = 0;
|
|
33430
|
+
const writes = this.flush();
|
|
33431
|
+
if (!writes.length) {
|
|
33432
|
+
events.push({
|
|
33433
|
+
kind: "idle",
|
|
33434
|
+
...result ? { result } : {},
|
|
33435
|
+
...failed ? { failed: true } : {}
|
|
33436
|
+
});
|
|
33437
|
+
}
|
|
33438
|
+
return { events, writes };
|
|
33439
|
+
}
|
|
33440
|
+
return none;
|
|
33441
|
+
}
|
|
33442
|
+
// ── the agent talking to us ──
|
|
33443
|
+
handleNotification(msg) {
|
|
33444
|
+
if (msg.method !== "session/update") return none;
|
|
33445
|
+
const update = msg.params?.update;
|
|
33446
|
+
switch (update?.sessionUpdate) {
|
|
33447
|
+
case "agent_message_chunk":
|
|
33448
|
+
this.text += update.content?.text ?? "";
|
|
33449
|
+
return none;
|
|
33450
|
+
case "tool_call": {
|
|
33451
|
+
const title = update.title?.trim() || update.kind || "tool";
|
|
33452
|
+
this.tools.push(title);
|
|
33453
|
+
const note = this.text.slice(this.reportFrom).trim();
|
|
33454
|
+
this.reportFrom = this.text.length;
|
|
33455
|
+
return { events: [{ kind: "work", tool: title, ...note ? { note } : {} }], writes: [] };
|
|
33456
|
+
}
|
|
33457
|
+
default:
|
|
33458
|
+
return none;
|
|
33459
|
+
}
|
|
33460
|
+
}
|
|
33461
|
+
handleRequest(msg) {
|
|
33462
|
+
if (msg.method !== "session/request_permission") {
|
|
33463
|
+
return {
|
|
33464
|
+
events: [],
|
|
33465
|
+
writes: [frame2({ id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } })]
|
|
33466
|
+
};
|
|
33467
|
+
}
|
|
33468
|
+
const id = msg.id;
|
|
33469
|
+
const options = msg.params?.options ?? [];
|
|
33470
|
+
const title = msg.params?.toolCall?.title?.trim() || "a tool call";
|
|
33471
|
+
const tool = msg.params?.toolCall?.kind ?? "tool";
|
|
33472
|
+
if (this.mode === "bypass") {
|
|
33473
|
+
const optionId = optionFor(options, { allow: true }) ?? optionFor(options, { allow: false });
|
|
33474
|
+
return {
|
|
33475
|
+
// The decision still goes through Paigy — as history, not a question. Bypass
|
|
33476
|
+
// means "don't stall the agent", never "don't tell the user".
|
|
33477
|
+
events: [{ kind: "turn", role: "agent", text: `auto-approved: ${title}`, tools: [tool] }],
|
|
33478
|
+
writes: [
|
|
33479
|
+
optionId ? frame2({ id, result: { outcome: { outcome: "selected", optionId } } }) : frame2({ id, result: { outcome: { outcome: "cancelled" } } })
|
|
33480
|
+
]
|
|
33481
|
+
};
|
|
33482
|
+
}
|
|
33483
|
+
this.pending.set(String(id), { id, options });
|
|
33484
|
+
return {
|
|
33485
|
+
events: [{ kind: "permission", id: String(id), tool, summary: title }],
|
|
33486
|
+
writes: []
|
|
33487
|
+
};
|
|
33488
|
+
}
|
|
33489
|
+
/** Send the queued prompts as one turn, if the agent can take one right now. */
|
|
33490
|
+
flush() {
|
|
33491
|
+
if (!this.sessionId || this.promptId !== void 0 || !this.queued.length) return [];
|
|
33492
|
+
const blocks = this.queued.map((text) => ({ type: "text", text }));
|
|
33493
|
+
this.queued = [];
|
|
33494
|
+
this.promptId = this.nextId++;
|
|
33495
|
+
return [
|
|
33496
|
+
frame2({
|
|
33497
|
+
id: this.promptId,
|
|
33498
|
+
method: "session/prompt",
|
|
33499
|
+
params: { sessionId: this.sessionId, prompt: blocks }
|
|
33500
|
+
})
|
|
33501
|
+
];
|
|
33502
|
+
}
|
|
33503
|
+
};
|
|
33504
|
+
var frame2 = (body) => JSON.stringify({ jsonrpc: "2.0", ...body });
|
|
33505
|
+
|
|
33506
|
+
// src/harness/session.ts
|
|
33507
|
+
init_catalog();
|
|
33508
|
+
var ADAPTER_BIN = {
|
|
33509
|
+
claude: "claude-agent-acp",
|
|
33510
|
+
codex: "codex-acp",
|
|
33511
|
+
agy: "agy"
|
|
33512
|
+
};
|
|
33513
|
+
function splitLines(buffer, chunk) {
|
|
33514
|
+
const combined = buffer + chunk;
|
|
33515
|
+
const parts = combined.split("\n");
|
|
33516
|
+
const rest = parts.pop() ?? "";
|
|
33517
|
+
return { lines: parts.filter((l) => l.trim()), rest };
|
|
33518
|
+
}
|
|
33519
|
+
function startSession(opts) {
|
|
33520
|
+
const cwd = resolve(opts.cwd.replace(/^~(?=$|\/)/, homedir3()));
|
|
33521
|
+
if (!existsSync3(cwd)) {
|
|
33522
|
+
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
33523
|
+
}
|
|
33524
|
+
const child = (opts.spawnFn ?? spawn)(opts.bin ?? ADAPTER_BIN[opts.harness], [], {
|
|
33525
|
+
cwd,
|
|
33526
|
+
// The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI- or
|
|
33527
|
+
// launchd-launched process's PATH won't have it — probe dirs plus the RUNNING
|
|
33528
|
+
// node's own bin dir (nvm installs the adapters next to node; launchd's bare
|
|
33529
|
+
// PATH knows neither — live catch 2026-08-04: the first slot-wake ENOENT'd).
|
|
33530
|
+
env: {
|
|
33531
|
+
...process.env,
|
|
33532
|
+
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
33533
|
+
...opts.token ? { PAIGY_TOKEN: opts.token } : {}
|
|
33534
|
+
},
|
|
33535
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
33536
|
+
});
|
|
33537
|
+
const write = (frames) => {
|
|
33538
|
+
for (const f of frames) child.stdin?.write(`${f}
|
|
33539
|
+
`);
|
|
33540
|
+
};
|
|
33541
|
+
child.stderr?.on("data", (chunk) => {
|
|
33542
|
+
const text = String(chunk).trim();
|
|
33543
|
+
if (text) opts.onEvent({ kind: "error", message: text });
|
|
33544
|
+
});
|
|
33545
|
+
child.on("exit", (code) => {
|
|
33546
|
+
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
33547
|
+
opts.onExit?.();
|
|
33548
|
+
});
|
|
33549
|
+
child.on("error", (e) => {
|
|
33550
|
+
opts.onEvent({ kind: "error", message: e.message });
|
|
33551
|
+
opts.onExit?.();
|
|
33552
|
+
});
|
|
33553
|
+
const driver = createAcpDriver({ cwd, mode: opts.mode });
|
|
33554
|
+
let buffer = "";
|
|
33555
|
+
child.stdout?.on("data", (chunk) => {
|
|
33556
|
+
const { lines: lines2, rest } = splitLines(buffer, String(chunk));
|
|
33557
|
+
buffer = rest;
|
|
33558
|
+
for (const line of lines2) {
|
|
33559
|
+
const { events, writes } = driver.handleLine(line);
|
|
33560
|
+
write(writes);
|
|
33561
|
+
for (const event of events) opts.onEvent(event);
|
|
33562
|
+
}
|
|
33563
|
+
});
|
|
33564
|
+
write(driver.open());
|
|
33565
|
+
return {
|
|
33566
|
+
send(text) {
|
|
33567
|
+
write(driver.send(text));
|
|
33568
|
+
},
|
|
33569
|
+
respond(id, decision) {
|
|
33570
|
+
write(driver.respond(id, decision));
|
|
33571
|
+
},
|
|
33572
|
+
stop() {
|
|
33573
|
+
write(driver.close());
|
|
33574
|
+
child.kill();
|
|
33575
|
+
}
|
|
33576
|
+
};
|
|
33577
|
+
}
|
|
33600
33578
|
|
|
33601
33579
|
// ../../packages/schema/dist/index.js
|
|
33602
33580
|
init_zod();
|
|
@@ -34894,22 +34872,20 @@ var zodToJsonSchema2 = (schema, options) => {
|
|
|
34894
34872
|
};
|
|
34895
34873
|
|
|
34896
34874
|
// ../../packages/schema/dist/index.js
|
|
34875
|
+
init_zod();
|
|
34897
34876
|
var OPTIONS_MIN2 = 2;
|
|
34898
34877
|
var OPTIONS_MAX2 = 6;
|
|
34899
|
-
var MISSED_CALL_PLAN2 = {
|
|
34900
|
-
retry_10m: { kind: "every", minutes: 10 },
|
|
34901
|
-
retry_30m: { kind: "every", minutes: 30 },
|
|
34902
|
-
retry_60m: { kind: "every", minutes: 60 },
|
|
34903
|
-
backoff_gentle: { kind: "at", minutes: [30, 120, 360] },
|
|
34904
|
-
backoff_standard: { kind: "at", minutes: [10, 30, 120] },
|
|
34905
|
-
backoff_aggressive: { kind: "at", minutes: [5, 15, 45] },
|
|
34906
|
-
inbox: { kind: "once" },
|
|
34907
|
-
dismiss: { kind: "grace", minutes: 2 }
|
|
34908
|
-
};
|
|
34909
34878
|
function draft20202(node) {
|
|
34910
34879
|
if (Array.isArray(node)) return node.map(draft20202);
|
|
34911
34880
|
if (node && typeof node === "object") {
|
|
34912
34881
|
const o = node;
|
|
34882
|
+
if (Array.isArray(o.items)) {
|
|
34883
|
+
o.prefixItems = o.items;
|
|
34884
|
+
if ("additionalItems" in o) {
|
|
34885
|
+
o.items = o.additionalItems;
|
|
34886
|
+
delete o.additionalItems;
|
|
34887
|
+
} else delete o.items;
|
|
34888
|
+
}
|
|
34913
34889
|
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
34914
34890
|
if (typeof o[excl] === "boolean") {
|
|
34915
34891
|
if (o[excl] === true && typeof o[lim] === "number") {
|
|
@@ -34928,11 +34904,54 @@ function mcpInputSchema2(s) {
|
|
|
34928
34904
|
delete schema.$schema;
|
|
34929
34905
|
return draft20202(schema);
|
|
34930
34906
|
}
|
|
34907
|
+
var StartContactSchema2 = external_exports.object({
|
|
34908
|
+
goalIds: external_exports.tuple([external_exports.string().uuid()]),
|
|
34909
|
+
// THE ASK IS THE QUESTION, not a bulletin with a question at the end (2026-09-16).
|
|
34910
|
+
//
|
|
34911
|
+
// A DecisionNeed is settled only by an answer in the shape THIS ask declares. Bundle news,
|
|
34912
|
+
// findings and a decision into one, and the person answers whichever part engaged them —
|
|
34913
|
+
// which settles nothing, leaves the need open, and gets them asked again. On call 619c5a92
|
|
34914
|
+
// one ask carried a briefing, a side question ("the ring should show the agent's name now —
|
|
34915
|
+
// what did you actually see?") and a one-of-four about how far an erase reaches. The owner
|
|
34916
|
+
// answered the side question. The need stayed open; the card asked again. Of the 45 call
|
|
34917
|
+
// asks carrying a decision, 27 are over 600 characters.
|
|
34918
|
+
//
|
|
34919
|
+
// Sending them separately costs nothing, because a CALL contact joins the call already
|
|
34920
|
+
// happening (`goal/store.ts`, JOIN BEFORE MINTING) — several contacts on one Goal arrive as
|
|
34921
|
+
// one call, planned as its turns, and each keeps its own settleable need.
|
|
34922
|
+
ask: external_exports.string().trim().min(1).max(1e4).describe(
|
|
34923
|
+
"The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
|
|
34924
|
+
),
|
|
34925
|
+
waiting: external_exports.enum(["none", "hard"]).default("none"),
|
|
34926
|
+
channel: external_exports.enum(["notification", "call"]).default("notification"),
|
|
34927
|
+
options: external_exports.array(external_exports.object({ label: external_exports.string().trim().min(1).max(1e3), image: external_exports.string().url().optional(), html: external_exports.string().max(16384).optional() }).strict()).min(2).max(6).optional(),
|
|
34928
|
+
threadId: external_exports.string().uuid().optional().describe("Continue an existing Thread: the threadId a prior Delivery returned. Omit to start a new Thread.")
|
|
34929
|
+
}).strict();
|
|
34930
|
+
var ContactSchema2 = external_exports.union([StartContactSchema2, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict()]);
|
|
34931
|
+
var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
34932
|
+
var CONTACT_DESCRIPTION2 = "Contact the user about exactly one existing Goal: pass goalIds:[goalId], ask, channel:'notification'|'call', and waiting:'none'|'hard'. Options supply choices. Notification returns immediately; collect durable answers with claim_goal/get_goal. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Continuation sends nothing and rereads the same durable evidence, including previously read answers. Entries retain authorship and provenance; accepted decisions are separate from quoted speech. Call state open does not mean ringing. Unsupported: soft waiting, multiple Goals/questions, outcome admission, and re-presentation of an existing request. Create a Goal explicitly first; never resend a pending ask to continue waiting.";
|
|
34931
34933
|
var CreateGoalSchema2 = external_exports.object({
|
|
34932
34934
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
34933
34935
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
34934
|
-
idempotencyKey: external_exports.string().trim().min(1).max(200)
|
|
34936
|
+
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
34937
|
+
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
34938
|
+
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
34939
|
+
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
34940
|
+
* the writer refuses a thread belonging to another account. */
|
|
34941
|
+
contextThreadId: external_exports.string().uuid().optional(),
|
|
34942
|
+
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
34943
|
+
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
34944
|
+
* worked"). It could not have been: this door took no parent, so the only route was
|
|
34945
|
+
* create -> claim -> `update_goal`, three calls with a lease in the middle, and every agent
|
|
34946
|
+
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
34947
|
+
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
34948
|
+
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
34949
|
+
parentGoalId: external_exports.string().uuid().optional()
|
|
34935
34950
|
});
|
|
34951
|
+
var CreateGoalToolSchema2 = CreateGoalSchema2.extend({
|
|
34952
|
+
idempotencyKey: CreateGoalSchema2.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
34953
|
+
}).strict();
|
|
34954
|
+
var CREATE_GOAL_DESCRIPTION2 = "Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.";
|
|
34936
34955
|
var UpdateGoalSchema2 = external_exports.object({
|
|
34937
34956
|
revision: external_exports.number().int().positive(),
|
|
34938
34957
|
changes: external_exports.object({
|
|
@@ -34948,52 +34967,48 @@ var UpdateGoalSchema2 = external_exports.object({
|
|
|
34948
34967
|
reason: external_exports.string().trim().min(1).max(2e3),
|
|
34949
34968
|
operationId: external_exports.string().uuid().optional()
|
|
34950
34969
|
}).strict();
|
|
34951
|
-
var UpdateGoalToolSchema2 = UpdateGoalSchema2.extend({ goalId: external_exports.string().uuid() }).strict();
|
|
34970
|
+
var UpdateGoalToolSchema2 = UpdateGoalSchema2.omit({ operationId: true }).extend({ goalId: external_exports.string().uuid() }).strict();
|
|
34952
34971
|
var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
34953
|
-
var
|
|
34954
|
-
var
|
|
34955
|
-
|
|
34956
|
-
|
|
34957
|
-
|
|
34958
|
-
|
|
34959
|
-
|
|
34960
|
-
|
|
34961
|
-
|
|
34962
|
-
|
|
34963
|
-
|
|
34964
|
-
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
34968
|
-
|
|
34969
|
-
|
|
34970
|
-
|
|
34971
|
-
|
|
34972
|
-
|
|
34973
|
-
|
|
34974
|
-
|
|
34975
|
-
|
|
34976
|
-
|
|
34977
|
-
|
|
34978
|
-
|
|
34979
|
-
|
|
34980
|
-
|
|
34981
|
-
|
|
34982
|
-
|
|
34983
|
-
|
|
34984
|
-
|
|
34985
|
-
|
|
34986
|
-
|
|
34987
|
-
|
|
34988
|
-
|
|
34989
|
-
)
|
|
34990
|
-
}
|
|
34991
|
-
|
|
34992
|
-
delete out.required;
|
|
34993
|
-
out.anyOf = [{ required: ["ask"] }, { required: ["wait"] }];
|
|
34994
|
-
return out;
|
|
34972
|
+
var GetGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
34973
|
+
var GET_GOAL_DESCRIPTION2 = "Read the current authorized Goal brief: state, owner, blockers, open decisions, progress, and the next operation. Foreign or sibling-owned Goals are not disclosed.";
|
|
34974
|
+
var UPDATE_GOAL_DESCRIPTION2 = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, and review acknowledgement are explicit; stale revisions are rejected. Returns the new revision and a prose summary.";
|
|
34975
|
+
var CLAIM_GOAL_DESCRIPTION2 = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns a Goal-scoped brief, current revision, blockers, and the next valid operation. Claiming creates or renews the execution lease.";
|
|
34976
|
+
var CHECK_REPLIES_DESCRIPTION2 = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 each with its durable Entries, accepted decisions and open decision needs, in the same shape a contact read returns. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it with contact({deliveryId}). Your runnable and review-pending Goals come from claim_goal, not from here.";
|
|
34977
|
+
var CheckRepliesSchema2 = external_exports.object({}).strict();
|
|
34978
|
+
var GetThreadSchema2 = external_exports.object({
|
|
34979
|
+
parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
34980
|
+
}).strict();
|
|
34981
|
+
var GET_THREAD_DESCRIPTION2 = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
|
|
34982
|
+
var SearchThreadsSchema2 = external_exports.object({
|
|
34983
|
+
q: external_exports.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
|
|
34984
|
+
}).strict();
|
|
34985
|
+
var SEARCH_THREADS_DESCRIPTION2 = `Search your PAST conversations before asking \u2014 "have we discussed this before?". Full-text over your own threads (the asks you sent + the user's answers); returns ranked threads with highlighted snippets, NOT rows: { hits: [{ parentId, at, agentLabel, matches: [{ notificationId, role, snippet }] }] }. The loop this exists for: search first \u2192 get_thread the best hit to rehydrate it \u2192 THEN continue or contact, so you answer with receipts ("last week you said ship it") instead of re-asking. Read-only, safe to call anytime; scoped to your own account's threads.`;
|
|
34986
|
+
var AGENT_TOOLS2 = [
|
|
34987
|
+
{ name: "contact", description: CONTACT_DESCRIPTION2, inputSchema: CONTACT_SCHEMA2 },
|
|
34988
|
+
{ name: "check_replies", description: CHECK_REPLIES_DESCRIPTION2, inputSchema: mcpInputSchema2(CheckRepliesSchema2) },
|
|
34989
|
+
{ name: "get_thread", description: GET_THREAD_DESCRIPTION2, inputSchema: mcpInputSchema2(GetThreadSchema2) },
|
|
34990
|
+
{ name: "search_threads", description: SEARCH_THREADS_DESCRIPTION2, inputSchema: mcpInputSchema2(SearchThreadsSchema2) },
|
|
34991
|
+
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(CreateGoalToolSchema2) },
|
|
34992
|
+
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(ClaimGoalSchema2) },
|
|
34993
|
+
{ name: "get_goal", description: GET_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(GetGoalSchema2) },
|
|
34994
|
+
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(UpdateGoalToolSchema2) }
|
|
34995
|
+
];
|
|
34996
|
+
var AGENT_TOOL_NAMES2 = AGENT_TOOLS2.map((t) => t.name);
|
|
34997
|
+
function entryWords(entry) {
|
|
34998
|
+
const content = entry.content;
|
|
34999
|
+
if (content && "sealed" in content) return "";
|
|
35000
|
+
const plain = content?.plain;
|
|
35001
|
+
if (typeof plain === "string") return plain;
|
|
35002
|
+
if (plain && typeof plain === "object") {
|
|
35003
|
+
const text = plain.text;
|
|
35004
|
+
if (typeof text === "string") return text;
|
|
35005
|
+
const title = plain.title;
|
|
35006
|
+
const description = plain.description;
|
|
35007
|
+
const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
|
|
35008
|
+
if (parts.length) return parts.join("\n\n");
|
|
35009
|
+
}
|
|
35010
|
+
return entry.sources.map((source) => source.text).join("\n");
|
|
34995
35011
|
}
|
|
34996
|
-
var CONTACT_DESCRIPTION2 = `Reach the user through Paigy \u2014 tell them something, or ask and get their answer. State what you need in \`ask\`, say what happens to your work while you wait in \`waiting\`, and Paigy handles the rest (channel, phrasing, answer format). If the user explicitly asks you to CALL them, send waiting:'hard' and say so in the ask. Returns { notificationId, parentId, workId?, goalId?, entryId?, decisionId?, wait? }. WHEN IT RANG, contact holds the first ~45 s window ITSELF and \`wait\` carries the outcome: { type:'reply', answer } to act on; { type:'partial', inFlight:true, turn } \u2014 what the user is saying to each turn, provisional: use it to PREPARE (fetch, draft, warm the build), never to act irreversibly, they can still revise it until the final reply (partial = intelligence, settled = authorization; if a partial's acts carry a question aimed at you and you know the answer, contact on the SAME parentId right away \u2014 they hear it on the same call); { type:'remind', remindInSeconds } \u2014 schedule a wake-up; { type:'idle' } \u2014 still waiting. TO KEEP WAITING, call contact again with ONLY { wait: notificationId } \u2014 no ask, nothing new is sent; it holds the next scoped ~45 s window (under the 60 s host cap, so it always returns) and never returns another notification's reply. Keep doing that until the reply \u2014 THAT one is the decision \u2014 so the user steps away and comes back to find you already continued; stop only to do other work and check back, or after an unreasonably long stretch worth telling them about. A message delivery has NO \`wait\`: never poll for it \u2014 the reply arrives through check_replies or your wake. Pass parentId to a later contact to continue the conversation. A Goal-targeted contact also returns goalId and entryId for the durable Goal entry. When it rang, the reply also carries { ifMissed: { mode, means } }: what the user's own policy does with a call they don't take ("${STANDARD_MEANS2}"), so a no-answer tells you how long to wait before coming back. THREADING REPLACES: a threaded follow-up SUPERSEDES your earlier pending items on that thread \u2014 right for updates to one ask, WRONG for a checklist (send independent to-dos un-threaded). A threaded re-send with IDENTICAL content escalates the pending ask in place. If a reply comes back as {kind:'clarify', chunks:[...]}, the user wants more detail \u2014 contact again on the SAME parentId with an expanded ask. BLOCKED ON A DECISION for existing work? Pass that work's \`workId\`; the reply returns the same workId plus a decisionId, so the answer resumes the right outcome. ONE ASK, ONE ROW: never restate a still-pending ask's question inside a NEW contact (e.g. weaving it into a briefing) \u2014 the whole answer settles on the new row and the original can never receive it. Keep waiting on the original (a live call reads every pending ask out separately, each answer routes to its own row), and use \`needs\` for a genuinely multi-part NEW ask. ANSWERABLE, NOT JUST ASKED: when the reply comes back carrying \`plan.units[].needs\`, that unit asked for something it gave the user no way to answer \u2014 'options' means it posed a choice with nothing to choose from, 'visuals' means it asked about something to look at with nothing to look at. Send it again on the SAME parentId with ${OPTIONS_MIN2}-${OPTIONS_MAX2} options (or the image), drawn from your own sentence. Paigy will not add them for you: a shape it guessed wrong cannot be undone, and you are the one who knows what the real alternatives are. \`units\` reports WHAT BECAME OF YOUR PROSE \u2014 { kept, raw, why }: how many topics Paigy compressed for delivery, how many kept your exact words, and the reason when it kept them (e.g. 'no_output' = compression produced nothing usable, so the user got your raw sentence). It needs no action and is not an error \u2014 read it only when the delivered wording matters to you; a high \`raw\` count means the user is hearing you verbatim. READING A CALL'S REPLY: it can come back as {kind:'turns', turns:[{prompt,reply}]} \u2014 the ordered log of that call. Read turns[0].reply as the user's main instruction. Usually that's the only turn; if there are more (e.g. an end-of-call 'call me back when it's done / I have a question that blocks me'), read each one in order as a further follow-up instruction, not a single combined one. If they asked for a callback, re-engage in the SAME thread (contact with the reply's parentId) when the task is done or you hit a blocker \u2014 waiting:'hard' for a blocker, waiting:'none' for done. Paigy has no scheduler; the callback is yours to send (use ScheduleWakeup/cron for timing). A call-mapped answer may carry \`intents\` \u2014 next steps the user attached, each { kind, detail } with detail quoting their words. ACT on them, don't just read them: 'defer' ("call me after lunch") \u2192 register it NOW with schedule_callback \u2014 when the intent carries \`dueInSeconds\` (Paigy pre-parsed the spoken time against the user's clock) pass it straight through; otherwise derive it from the detail yourself \u2014 then follow up on the same thread; 'delegate' ("you pick") \u2192 make the call yourself and tell them what you chose; 'channel' ("text me next time") \u2192 honor it on your next contact (channel:'message'); 'question' (an open question aimed back at you that the call couldn't answer) \u2192 you OWE them the answer \u2014 work it out and follow up on the same thread without being asked, the call deliberately skipped "should I call you back?" because the follow-up is implied. \`transcript\` is the user's raw words behind a shaped answer \u2014 read it for hedges and conditions ("yes, IF tests pass") before acting. If your ask declared \`points\`, the reply carries \`covered\` \u2014 the points actually addressed. Compare against what you declared: a missing point is STILL unanswered \u2014 re-ask it (contact on the same parentId) or proceed knowingly partial; never treat a partial answer as complete.`;
|
|
34997
35012
|
var ContextSchema2 = external_exports.object({
|
|
34998
35013
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
34999
35014
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -35222,35 +35237,8 @@ var NotifyRequestSchema2 = NotifyRequestFields2.superRefine((r, ctx) => {
|
|
|
35222
35237
|
if (!needsOptions && r.options?.length)
|
|
35223
35238
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
35224
35239
|
});
|
|
35225
|
-
function unitsOf2(text) {
|
|
35226
|
-
const spans = [];
|
|
35227
|
-
const re = /\n\s*\n+/g;
|
|
35228
|
-
let cursor = 0;
|
|
35229
|
-
const push = (from, to) => {
|
|
35230
|
-
const slice = text.slice(from, to);
|
|
35231
|
-
const lead = slice.length - slice.trimStart().length;
|
|
35232
|
-
const tail = slice.length - slice.trimEnd().length;
|
|
35233
|
-
if (from + lead < to - tail) spans.push({ start: from + lead, end: to - tail });
|
|
35234
|
-
};
|
|
35235
|
-
for (let m = re.exec(text); m; m = re.exec(text)) {
|
|
35236
|
-
push(cursor, m.index);
|
|
35237
|
-
cursor = m.index + m[0].length;
|
|
35238
|
-
}
|
|
35239
|
-
push(cursor, text.length);
|
|
35240
|
-
return spans;
|
|
35241
|
-
}
|
|
35242
35240
|
var NotifyStatusSchema2 = external_exports.enum(["pending", "answered", "ignored"]);
|
|
35243
35241
|
var AgentStateSchema2 = external_exports.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
35244
|
-
var SetTaskStateSchema2 = external_exports.object({
|
|
35245
|
-
state: external_exports.enum(["in_progress", "completed", "needs_input"])
|
|
35246
|
-
});
|
|
35247
|
-
var SetWorkStateSchema2 = external_exports.object({
|
|
35248
|
-
workId: external_exports.string().uuid().optional(),
|
|
35249
|
-
notificationId: external_exports.string().optional(),
|
|
35250
|
-
state: SetTaskStateSchema2.shape.state
|
|
35251
|
-
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
35252
|
-
message: "exactly one of workId or notificationId is required"
|
|
35253
|
-
});
|
|
35254
35242
|
var TurnSchema2 = external_exports.object({
|
|
35255
35243
|
prompt: external_exports.string(),
|
|
35256
35244
|
reply: external_exports.string()
|
|
@@ -35380,108 +35368,6 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
35380
35368
|
})
|
|
35381
35369
|
]);
|
|
35382
35370
|
var CallbackTriggerSchema2 = external_exports.enum(["on_done", "on_blocked", "scheduled"]);
|
|
35383
|
-
var ScheduleCallbackSchema2 = external_exports.object({
|
|
35384
|
-
parentId: external_exports.string().describe("The thread to call back on (from a prior contact / reply / request)."),
|
|
35385
|
-
goalId: external_exports.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
|
|
35386
|
-
trigger: CallbackTriggerSchema2,
|
|
35387
|
-
dueInSeconds: external_exports.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
|
|
35388
|
-
note: external_exports.string().optional().describe("What to tell the user when you follow up.")
|
|
35389
|
-
});
|
|
35390
|
-
var PendingRepliesSchema2 = external_exports.object({
|
|
35391
|
-
replies: external_exports.array(
|
|
35392
|
-
external_exports.object({
|
|
35393
|
-
parentId: external_exports.string(),
|
|
35394
|
-
notificationId: external_exports.string(),
|
|
35395
|
-
workId: external_exports.string().uuid().optional(),
|
|
35396
|
-
decisionId: external_exports.string().uuid().optional(),
|
|
35397
|
-
answer: UserAnswerSchema2,
|
|
35398
|
-
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the
|
|
35399
|
-
* pairing is E2EE — the agent opens it and re-derives the real answer. Absent =
|
|
35400
|
-
* plaintext answer (in `answer`). See AwaitItemSchema's reply variant. */
|
|
35401
|
-
sealed: external_exports.lazy(() => SealedAnswerSchema2).optional(),
|
|
35402
|
-
/** The call record rendered for THIS agent: the raw words the shaped answer was
|
|
35403
|
-
* mapped from, filtered to its own claims. See AwaitItemSchema's reply variant. */
|
|
35404
|
-
transcript: external_exports.string().optional(),
|
|
35405
|
-
/** Coverage report (#396): which declared `points` this answer addressed. */
|
|
35406
|
-
covered: external_exports.array(external_exports.string()).optional()
|
|
35407
|
-
})
|
|
35408
|
-
),
|
|
35409
|
-
pending: external_exports.array(
|
|
35410
|
-
external_exports.object({ parentId: external_exports.string(), notificationId: external_exports.string(), createdAt: external_exports.string() })
|
|
35411
|
-
),
|
|
35412
|
-
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
35413
|
-
* sees for this session's identity. From inside a session there was no way to find out —
|
|
35414
|
-
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
35415
|
-
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
35416
|
-
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
35417
|
-
you: external_exports.object({ name: external_exports.string(), device: external_exports.string().nullable(), tokenId: external_exports.string() }).optional(),
|
|
35418
|
-
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
35419
|
-
* contact on the same parentId. Keeps reappearing until you call
|
|
35420
|
-
* set_work_state on its workId (or notificationId once while bootstrapping). */
|
|
35421
|
-
requests: external_exports.array(
|
|
35422
|
-
external_exports.object({
|
|
35423
|
-
parentId: external_exports.string(),
|
|
35424
|
-
notificationId: external_exports.string(),
|
|
35425
|
-
workId: external_exports.string().uuid().optional(),
|
|
35426
|
-
text: external_exports.string(),
|
|
35427
|
-
createdAt: external_exports.string(),
|
|
35428
|
-
/** The user seeded this request with a past conversation — call get_thread on it
|
|
35429
|
-
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
35430
|
-
contextParentId: external_exports.string().optional(),
|
|
35431
|
-
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
35432
|
-
* the account — the name here — which has not been seen since it landed, so nobody
|
|
35433
|
-
* came for it. Handed to you because you are the session that is here. Take it like
|
|
35434
|
-
* any request (set_work_state claims it, reply with contact on its parentId), and say
|
|
35435
|
-
* whose it was, because the user chose that agent on purpose. */
|
|
35436
|
-
stranded: external_exports.string().optional()
|
|
35437
|
-
})
|
|
35438
|
-
),
|
|
35439
|
-
/** Durable outcomes currently owned by this agent. This is the Work-native queue; flat
|
|
35440
|
-
* notification lists remain during migration so old clients keep their existing view. */
|
|
35441
|
-
work: external_exports.array(external_exports.object({
|
|
35442
|
-
workId: external_exports.string().uuid(),
|
|
35443
|
-
parentId: external_exports.string().optional(),
|
|
35444
|
-
objective: external_exports.string().nullable(),
|
|
35445
|
-
state: external_exports.enum(["active", "blocked", "waiting_external"]),
|
|
35446
|
-
blockedOn: external_exports.array(external_exports.string().uuid()),
|
|
35447
|
-
updatedAt: external_exports.string()
|
|
35448
|
-
})).optional(),
|
|
35449
|
-
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
35450
|
-
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
35451
|
-
* fulfill one by calling contact on its parentId. */
|
|
35452
|
-
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
35453
|
-
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
35454
|
-
also: external_exports.array(RideAlongSchema2).optional(),
|
|
35455
|
-
owedCallbacks: external_exports.array(
|
|
35456
|
-
external_exports.object({ parentId: external_exports.string(), trigger: CallbackTriggerSchema2, note: external_exports.string() })
|
|
35457
|
-
),
|
|
35458
|
-
/** Work (either direction) you reported in_progress a while ago and never reported
|
|
35459
|
-
* completed — likely left half-done by this session or a prior one that crashed or
|
|
35460
|
-
* went idle. Report a real state (set_work_state) or continue the work. */
|
|
35461
|
-
stalled: external_exports.array(
|
|
35462
|
-
external_exports.object({ parentId: external_exports.string(), notificationId: external_exports.string(), title: external_exports.string().nullable(), startedAt: external_exports.string() })
|
|
35463
|
-
),
|
|
35464
|
-
/** The queue rail (#614, pending/design.md): the same replies + requests, grouped by
|
|
35465
|
-
* thread and ordered oldest-thread-first, so you work ONE thread at a time — fold all of
|
|
35466
|
-
* a thread's `items` into a single turn rather than interleaving threads. `busy` = the
|
|
35467
|
-
* thread already has a turn in progress (younger than the stall cutoff); let it finish and
|
|
35468
|
-
* ride the next turn. `items` are that thread's replies/requests in arrival order; the
|
|
35469
|
-
* full payload for each is in the flat `replies`/`requests` arrays (matched by
|
|
35470
|
-
* notificationId). Derived, never stored — a crashed agent recomputes it exactly. */
|
|
35471
|
-
threads: external_exports.array(
|
|
35472
|
-
external_exports.object({
|
|
35473
|
-
parentId: external_exports.string(),
|
|
35474
|
-
busy: external_exports.boolean(),
|
|
35475
|
-
items: external_exports.array(
|
|
35476
|
-
external_exports.object({
|
|
35477
|
-
kind: external_exports.enum(["reply", "request"]),
|
|
35478
|
-
notificationId: external_exports.string(),
|
|
35479
|
-
at: external_exports.string()
|
|
35480
|
-
})
|
|
35481
|
-
)
|
|
35482
|
-
})
|
|
35483
|
-
)
|
|
35484
|
-
});
|
|
35485
35371
|
var NotifyResponseSchema2 = external_exports.object({
|
|
35486
35372
|
notificationId: external_exports.string(),
|
|
35487
35373
|
workId: external_exports.string().uuid().optional(),
|
|
@@ -35544,6 +35430,13 @@ var UserResponseSchema2 = external_exports.object({
|
|
|
35544
35430
|
});
|
|
35545
35431
|
var VoiceKeySchema2 = external_exports.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
35546
35432
|
var AgendaTurnSchema2 = external_exports.object({
|
|
35433
|
+
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
35434
|
+
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
35435
|
+
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
35436
|
+
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
35437
|
+
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
35438
|
+
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
35439
|
+
id: external_exports.string().optional(),
|
|
35547
35440
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
35548
35441
|
* the planner declares duplicates instead of asking them twice. */
|
|
35549
35442
|
coveredIds: external_exports.array(external_exports.string()).optional(),
|
|
@@ -35702,7 +35595,21 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
35702
35595
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
35703
35596
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
35704
35597
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
35705
|
-
agentX25519: external_exports.string().optional()
|
|
35598
|
+
agentX25519: external_exports.string().optional(),
|
|
35599
|
+
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
35600
|
+
* that Delivery's kind, the request Entry, the Goals it answers for, the exact DecisionNeed (none
|
|
35601
|
+
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
35602
|
+
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
35603
|
+
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
35604
|
+
communication: external_exports.object({
|
|
35605
|
+
deliveryId: external_exports.string(),
|
|
35606
|
+
kind: external_exports.enum(["notification", "call"]),
|
|
35607
|
+
entryId: external_exports.string(),
|
|
35608
|
+
goalIds: external_exports.array(external_exports.string()),
|
|
35609
|
+
decisionNeedId: external_exports.string().optional(),
|
|
35610
|
+
sealed: external_exports.boolean(),
|
|
35611
|
+
goalState: external_exports.string().optional()
|
|
35612
|
+
}).optional()
|
|
35706
35613
|
});
|
|
35707
35614
|
var SnoozeRequestSchema2 = external_exports.object({
|
|
35708
35615
|
requestId: external_exports.string(),
|
|
@@ -35938,26 +35845,54 @@ var CreateRequestSchema2 = external_exports.object({
|
|
|
35938
35845
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
35939
35846
|
contextParentId: external_exports.string().optional()
|
|
35940
35847
|
});
|
|
35941
|
-
var
|
|
35942
|
-
/**
|
|
35943
|
-
|
|
35944
|
-
/**
|
|
35945
|
-
|
|
35946
|
-
/**
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
/**
|
|
35951
|
-
|
|
35952
|
-
|
|
35953
|
-
|
|
35954
|
-
|
|
35955
|
-
|
|
35956
|
-
|
|
35957
|
-
|
|
35958
|
-
|
|
35959
|
-
|
|
35960
|
-
|
|
35848
|
+
var QueueQuestionSchema2 = external_exports.object({
|
|
35849
|
+
/** The decision need's id — what an answer is accepted against. */
|
|
35850
|
+
id: external_exports.string(),
|
|
35851
|
+
/** The words that were asked, from the request Entry that asked them. */
|
|
35852
|
+
question: external_exports.string(),
|
|
35853
|
+
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
35854
|
+
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
35855
|
+
* can answer. */
|
|
35856
|
+
deliveryId: external_exports.string().nullable().default(null),
|
|
35857
|
+
/** The Entry the ruling is about. */
|
|
35858
|
+
aboutId: external_exports.string().nullable().default(null),
|
|
35859
|
+
/** Empty for a free-text question. */
|
|
35860
|
+
options: external_exports.array(OptionSchema2).default([]),
|
|
35861
|
+
select: external_exports.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
35862
|
+
askedAt: external_exports.string(),
|
|
35863
|
+
/** Null while the question is open — which is how the page tells the two apart. */
|
|
35864
|
+
answeredAt: external_exports.string().nullable().default(null),
|
|
35865
|
+
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
35866
|
+
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
35867
|
+
* for a settled question whose reply carried nothing readable. */
|
|
35868
|
+
answer: external_exports.string().nullable().default(null)
|
|
35869
|
+
});
|
|
35870
|
+
var QueueItemSchema2 = external_exports.object({
|
|
35871
|
+
id: external_exports.string(),
|
|
35872
|
+
/** One-line headline — the first sentence of the outcome. */
|
|
35873
|
+
title: external_exports.string(),
|
|
35874
|
+
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
35875
|
+
intent: external_exports.string(),
|
|
35876
|
+
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
35877
|
+
state: external_exports.string(),
|
|
35878
|
+
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
35879
|
+
assignee: external_exports.string().nullable().default(null),
|
|
35880
|
+
/** What the agent last said it was doing; null if it has said nothing. */
|
|
35881
|
+
progress: external_exports.string().nullable().default(null),
|
|
35882
|
+
reviewPending: external_exports.boolean().default(false),
|
|
35883
|
+
dueAt: external_exports.string().nullable().default(null),
|
|
35884
|
+
/** The Goal this one was opened under; null at the root. */
|
|
35885
|
+
parentGoalId: external_exports.string().nullable().default(null),
|
|
35886
|
+
/** Goals opened under this one — only those the same list holds. */
|
|
35887
|
+
childGoalIds: external_exports.array(external_exports.string()).default([]),
|
|
35888
|
+
/** Goals this one waits on (start or finish gates). */
|
|
35889
|
+
dependencyGoalIds: external_exports.array(external_exports.string()).default([]),
|
|
35890
|
+
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
35891
|
+
blocked: external_exports.boolean().default(false),
|
|
35892
|
+
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
35893
|
+
questions: external_exports.array(QueueQuestionSchema2).default([]),
|
|
35894
|
+
createdAt: external_exports.string(),
|
|
35895
|
+
updatedAt: external_exports.string().nullable().default(null)
|
|
35961
35896
|
});
|
|
35962
35897
|
var NoteSourceSchema2 = external_exports.enum(["app", "call"]);
|
|
35963
35898
|
var NoteStatusSchema2 = external_exports.enum(["open", "assigned", "in_progress", "done"]);
|
|
@@ -36094,15 +36029,29 @@ var OAuthStartSchema2 = external_exports.object({
|
|
|
36094
36029
|
var DeliveryConfigSchema2 = external_exports.object({
|
|
36095
36030
|
tokenId: external_exports.string(),
|
|
36096
36031
|
mode: DeliveryModeSchema2,
|
|
36097
|
-
/** null when the
|
|
36098
|
-
*
|
|
36032
|
+
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
36033
|
+
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
36034
|
+
* carries credentials; only a `poll` registration can come back with null here. */
|
|
36099
36035
|
realtime: external_exports.object({ url: external_exports.string(), anonKey: external_exports.string() }).nullable()
|
|
36100
36036
|
});
|
|
36101
36037
|
var StatusSchema2 = external_exports.object({
|
|
36102
36038
|
name: external_exports.string(),
|
|
36103
36039
|
sessionMode: external_exports.enum(["default", "all_calls", "silent"]),
|
|
36104
36040
|
/** A phone is registered for push/ring (any push token on the account). */
|
|
36105
|
-
phone: external_exports.boolean()
|
|
36041
|
+
phone: external_exports.boolean(),
|
|
36042
|
+
/** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
|
|
36043
|
+
* it never picked up. THE SAME NUMBER the harness's wake gate reads
|
|
36044
|
+
* (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
|
|
36045
|
+
* zero while the sweep sees one is two ideas of "waiting".
|
|
36046
|
+
*
|
|
36047
|
+
* Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
|
|
36048
|
+
* that nobody spawned, so a terminal session only learns of work by asking. The harness
|
|
36049
|
+
* used to paper over that by spawning a SECOND process on the identity; now it stands
|
|
36050
|
+
* back, correctly, and the person sitting at the terminal is the one who can act. A
|
|
36051
|
+
* coffee-beans request sat unread for three days.
|
|
36052
|
+
*
|
|
36053
|
+
* Optional: an older API sends no field, and the statusline then renders exactly as before. */
|
|
36054
|
+
waiting: external_exports.number().int().nonnegative().optional()
|
|
36106
36055
|
});
|
|
36107
36056
|
var EnvelopeRecipientSchema2 = external_exports.object({
|
|
36108
36057
|
keyId: external_exports.string(),
|
|
@@ -36268,18 +36217,31 @@ var FeedbackOutcomeSchema2 = external_exports.object({
|
|
|
36268
36217
|
message: external_exports.string(),
|
|
36269
36218
|
childIds: external_exports.array(external_exports.string()).optional()
|
|
36270
36219
|
});
|
|
36271
|
-
var CONTACT_SCHEMA2 = contactSchemaFrom2({
|
|
36272
|
-
ask: NotifyRequestFields2.shape.ask,
|
|
36273
|
-
waiting: NotifyRequestFields2.shape.waiting,
|
|
36274
|
-
options: NotifyRequestFields2.shape.options,
|
|
36275
|
-
channel: NotifyRequestFields2.shape.channel,
|
|
36276
|
-
parentId: NotifyRequestFields2.shape.parentId,
|
|
36277
|
-
workId: NotifyRequestFields2.shape.workId,
|
|
36278
|
-
goalId: NotifyRequestFields2.shape.goalId
|
|
36279
|
-
});
|
|
36280
36220
|
|
|
36281
|
-
// src/paigy/
|
|
36221
|
+
// src/paigy/activity.ts
|
|
36222
|
+
function shortenPaths(s) {
|
|
36223
|
+
return s.replace(/(?<![\w.@+-])(?:\/[\w.@+-]+){3,}/g, (p) => {
|
|
36224
|
+
const parts = p.split("/").filter(Boolean);
|
|
36225
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
36226
|
+
});
|
|
36227
|
+
}
|
|
36228
|
+
function workLine(event) {
|
|
36229
|
+
const note = (event.note ?? "").split("\n").find((l) => l.trim()) ?? "";
|
|
36230
|
+
const line = shortenPaths([event.tool.trim(), note.trim()].filter(Boolean).join(" \u2014 ").replace(/\s+/g, " "));
|
|
36231
|
+
return line.length > ACTIVITY_LINE_MAX2 ? `${line.slice(0, ACTIVITY_LINE_MAX2 - 1)}\u2026` : line;
|
|
36232
|
+
}
|
|
36233
|
+
function pushWork(lines2, line) {
|
|
36234
|
+
if (!line || lines2[lines2.length - 1] === line) return [...lines2];
|
|
36235
|
+
return [...lines2, line].slice(-ACTIVITY_LINES2);
|
|
36236
|
+
}
|
|
36237
|
+
function sameTail(a, b) {
|
|
36238
|
+
return a.length === b.length && a.every((l, i) => l === b[i]);
|
|
36239
|
+
}
|
|
36240
|
+
|
|
36241
|
+
// src/paigy/bridge.ts
|
|
36282
36242
|
init_dist();
|
|
36243
|
+
|
|
36244
|
+
// src/paigy/conversation.ts
|
|
36283
36245
|
function endsWithQuestion(text) {
|
|
36284
36246
|
if (!text) return false;
|
|
36285
36247
|
const tail = text.trim().split("\n").filter(Boolean).slice(-3).join(" ").toLowerCase();
|
|
@@ -36292,57 +36254,60 @@ function endsWithQuestion(text) {
|
|
|
36292
36254
|
}
|
|
36293
36255
|
var POLL_MS = 5e3;
|
|
36294
36256
|
var wait = () => new Promise((r) => setTimeout(r, POLL_MS));
|
|
36295
|
-
|
|
36296
|
-
|
|
36297
|
-
|
|
36298
|
-
|
|
36299
|
-
|
|
36300
|
-
|
|
36301
|
-
|
|
36257
|
+
var ended = (state) => state === "done" || state === "cancelled";
|
|
36258
|
+
function unheard(brief, state) {
|
|
36259
|
+
const seen = state.seen ??= /* @__PURE__ */ new Set();
|
|
36260
|
+
const words2 = [];
|
|
36261
|
+
for (const e of brief.entries ?? []) {
|
|
36262
|
+
if (seen.has(e.entryId) || !e.authorParticipant.startsWith("human:")) continue;
|
|
36263
|
+
seen.add(e.entryId);
|
|
36264
|
+
if (e.aboutId && seen.has(e.aboutId) && brief.answers?.some((a) => a.requestEntryId === e.aboutId)) continue;
|
|
36265
|
+
const text = entryWords(e).trim();
|
|
36266
|
+
if (text) words2.push(text);
|
|
36267
|
+
}
|
|
36268
|
+
return words2;
|
|
36269
|
+
}
|
|
36270
|
+
async function step(session, state, rail) {
|
|
36271
|
+
if (!state.goal) {
|
|
36272
|
+
if (!state.resting) return;
|
|
36273
|
+
const brief2 = await rail.claim();
|
|
36274
|
+
if (!brief2.goalId || brief2.revision === void 0) return;
|
|
36275
|
+
unheard(brief2, state);
|
|
36276
|
+
if (ended(brief2.state)) await rail.update(brief2.goalId, { revision: brief2.revision, changes: { reviewed: true }, reason: "Typed into the session." });
|
|
36277
|
+
else state.goal = { id: brief2.goalId, revision: brief2.revision };
|
|
36278
|
+
send(session, state, brief2.message);
|
|
36279
|
+
return;
|
|
36302
36280
|
}
|
|
36303
|
-
const
|
|
36304
|
-
|
|
36305
|
-
|
|
36306
|
-
|
|
36307
|
-
|
|
36308
|
-
|
|
36309
|
-
|
|
36310
|
-
|
|
36311
|
-
|
|
36312
|
-
|
|
36313
|
-
|
|
36314
|
-
|
|
36315
|
-
|
|
36316
|
-
} catch {
|
|
36317
|
-
}
|
|
36281
|
+
const brief = await rail.read(state.goal.id);
|
|
36282
|
+
state.goal.revision = brief.revision ?? state.goal.revision;
|
|
36283
|
+
for (const answer of brief.answers ?? []) {
|
|
36284
|
+
const resolve3 = state.asks?.get(answer.requestEntryId);
|
|
36285
|
+
if (!resolve3) continue;
|
|
36286
|
+
state.asks.delete(answer.requestEntryId);
|
|
36287
|
+
resolve3(answer.result);
|
|
36288
|
+
}
|
|
36289
|
+
if (ended(brief.state)) {
|
|
36290
|
+
state.goal = void 0;
|
|
36291
|
+
for (const resolve3 of state.asks?.values() ?? []) resolve3(null);
|
|
36292
|
+
state.asks?.clear();
|
|
36293
|
+
return;
|
|
36318
36294
|
}
|
|
36319
|
-
return
|
|
36295
|
+
if (state.asks?.size || !state.resting) return;
|
|
36296
|
+
const words2 = unheard(brief, state);
|
|
36297
|
+
if (words2.length) send(session, state, words2.join("\n\n"));
|
|
36320
36298
|
}
|
|
36321
|
-
|
|
36322
|
-
|
|
36323
|
-
|
|
36324
|
-
return answer?.trim() ? answer : null;
|
|
36299
|
+
function send(session, state, text) {
|
|
36300
|
+
state.resting = false;
|
|
36301
|
+
session.send(text);
|
|
36325
36302
|
}
|
|
36326
|
-
async function pump(session, state,
|
|
36303
|
+
async function pump(session, state, rail, opts) {
|
|
36327
36304
|
for (; ; ) {
|
|
36328
|
-
|
|
36329
|
-
state
|
|
36330
|
-
|
|
36331
|
-
const resolve3 = key !== void 0 ? state.asks?.get(key) : void 0;
|
|
36332
|
-
if (key !== void 0 && resolve3) {
|
|
36333
|
-
state.asks?.delete(key);
|
|
36334
|
-
resolve3(item.answer);
|
|
36335
|
-
continue;
|
|
36336
|
-
}
|
|
36337
|
-
const words2 = spokenText(item.answer);
|
|
36338
|
-
if (words2) {
|
|
36339
|
-
state.resting = false;
|
|
36340
|
-
session.send(words2);
|
|
36341
|
-
}
|
|
36305
|
+
try {
|
|
36306
|
+
await step(session, state, rail);
|
|
36307
|
+
} catch {
|
|
36342
36308
|
}
|
|
36343
|
-
|
|
36344
|
-
|
|
36345
|
-
await (deps.pause ?? wait)();
|
|
36309
|
+
if (!await opts.onIdle()) return;
|
|
36310
|
+
await (opts.pause ?? wait)();
|
|
36346
36311
|
}
|
|
36347
36312
|
}
|
|
36348
36313
|
|
|
@@ -36364,530 +36329,107 @@ var DESTRUCTIVE = [
|
|
|
36364
36329
|
function isDestructive(summary) {
|
|
36365
36330
|
return DESTRUCTIVE.some((re) => re.test(summary));
|
|
36366
36331
|
}
|
|
36367
|
-
function levelFor(event) {
|
|
36368
|
-
switch (event.kind) {
|
|
36369
|
-
case "turn":
|
|
36370
|
-
return "inbox";
|
|
36371
|
-
case "permission":
|
|
36372
|
-
return isDestructive(event.summary) ? "call" : "banner";
|
|
36373
|
-
case "idle":
|
|
36374
|
-
if (endsWithQuestion(event.result)) return "banner";
|
|
36375
|
-
return event.failed ? "push" : "inbox";
|
|
36376
|
-
case "work":
|
|
36377
|
-
return "inbox";
|
|
36378
|
-
// moot — entryFor drops work before a level ever matters
|
|
36379
|
-
case "error":
|
|
36380
|
-
return "inbox";
|
|
36381
|
-
}
|
|
36382
|
-
}
|
|
36383
36332
|
|
|
36384
36333
|
// src/paigy/bridge.ts
|
|
36334
|
+
function railFor(opts) {
|
|
36335
|
+
return {
|
|
36336
|
+
claim: () => claimGoal(void 0, opts),
|
|
36337
|
+
read: (goalId) => getGoal(goalId, opts),
|
|
36338
|
+
update: (goalId, input) => updateGoal(goalId, input, opts),
|
|
36339
|
+
// One read and back: the pump owns the waiting, so a Call must not hold a window here.
|
|
36340
|
+
contact: (input) => contact(input, { ...opts, waits: false })
|
|
36341
|
+
};
|
|
36342
|
+
}
|
|
36385
36343
|
function cancelAsks(state) {
|
|
36386
|
-
for (const resolve3 of state.asks?.values() ?? []) resolve3(
|
|
36344
|
+
for (const resolve3 of state.asks?.values() ?? []) resolve3(null);
|
|
36387
36345
|
state.asks?.clear();
|
|
36388
36346
|
}
|
|
36389
|
-
|
|
36390
|
-
|
|
36391
|
-
|
|
36392
|
-
|
|
36393
|
-
|
|
36394
|
-
|
|
36395
|
-
|
|
36396
|
-
|
|
36397
|
-
case "turn": {
|
|
36398
|
-
const who = event.role === "agent" ? "Agent" : "You";
|
|
36399
|
-
const body = event.text ? event.role === "agent" ? event.text : `${who}: ${event.text}` : `${who} ran ${(event.tools ?? []).join(", ")}.`;
|
|
36400
|
-
return {
|
|
36401
|
-
...common,
|
|
36402
|
-
ask: body
|
|
36403
|
-
// No `select`: the contract refuses it on the `ask` form ("the broker derives it"),
|
|
36404
|
-
// and with no options it derives "text" anyway — the shape this wants, since any
|
|
36405
|
-
// inbox row can be replied to and a reply to history is just the user initiating
|
|
36406
|
-
// (the pump feeds it back in).
|
|
36407
|
-
};
|
|
36408
|
-
}
|
|
36409
|
-
case "permission":
|
|
36410
|
-
return {
|
|
36411
|
-
...common,
|
|
36412
|
-
context: {
|
|
36413
|
-
title: clip(event.summary),
|
|
36414
|
-
description: [
|
|
36415
|
-
`The agent is blocked waiting to run: ${event.summary}`,
|
|
36416
|
-
...event.reason ? [event.reason] : []
|
|
36417
|
-
]
|
|
36418
|
-
},
|
|
36419
|
-
select: "confirm",
|
|
36420
|
-
confirmStyle: "approve",
|
|
36421
|
-
// Literally true, and the inbox uses it: a blocking item gets the badge and the
|
|
36422
|
-
// extra confirm-before-dismiss, which is exactly right for something holding a
|
|
36423
|
-
// real process still.
|
|
36424
|
-
blocking: true
|
|
36425
|
-
};
|
|
36426
|
-
case "idle": {
|
|
36427
|
-
const asking = endsWithQuestion(event.result);
|
|
36428
|
-
const result = event.result?.trim();
|
|
36429
|
-
const norm = (t) => (t ?? "").replace(/\s+/g, " ").trim();
|
|
36430
|
-
if (result && !event.failed && norm(result) === norm(state.lastAgentText)) {
|
|
36431
|
-
if (!asking) return null;
|
|
36432
|
-
const spans = unitsOf2(result);
|
|
36433
|
-
const tail = spans.map((sp) => result.slice(sp.start, sp.end)).reverse().find((t) => t.includes("?"));
|
|
36434
|
-
return { ...common, ask: tail ?? result, blocking: true };
|
|
36435
|
-
}
|
|
36436
|
-
const lead = asking ? "" : event.failed ? "The agent stopped without finishing. " : "Turn complete. ";
|
|
36437
|
-
return {
|
|
36438
|
-
...common,
|
|
36439
|
-
ask: `${lead}${result || (event.failed ? "No result reported." : "Done.")}`,
|
|
36440
|
-
...asking ? { blocking: true } : {}
|
|
36441
|
-
};
|
|
36347
|
+
var ALLOW = "Allow";
|
|
36348
|
+
function decisionFrom(result) {
|
|
36349
|
+
if (result && "kind" in result) {
|
|
36350
|
+
if (result.kind === "one") return result.option.label === ALLOW ? { allow: true } : { allow: false, reason: "Denied from Paigy" };
|
|
36351
|
+
if (result.kind === "confirm") return result.approved ? { allow: true } : { allow: false, reason: "Denied from Paigy" };
|
|
36352
|
+
if (result.kind === "text") {
|
|
36353
|
+
if (/^(yes|y|ok|okay|approve[d]?|allow|go ahead|do it)\b/.test(result.text.trim().toLowerCase())) return { allow: true };
|
|
36354
|
+
return { allow: false, reason: result.text };
|
|
36442
36355
|
}
|
|
36443
|
-
case "work":
|
|
36444
|
-
return null;
|
|
36445
|
-
// log-only by contract — the live texture of the working log
|
|
36446
|
-
case "error":
|
|
36447
|
-
return null;
|
|
36448
|
-
}
|
|
36449
|
-
}
|
|
36450
|
-
function decisionFrom(answer) {
|
|
36451
|
-
if (answer.kind === "confirm") {
|
|
36452
|
-
return answer.approved ? { allow: true } : { allow: false, reason: "Denied from Paigy" };
|
|
36453
|
-
}
|
|
36454
|
-
if (answer.kind === "text") {
|
|
36455
|
-
const said = answer.text.trim().toLowerCase();
|
|
36456
|
-
if (/^(yes|y|ok|okay|approve[d]?|allow|go ahead|do it)\b/.test(said)) return { allow: true };
|
|
36457
|
-
return { allow: false, reason: answer.text };
|
|
36458
|
-
}
|
|
36459
|
-
if (answer.kind === "turns") {
|
|
36460
|
-
const first = answer.turns[0]?.reply ?? "";
|
|
36461
|
-
return decisionFrom({ kind: "text", text: first });
|
|
36462
36356
|
}
|
|
36463
36357
|
return { allow: false, reason: "No approval given" };
|
|
36464
36358
|
}
|
|
36465
|
-
|
|
36466
|
-
|
|
36467
|
-
|
|
36468
|
-
|
|
36469
|
-
|
|
36470
|
-
|
|
36471
|
-
|
|
36472
|
-
|
|
36473
|
-
|
|
36474
|
-
|
|
36475
|
-
|
|
36476
|
-
|
|
36477
|
-
const { notificationId, parentId } = await (deps.submit ?? submitNotification)(req);
|
|
36478
|
-
(state.mine ??= /* @__PURE__ */ new Set()).add(notificationId);
|
|
36479
|
-
return { parentId, notificationId };
|
|
36480
|
-
} catch {
|
|
36481
|
-
return {};
|
|
36482
|
-
}
|
|
36483
|
-
}
|
|
36484
|
-
function awaitAnswer(state, notificationId) {
|
|
36485
|
-
state.asks ??= /* @__PURE__ */ new Map();
|
|
36486
|
-
return new Promise((resolve3) => state.asks.set(notificationId, resolve3));
|
|
36487
|
-
}
|
|
36488
|
-
async function askPermission(event, state, deps = {}) {
|
|
36489
|
-
const entry = entryFor(event, state);
|
|
36490
|
-
if (!entry) return { decision: { allow: false, reason: "Could not ask" } };
|
|
36491
|
-
try {
|
|
36492
|
-
const sent = await (deps.submit ?? submitNotification)(NotifyRequestSchema2.parse(entry));
|
|
36493
|
-
(state.mine ??= /* @__PURE__ */ new Set()).add(sent.notificationId);
|
|
36494
|
-
const answer = await awaitAnswer(state, sent.notificationId);
|
|
36495
|
-
return { decision: decisionFrom(answer), parentId: sent.parentId };
|
|
36496
|
-
} catch (e) {
|
|
36497
|
-
return { decision: { allow: false, reason: `Could not reach Paigy: ${e.message}` } };
|
|
36359
|
+
function wordsOf(result) {
|
|
36360
|
+
if (!result || !("kind" in result)) return null;
|
|
36361
|
+
switch (result.kind) {
|
|
36362
|
+
case "text":
|
|
36363
|
+
return result.text.trim() || null;
|
|
36364
|
+
case "confirm":
|
|
36365
|
+
return result.approved ? "yes" : "no";
|
|
36366
|
+
case "one":
|
|
36367
|
+
return result.option.label;
|
|
36368
|
+
case "many":
|
|
36369
|
+
case "rank":
|
|
36370
|
+
return result.options.map((o) => o.label).join(", ");
|
|
36498
36371
|
}
|
|
36499
36372
|
}
|
|
36500
|
-
async function
|
|
36501
|
-
|
|
36502
|
-
...state.parentId ? { parentId: state.parentId } : {},
|
|
36503
|
-
...state.repo ? { repo: state.repo } : {},
|
|
36504
|
-
...state.branch ? { branch: state.branch } : {},
|
|
36505
|
-
// Prose in — see `entryFor`'s `turn` case. An agent's question is the case most likely
|
|
36506
|
-
// to run long, and its title was a clipped prefix of itself.
|
|
36507
|
-
ask: question,
|
|
36508
|
-
blocking: true,
|
|
36509
|
-
urgency: "banner"
|
|
36510
|
-
};
|
|
36373
|
+
async function ask(state, rail, input) {
|
|
36374
|
+
if (!state.goal) return null;
|
|
36511
36375
|
try {
|
|
36512
|
-
const sent = await
|
|
36513
|
-
|
|
36514
|
-
|
|
36376
|
+
const sent = await rail.contact({
|
|
36377
|
+
goalIds: [state.goal.id],
|
|
36378
|
+
ask: input.ask,
|
|
36379
|
+
waiting: "hard",
|
|
36380
|
+
channel: input.call ? "call" : "notification",
|
|
36381
|
+
...input.options ? { options: input.options } : {}
|
|
36382
|
+
});
|
|
36383
|
+
const entryId = sent.requestEntryIds[0];
|
|
36384
|
+
if (!entryId) return null;
|
|
36385
|
+
(state.seen ??= /* @__PURE__ */ new Set()).add(entryId);
|
|
36386
|
+
return await new Promise((resolve3) => (state.asks ??= /* @__PURE__ */ new Map()).set(entryId, resolve3));
|
|
36515
36387
|
} catch {
|
|
36516
36388
|
return null;
|
|
36517
36389
|
}
|
|
36518
36390
|
}
|
|
36519
|
-
async function
|
|
36520
|
-
|
|
36521
|
-
|
|
36522
|
-
|
|
36523
|
-
|
|
36524
|
-
|
|
36525
|
-
select: "text",
|
|
36526
|
-
urgency: "push"
|
|
36527
|
-
};
|
|
36528
|
-
try {
|
|
36529
|
-
await (deps.submit ?? submitNotification)(NotifyRequestSchema2.parse(entry));
|
|
36530
|
-
} catch {
|
|
36531
|
-
}
|
|
36532
|
-
}
|
|
36533
|
-
function spokenText(answer) {
|
|
36534
|
-
switch (answer.kind) {
|
|
36535
|
-
case "text":
|
|
36536
|
-
return answer.text.trim() || null;
|
|
36537
|
-
case "option":
|
|
36538
|
-
return answer.label ?? answer.optionId;
|
|
36539
|
-
case "multi":
|
|
36540
|
-
case "ranked":
|
|
36541
|
-
return (answer.labels ?? answer.optionIds).join(", ");
|
|
36542
|
-
case "confirm":
|
|
36543
|
-
return answer.approved ? "yes" : "no";
|
|
36544
|
-
case "clarify":
|
|
36545
|
-
return answer.chunks.join(" ");
|
|
36546
|
-
// A call log: the user's replies are the instruction, the bot's prompts are not.
|
|
36547
|
-
case "turns":
|
|
36548
|
-
return answer.turns.map((t) => t.reply).join(" ").trim() || null;
|
|
36549
|
-
// An auto-answer derived from the user's past decisions — reads like a fast human.
|
|
36550
|
-
case "precedent":
|
|
36551
|
-
return answer.answer;
|
|
36552
|
-
case "ignored":
|
|
36553
|
-
return null;
|
|
36554
|
-
}
|
|
36555
|
-
}
|
|
36556
|
-
var clip = (s) => (s.length > 120 ? `${s.slice(0, 117)}\u2026` : s) || "(no text)";
|
|
36557
|
-
|
|
36558
|
-
// src/run.ts
|
|
36559
|
-
init_dist();
|
|
36560
|
-
|
|
36561
|
-
// src/harness/session.ts
|
|
36562
|
-
import { spawn } from "child_process";
|
|
36563
|
-
import { existsSync as existsSync3 } from "fs";
|
|
36564
|
-
import { homedir as homedir3 } from "os";
|
|
36565
|
-
import { resolve, delimiter as delimiter2 } from "path";
|
|
36566
|
-
|
|
36567
|
-
// src/harness/acp.ts
|
|
36568
|
-
var none = { events: [], writes: [] };
|
|
36569
|
-
function optionFor(options, decision) {
|
|
36570
|
-
const want = decision.allow ? "allow_once" : "reject_once";
|
|
36571
|
-
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
36391
|
+
async function askPermission(event, state, rail) {
|
|
36392
|
+
return decisionFrom(await ask(state, rail, {
|
|
36393
|
+
ask: [`The agent is blocked waiting to run: ${event.summary}`, ...event.reason ? [event.reason] : []].join("\n\n"),
|
|
36394
|
+
options: [{ label: ALLOW }, { label: "Deny" }],
|
|
36395
|
+
call: isDestructive(event.summary)
|
|
36396
|
+
}));
|
|
36572
36397
|
}
|
|
36573
|
-
function
|
|
36574
|
-
return
|
|
36398
|
+
async function askQuestion(question, state, rail) {
|
|
36399
|
+
return wordsOf(await ask(state, rail, { ask: question }));
|
|
36575
36400
|
}
|
|
36576
|
-
|
|
36577
|
-
|
|
36578
|
-
|
|
36579
|
-
|
|
36580
|
-
|
|
36581
|
-
|
|
36582
|
-
|
|
36583
|
-
|
|
36584
|
-
|
|
36585
|
-
|
|
36586
|
-
initId;
|
|
36587
|
-
sessionNewId;
|
|
36588
|
-
promptId;
|
|
36589
|
-
sessionId;
|
|
36590
|
-
queued = [];
|
|
36591
|
-
/** Options of each unanswered permission request, keyed by its JSON-RPC id. */
|
|
36592
|
-
pending = /* @__PURE__ */ new Map();
|
|
36593
|
-
/** Where the REPORT starts in `text` — everything before the LAST tool call is working
|
|
36594
|
-
* narration ("Now the API endpoints." → runs a tool), and it used to ship: the chunks
|
|
36595
|
-
* concatenate with no separator, so the owner's phone got "…find the repo.Now I have
|
|
36596
|
-
* the full picture. Writing the migration.Now…" as the opening paragraph of a finished
|
|
36597
|
-
* task (live, 2026-08-11 — "looks like a working log"). The narration's audience is the
|
|
36598
|
-
* terminal and host.log; what the agent composed AFTER its last tool call is the part
|
|
36599
|
-
* addressed to a human, and that is what leaves the machine. */
|
|
36600
|
-
reportFrom = 0;
|
|
36601
|
-
/** The turn being streamed: text accumulates, tools append, both flush on stopReason. */
|
|
36602
|
-
text = "";
|
|
36603
|
-
tools = [];
|
|
36604
|
-
/** The opening frame. Everything after is driven by responses in `handleLine`. */
|
|
36605
|
-
open() {
|
|
36606
|
-
this.initId = this.nextId++;
|
|
36607
|
-
return [
|
|
36608
|
-
frame({
|
|
36609
|
-
id: this.initId,
|
|
36610
|
-
method: "initialize",
|
|
36611
|
-
params: {
|
|
36612
|
-
protocolVersion: 2,
|
|
36613
|
-
// We are not an editor: no file services offered, the agent uses its own.
|
|
36614
|
-
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
|
|
36615
|
-
clientInfo: { name: "paigy-desktop", version: "0.0.0" }
|
|
36616
|
-
}
|
|
36617
|
-
})
|
|
36618
|
-
];
|
|
36619
|
-
}
|
|
36620
|
-
/** Queue a prompt; it goes out when the session exists and no turn is running. */
|
|
36621
|
-
send(text) {
|
|
36622
|
-
this.queued.push(text);
|
|
36623
|
-
return this.flush();
|
|
36624
|
-
}
|
|
36625
|
-
/** Answer a PermissionEvent (ask mode). The id is the request's JSON-RPC id as a string. */
|
|
36626
|
-
respond(id, decision) {
|
|
36627
|
-
const request = this.pending.get(id);
|
|
36628
|
-
if (!request) return [];
|
|
36629
|
-
this.pending.delete(id);
|
|
36630
|
-
const optionId = optionFor(request.options, decision);
|
|
36631
|
-
return [
|
|
36632
|
-
optionId ? frame({ id: request.id, result: { outcome: { outcome: "selected", optionId } } }) : frame({ id: request.id, result: { outcome: { outcome: "cancelled" } } })
|
|
36633
|
-
];
|
|
36634
|
-
}
|
|
36635
|
-
/** Cancel anything still blocked — called on stop so the process can exit cleanly. */
|
|
36636
|
-
close() {
|
|
36637
|
-
const writes = [...this.pending.values()].map(
|
|
36638
|
-
(r) => frame({ id: r.id, result: { outcome: { outcome: "cancelled" } } })
|
|
36639
|
-
);
|
|
36640
|
-
this.pending.clear();
|
|
36641
|
-
return writes;
|
|
36642
|
-
}
|
|
36643
|
-
handleLine(line) {
|
|
36644
|
-
const trimmed = line.trim();
|
|
36645
|
-
if (!trimmed) return none;
|
|
36646
|
-
let msg;
|
|
36647
|
-
try {
|
|
36648
|
-
msg = JSON.parse(trimmed);
|
|
36649
|
-
} catch {
|
|
36650
|
-
return none;
|
|
36651
|
-
}
|
|
36652
|
-
if (msg.method !== void 0) {
|
|
36653
|
-
return msg.id !== void 0 ? this.handleRequest(msg) : this.handleNotification(msg);
|
|
36654
|
-
}
|
|
36655
|
-
if (msg.id !== void 0) return this.handleResponse(msg);
|
|
36656
|
-
return none;
|
|
36657
|
-
}
|
|
36658
|
-
// ── responses to our requests ──
|
|
36659
|
-
handleResponse(msg) {
|
|
36660
|
-
if (msg.id === this.initId) {
|
|
36661
|
-
this.initId = void 0;
|
|
36662
|
-
if (msg.error) {
|
|
36663
|
-
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}` }], writes: [] };
|
|
36664
|
-
}
|
|
36665
|
-
this.sessionNewId = this.nextId++;
|
|
36666
|
-
return {
|
|
36667
|
-
events: [],
|
|
36668
|
-
writes: [frame({ id: this.sessionNewId, method: "session/new", params: { cwd: this.cwd, mcpServers: this.mcp } })]
|
|
36669
|
-
};
|
|
36670
|
-
}
|
|
36671
|
-
if (msg.id === this.sessionNewId) {
|
|
36672
|
-
this.sessionNewId = void 0;
|
|
36673
|
-
const sessionId2 = msg.result?.sessionId;
|
|
36674
|
-
if (!sessionId2) {
|
|
36675
|
-
return {
|
|
36676
|
-
events: [{ kind: "error", message: `session/new failed: ${msg.error?.message ?? "no sessionId"}` }],
|
|
36677
|
-
writes: []
|
|
36678
|
-
};
|
|
36679
|
-
}
|
|
36680
|
-
this.sessionId = sessionId2;
|
|
36681
|
-
const value = this.mode === "bypass" ? "bypassPermissions" : "default";
|
|
36682
|
-
const writes = [
|
|
36683
|
-
frame({
|
|
36684
|
-
id: this.nextId++,
|
|
36685
|
-
method: "session/set_config_option",
|
|
36686
|
-
params: { sessionId: sessionId2, configId: "mode", value }
|
|
36687
|
-
}),
|
|
36688
|
-
frame({ id: this.nextId++, method: "session/set_mode", params: { sessionId: sessionId2, modeId: value } })
|
|
36689
|
-
];
|
|
36690
|
-
writes.push(...this.flush());
|
|
36691
|
-
return { events: [], writes };
|
|
36692
|
-
}
|
|
36693
|
-
if (msg.id === this.promptId) {
|
|
36694
|
-
this.promptId = void 0;
|
|
36695
|
-
const events = [];
|
|
36696
|
-
const report = this.text.slice(this.reportFrom).trim() || this.text.trim();
|
|
36697
|
-
if (report || this.tools.length) {
|
|
36698
|
-
events.push({
|
|
36699
|
-
kind: "turn",
|
|
36700
|
-
role: "agent",
|
|
36701
|
-
text: report,
|
|
36702
|
-
...this.tools.length ? { tools: [...this.tools] } : {}
|
|
36703
|
-
});
|
|
36704
|
-
}
|
|
36705
|
-
const result = report;
|
|
36706
|
-
const failed = msg.error !== void 0 || msg.result?.stopReason === "refusal";
|
|
36707
|
-
this.text = "";
|
|
36708
|
-
this.tools = [];
|
|
36709
|
-
this.reportFrom = 0;
|
|
36710
|
-
const writes = this.flush();
|
|
36711
|
-
if (!writes.length) {
|
|
36712
|
-
events.push({
|
|
36713
|
-
kind: "idle",
|
|
36714
|
-
...result ? { result } : {},
|
|
36715
|
-
...failed ? { failed: true } : {}
|
|
36716
|
-
});
|
|
36717
|
-
}
|
|
36718
|
-
return { events, writes };
|
|
36719
|
-
}
|
|
36720
|
-
return none;
|
|
36721
|
-
}
|
|
36722
|
-
// ── the agent talking to us ──
|
|
36723
|
-
handleNotification(msg) {
|
|
36724
|
-
if (msg.method !== "session/update") return none;
|
|
36725
|
-
const update = msg.params?.update;
|
|
36726
|
-
switch (update?.sessionUpdate) {
|
|
36727
|
-
case "agent_message_chunk":
|
|
36728
|
-
this.text += update.content?.text ?? "";
|
|
36729
|
-
return none;
|
|
36730
|
-
case "tool_call": {
|
|
36731
|
-
const title = update.title?.trim() || update.kind || "tool";
|
|
36732
|
-
this.tools.push(title);
|
|
36733
|
-
const note = this.text.slice(this.reportFrom).trim();
|
|
36734
|
-
this.reportFrom = this.text.length;
|
|
36735
|
-
return { events: [{ kind: "work", tool: title, ...note ? { note } : {} }], writes: [] };
|
|
36736
|
-
}
|
|
36737
|
-
default:
|
|
36738
|
-
return none;
|
|
36739
|
-
}
|
|
36740
|
-
}
|
|
36741
|
-
handleRequest(msg) {
|
|
36742
|
-
if (msg.method !== "session/request_permission") {
|
|
36743
|
-
return {
|
|
36744
|
-
events: [],
|
|
36745
|
-
writes: [frame({ id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } })]
|
|
36746
|
-
};
|
|
36747
|
-
}
|
|
36748
|
-
const id = msg.id;
|
|
36749
|
-
const options = msg.params?.options ?? [];
|
|
36750
|
-
const title = msg.params?.toolCall?.title?.trim() || "a tool call";
|
|
36751
|
-
const tool = msg.params?.toolCall?.kind ?? "tool";
|
|
36752
|
-
if (this.mode === "bypass") {
|
|
36753
|
-
const optionId = optionFor(options, { allow: true }) ?? optionFor(options, { allow: false });
|
|
36754
|
-
return {
|
|
36755
|
-
// The decision still goes through Paigy — as history, not a question. Bypass
|
|
36756
|
-
// means "don't stall the agent", never "don't tell the user".
|
|
36757
|
-
events: [{ kind: "turn", role: "agent", text: `auto-approved: ${title}`, tools: [tool] }],
|
|
36758
|
-
writes: [
|
|
36759
|
-
optionId ? frame({ id, result: { outcome: { outcome: "selected", optionId } } }) : frame({ id, result: { outcome: { outcome: "cancelled" } } })
|
|
36760
|
-
]
|
|
36761
|
-
};
|
|
36762
|
-
}
|
|
36763
|
-
this.pending.set(String(id), { id, options });
|
|
36764
|
-
return {
|
|
36765
|
-
events: [{ kind: "permission", id: String(id), tool, summary: title }],
|
|
36766
|
-
writes: []
|
|
36767
|
-
};
|
|
36768
|
-
}
|
|
36769
|
-
/** Send the queued prompts as one turn, if the agent can take one right now. */
|
|
36770
|
-
flush() {
|
|
36771
|
-
if (!this.sessionId || this.promptId !== void 0 || !this.queued.length) return [];
|
|
36772
|
-
const blocks = this.queued.map((text) => ({ type: "text", text }));
|
|
36773
|
-
this.queued = [];
|
|
36774
|
-
this.promptId = this.nextId++;
|
|
36775
|
-
return [
|
|
36776
|
-
frame({
|
|
36777
|
-
id: this.promptId,
|
|
36778
|
-
method: "session/prompt",
|
|
36779
|
-
params: { sessionId: this.sessionId, prompt: blocks }
|
|
36780
|
-
})
|
|
36781
|
-
];
|
|
36401
|
+
async function finish(result, state, rail) {
|
|
36402
|
+
const goal = state.goal;
|
|
36403
|
+
if (!goal) return;
|
|
36404
|
+
state.goal = void 0;
|
|
36405
|
+
const report = result?.trim() || "Done.";
|
|
36406
|
+
try {
|
|
36407
|
+
await rail.contact({ goalIds: [goal.id], ask: report, waiting: "none", channel: "notification" });
|
|
36408
|
+
const { revision } = await rail.read(goal.id);
|
|
36409
|
+
await rail.update(goal.id, { revision: revision ?? goal.revision, changes: { state: "done", progress: report.slice(0, 1e4) }, reason: "The agent finished its turn without asking anything." });
|
|
36410
|
+
} catch {
|
|
36782
36411
|
}
|
|
36783
|
-
};
|
|
36784
|
-
var frame = (body) => JSON.stringify({ jsonrpc: "2.0", ...body });
|
|
36785
|
-
|
|
36786
|
-
// src/harness/session.ts
|
|
36787
|
-
init_catalog();
|
|
36788
|
-
var ADAPTER_BIN = {
|
|
36789
|
-
claude: "claude-agent-acp",
|
|
36790
|
-
codex: "codex-acp",
|
|
36791
|
-
agy: "agy"
|
|
36792
|
-
};
|
|
36793
|
-
function splitLines(buffer, chunk) {
|
|
36794
|
-
const combined = buffer + chunk;
|
|
36795
|
-
const parts = combined.split("\n");
|
|
36796
|
-
const rest = parts.pop() ?? "";
|
|
36797
|
-
return { lines: parts.filter((l) => l.trim()), rest };
|
|
36798
36412
|
}
|
|
36799
|
-
function
|
|
36800
|
-
|
|
36801
|
-
|
|
36802
|
-
|
|
36413
|
+
async function afterTurn(event, state, rail) {
|
|
36414
|
+
if (!event.failed && !endsWithQuestion(event.result)) {
|
|
36415
|
+
await finish(event.result, state, rail);
|
|
36416
|
+
state.resting = true;
|
|
36417
|
+
return null;
|
|
36803
36418
|
}
|
|
36804
|
-
const
|
|
36805
|
-
cwd,
|
|
36806
|
-
// The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI- or
|
|
36807
|
-
// launchd-launched process's PATH won't have it — probe dirs plus the RUNNING
|
|
36808
|
-
// node's own bin dir (nvm installs the adapters next to node; launchd's bare
|
|
36809
|
-
// PATH knows neither — live catch 2026-08-04: the first slot-wake ENOENT'd).
|
|
36810
|
-
env: {
|
|
36811
|
-
...process.env,
|
|
36812
|
-
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
36813
|
-
...opts.token ? { PAIGY_TOKEN: opts.token } : {}
|
|
36814
|
-
},
|
|
36815
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
36816
|
-
});
|
|
36817
|
-
const write = (frames) => {
|
|
36818
|
-
for (const f of frames) child.stdin?.write(`${f}
|
|
36819
|
-
`);
|
|
36820
|
-
};
|
|
36821
|
-
child.stderr?.on("data", (chunk) => {
|
|
36822
|
-
const text = String(chunk).trim();
|
|
36823
|
-
if (text) opts.onEvent({ kind: "error", message: text });
|
|
36824
|
-
});
|
|
36825
|
-
child.on("exit", (code) => {
|
|
36826
|
-
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
36827
|
-
opts.onExit?.();
|
|
36828
|
-
});
|
|
36829
|
-
child.on("error", (e) => {
|
|
36830
|
-
opts.onEvent({ kind: "error", message: e.message });
|
|
36831
|
-
opts.onExit?.();
|
|
36832
|
-
});
|
|
36833
|
-
const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.mcp ? { mcp: opts.mcp } : {} });
|
|
36834
|
-
let buffer = "";
|
|
36835
|
-
child.stdout?.on("data", (chunk) => {
|
|
36836
|
-
const { lines, rest } = splitLines(buffer, String(chunk));
|
|
36837
|
-
buffer = rest;
|
|
36838
|
-
for (const line of lines) {
|
|
36839
|
-
const { events, writes } = driver.handleLine(line);
|
|
36840
|
-
write(writes);
|
|
36841
|
-
for (const event of events) opts.onEvent(event);
|
|
36842
|
-
}
|
|
36843
|
-
});
|
|
36844
|
-
write(driver.open());
|
|
36845
|
-
return {
|
|
36846
|
-
send(text) {
|
|
36847
|
-
write(driver.send(text));
|
|
36848
|
-
},
|
|
36849
|
-
respond(id, decision) {
|
|
36850
|
-
write(driver.respond(id, decision));
|
|
36851
|
-
},
|
|
36852
|
-
stop() {
|
|
36853
|
-
write(driver.close());
|
|
36854
|
-
child.kill();
|
|
36855
|
-
}
|
|
36856
|
-
};
|
|
36857
|
-
}
|
|
36419
|
+
const reply = await askQuestion(event.failed ? `The agent stopped without finishing.
|
|
36858
36420
|
|
|
36859
|
-
|
|
36860
|
-
|
|
36861
|
-
return
|
|
36862
|
-
const parts = p.split("/").filter(Boolean);
|
|
36863
|
-
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
36864
|
-
});
|
|
36865
|
-
}
|
|
36866
|
-
function workLine(event) {
|
|
36867
|
-
const note = (event.note ?? "").split("\n").find((l) => l.trim()) ?? "";
|
|
36868
|
-
const line = shortenPaths([event.tool.trim(), note.trim()].filter(Boolean).join(" \u2014 ").replace(/\s+/g, " "));
|
|
36869
|
-
return line.length > ACTIVITY_LINE_MAX2 ? `${line.slice(0, ACTIVITY_LINE_MAX2 - 1)}\u2026` : line;
|
|
36870
|
-
}
|
|
36871
|
-
function pushWork(lines, line) {
|
|
36872
|
-
if (!line || lines[lines.length - 1] === line) return [...lines];
|
|
36873
|
-
return [...lines, line].slice(-ACTIVITY_LINES2);
|
|
36874
|
-
}
|
|
36875
|
-
function sameTail(a, b) {
|
|
36876
|
-
return a.length === b.length && a.every((l, i) => l === b[i]);
|
|
36421
|
+
${event.result?.trim() || "No result reported."}` : event.result ?? "", state, rail);
|
|
36422
|
+
if (!reply) state.resting = true;
|
|
36423
|
+
return reply;
|
|
36877
36424
|
}
|
|
36878
36425
|
|
|
36879
36426
|
// src/run.ts
|
|
36880
36427
|
function runHarness(opts) {
|
|
36881
36428
|
let running = true;
|
|
36882
36429
|
let tail = [];
|
|
36883
|
-
const state = {
|
|
36430
|
+
const state = { resting: true };
|
|
36884
36431
|
let session = null;
|
|
36885
|
-
const
|
|
36886
|
-
const deps = {
|
|
36887
|
-
submit: (req) => submitNotification(req, asMe),
|
|
36888
|
-
check: () => checkReplies(asMe),
|
|
36889
|
-
ack: (id, st) => setTaskState(id, st, asMe)
|
|
36890
|
-
};
|
|
36432
|
+
const rail = railFor({ token: opts.token });
|
|
36891
36433
|
async function handle(event) {
|
|
36892
36434
|
if (event.kind === "error") {
|
|
36893
36435
|
opts.log(`\u26A0 ${event.message}`);
|
|
@@ -36901,8 +36443,7 @@ function runHarness(opts) {
|
|
|
36901
36443
|
return;
|
|
36902
36444
|
}
|
|
36903
36445
|
opts.log(`\u23F8 blocked: ${event.summary} \u2014 asking your phone`);
|
|
36904
|
-
const
|
|
36905
|
-
state.parentId ??= parentId2;
|
|
36446
|
+
const decision = await askPermission(event, state, rail);
|
|
36906
36447
|
session?.respond(event.id, decision);
|
|
36907
36448
|
opts.log(decision.allow ? `\u2713 approved: ${event.summary}` : `\u2717 denied: ${event.summary}`);
|
|
36908
36449
|
return;
|
|
@@ -36912,41 +36453,39 @@ function runHarness(opts) {
|
|
|
36912
36453
|
tail = pushWork(tail, workLine(event));
|
|
36913
36454
|
return;
|
|
36914
36455
|
}
|
|
36915
|
-
const { parentId } = await mirror(event, state, deps);
|
|
36916
|
-
state.parentId ??= parentId;
|
|
36917
36456
|
if (event.kind === "turn") opts.log(`${event.role}: ${event.text.split("\n")[0] ?? ""}`);
|
|
36918
36457
|
if (event.kind === "idle") {
|
|
36919
|
-
state.resting = true;
|
|
36920
36458
|
tail = [];
|
|
36921
36459
|
if (endsWithQuestion(event.result)) {
|
|
36922
36460
|
const local = await opts.localAsk?.question?.(event.result ?? "") ?? null;
|
|
36923
36461
|
if (local?.trim() && session && running) {
|
|
36924
36462
|
opts.log(`you (here): ${local.split("\n")[0] ?? ""}`);
|
|
36463
|
+
state.resting = false;
|
|
36925
36464
|
session.send(local);
|
|
36926
36465
|
return;
|
|
36927
36466
|
}
|
|
36928
36467
|
}
|
|
36929
|
-
const reply = await
|
|
36468
|
+
const reply = await afterTurn(event, state, rail);
|
|
36930
36469
|
if (reply && session && running) {
|
|
36931
36470
|
opts.log(`you: ${reply.split("\n")[0] ?? ""}`);
|
|
36932
36471
|
session.send(reply);
|
|
36933
36472
|
}
|
|
36934
36473
|
}
|
|
36935
36474
|
}
|
|
36936
|
-
const paigyMcp = opts.exclusive && opts.token ? [{ name: "paigy", command: "npx", args: ["-y", "@paigy/mcp@latest"], env: { PAIGY_TOKEN: opts.token } }] : void 0;
|
|
36937
|
-
if (paigyMcp) state.sharedWithAgent = true;
|
|
36938
36475
|
session = startSession({
|
|
36939
36476
|
harness: opts.harness,
|
|
36940
36477
|
cwd: opts.cwd,
|
|
36941
36478
|
mode: opts.mode,
|
|
36942
|
-
...paigyMcp ? { mcp: paigyMcp } : {},
|
|
36943
36479
|
...opts.token ? { token: opts.token } : {},
|
|
36944
36480
|
...opts.bin ? { bin: opts.bin } : {},
|
|
36945
36481
|
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
36946
36482
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
36947
36483
|
});
|
|
36948
|
-
if (opts.prompt)
|
|
36949
|
-
|
|
36484
|
+
if (opts.prompt) {
|
|
36485
|
+
state.resting = false;
|
|
36486
|
+
session.send(opts.prompt);
|
|
36487
|
+
}
|
|
36488
|
+
void pump(session, state, rail, { onIdle: () => running }).catch(
|
|
36950
36489
|
(e) => opts.log(`\u26A0 conversation loop stopped: ${e.message}`)
|
|
36951
36490
|
);
|
|
36952
36491
|
opts.log(`\u25B6 started ${opts.harness} in ${opts.cwd} (${opts.mode} mode)`);
|
|
@@ -36976,7 +36515,7 @@ import { join as join4 } from "path";
|
|
|
36976
36515
|
|
|
36977
36516
|
// src/workspaces.ts
|
|
36978
36517
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
36979
|
-
import { dirname as
|
|
36518
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
36980
36519
|
import { homedir as homedir4 } from "os";
|
|
36981
36520
|
function workspacesFile() {
|
|
36982
36521
|
return join3(homedir4(), ".paigy", "workspaces.json");
|
|
@@ -36994,7 +36533,7 @@ function addWorkspace(dir, deps) {
|
|
|
36994
36533
|
const all = listWorkspaces(deps);
|
|
36995
36534
|
if (!all.includes(next)) {
|
|
36996
36535
|
all.push(next);
|
|
36997
|
-
mkdirSync2(
|
|
36536
|
+
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
36998
36537
|
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
36999
36538
|
}
|
|
37000
36539
|
return all;
|
|
@@ -37013,21 +36552,17 @@ function resolveWakeDir(pinned, deps) {
|
|
|
37013
36552
|
|
|
37014
36553
|
// src/host.ts
|
|
37015
36554
|
var HOST_FILE = join4(homedir5(), ".paigy", "host.json");
|
|
37016
|
-
var IDLE_WAKE_MS = 10 * 6e4;
|
|
37017
36555
|
var runKey = (slot) => `slot:${slot}`;
|
|
37018
36556
|
var claimedRun = (sessionId2) => {
|
|
37019
36557
|
const slot = sessionSlot(sessionId2);
|
|
37020
36558
|
return { slot, key: runKey(slot) };
|
|
37021
36559
|
};
|
|
37022
36560
|
async function slotHasWaitingWork(token) {
|
|
37023
|
-
return (await
|
|
36561
|
+
return (await checkReplies({ token })).deliveries.length > 0;
|
|
37024
36562
|
}
|
|
37025
36563
|
var PRESENCE_FRESH_MS = 3 * 6e4;
|
|
37026
|
-
function wakeAction(live,
|
|
37027
|
-
if (live)
|
|
37028
|
-
if (seenAt === void 0) return "skip";
|
|
37029
|
-
return now - seenAt >= IDLE_WAKE_MS ? "prompt" : "skip";
|
|
37030
|
-
}
|
|
36564
|
+
function wakeAction(live, now, serverSeenAt) {
|
|
36565
|
+
if (live) return "skip";
|
|
37031
36566
|
if (serverSeenAt !== void 0 && now - serverSeenAt < PRESENCE_FRESH_MS) return "skip";
|
|
37032
36567
|
return "spawn";
|
|
37033
36568
|
}
|
|
@@ -37055,6 +36590,8 @@ function startHost(opts) {
|
|
|
37055
36590
|
workspaces: listWorkspaces(opts.wsDeps)
|
|
37056
36591
|
}, asHost).catch(() => {
|
|
37057
36592
|
});
|
|
36593
|
+
void listenSlots().then(sweepSlots).catch(() => {
|
|
36594
|
+
});
|
|
37058
36595
|
};
|
|
37059
36596
|
async function claimAndSpawn() {
|
|
37060
36597
|
const specs = await claimSessions(asHost).catch(() => []);
|
|
@@ -37066,8 +36603,7 @@ function startHost(opts) {
|
|
|
37066
36603
|
harness: spec.harness,
|
|
37067
36604
|
cwd: spec.workspace,
|
|
37068
36605
|
mode: "bypass",
|
|
37069
|
-
prompt:
|
|
37070
|
-
exclusive: true,
|
|
36606
|
+
prompt: "",
|
|
37071
36607
|
token: spec.token,
|
|
37072
36608
|
onExit: () => {
|
|
37073
36609
|
runs.delete(key);
|
|
@@ -37087,45 +36623,39 @@ function startHost(opts) {
|
|
|
37087
36623
|
}
|
|
37088
36624
|
}
|
|
37089
36625
|
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex", antigravity: "agy" };
|
|
37090
|
-
|
|
37091
|
-
async function sweepSlots() {
|
|
36626
|
+
async function presenceMap() {
|
|
37092
36627
|
const presence = /* @__PURE__ */ new Map();
|
|
37093
36628
|
try {
|
|
37094
36629
|
for (const c of (await listConnections(asHost)).items) {
|
|
37095
36630
|
if (c.lastSeenAt) presence.set(c.id, Date.parse(c.lastSeenAt));
|
|
37096
36631
|
}
|
|
36632
|
+
lastPresence = presence;
|
|
37097
36633
|
} catch {
|
|
37098
36634
|
}
|
|
37099
|
-
|
|
36635
|
+
return presence;
|
|
36636
|
+
}
|
|
36637
|
+
let lastPresence = /* @__PURE__ */ new Map();
|
|
36638
|
+
async function wakeSlot(slot, presence) {
|
|
36639
|
+
{
|
|
37100
36640
|
const harness = SLOT_HARNESS[slot] ?? (slot === "Desktop" ? void 0 : "claude");
|
|
37101
36641
|
const key = runKey(slot);
|
|
37102
|
-
if (!harness)
|
|
36642
|
+
if (!harness) return;
|
|
37103
36643
|
const live = runs.get(key);
|
|
37104
36644
|
const tokenId = slotIdentity(slot).tokenId;
|
|
37105
36645
|
const seenByServer = tokenId ? presence.get(tokenId) : void 0;
|
|
37106
|
-
|
|
37107
|
-
if (action === "skip") continue;
|
|
36646
|
+
if (wakeAction(!!live, Date.now(), seenByServer) === "skip") return;
|
|
37108
36647
|
const token = readToken(slot);
|
|
37109
|
-
if (!token)
|
|
36648
|
+
if (!token) return;
|
|
37110
36649
|
const workspace = resolveWakeDir(slotIdentity(slot).workspace, opts.wsDeps);
|
|
37111
|
-
if (!workspace)
|
|
37112
|
-
if (!await slotHasWaitingWork(token).catch(() => false))
|
|
36650
|
+
if (!workspace) return;
|
|
36651
|
+
if (!await slotHasWaitingWork(token).catch(() => false)) return;
|
|
37113
36652
|
const label = slotName(slot) ?? slot;
|
|
37114
|
-
if (live) {
|
|
37115
|
-
const seen = published.get(key);
|
|
37116
|
-
const quiet = seen ? Math.round((Date.now() - seen.movedAt) / 6e4) : 0;
|
|
37117
|
-
live.run.send(wakePrompt(false));
|
|
37118
|
-
if (seen) seen.movedAt = Date.now();
|
|
37119
|
-
opts.log(`\u25B6 re-woke ${label} \u2014 quiet ${quiet}m with work waiting`);
|
|
37120
|
-
continue;
|
|
37121
|
-
}
|
|
37122
36653
|
const log = (line) => opts.log(`[${label}] ${line}`);
|
|
37123
36654
|
const run = runHarness({
|
|
37124
36655
|
harness,
|
|
37125
36656
|
cwd: workspace,
|
|
37126
36657
|
mode: "bypass",
|
|
37127
|
-
prompt:
|
|
37128
|
-
exclusive: true,
|
|
36658
|
+
prompt: "",
|
|
37129
36659
|
token,
|
|
37130
36660
|
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
37131
36661
|
onExit: () => {
|
|
@@ -37140,20 +36670,54 @@ function startHost(opts) {
|
|
|
37140
36670
|
opts.log(`\u25B6 woke ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
37141
36671
|
}
|
|
37142
36672
|
}
|
|
36673
|
+
const listening = /* @__PURE__ */ new Map();
|
|
36674
|
+
const joining = /* @__PURE__ */ new Set();
|
|
36675
|
+
async function listenSlots() {
|
|
36676
|
+
const want = new Set(listSlots().filter((slot) => slot !== "Desktop" && !!readToken(slot)));
|
|
36677
|
+
for (const [slot, sub] of [...listening]) {
|
|
36678
|
+
if (want.has(slot)) continue;
|
|
36679
|
+
listening.delete(slot);
|
|
36680
|
+
void sub.close().catch(() => {
|
|
36681
|
+
});
|
|
36682
|
+
}
|
|
36683
|
+
for (const slot of want) {
|
|
36684
|
+
if (listening.has(slot) || joining.has(slot)) continue;
|
|
36685
|
+
const token = readToken(slot);
|
|
36686
|
+
if (!token) continue;
|
|
36687
|
+
joining.add(slot);
|
|
36688
|
+
try {
|
|
36689
|
+
const sub = await subscribeWake(
|
|
36690
|
+
() => void presenceMap().then((presence) => wakeSlot(slot, presence)).catch(() => {
|
|
36691
|
+
}),
|
|
36692
|
+
{ token }
|
|
36693
|
+
);
|
|
36694
|
+
listening.set(slot, sub);
|
|
36695
|
+
opts.log(`\u25C9 listening for ${slotName(slot) ?? slot}`);
|
|
36696
|
+
} catch (e) {
|
|
36697
|
+
opts.log(`\u25CC not listening for ${slotName(slot) ?? slot} \u2014 ${e.message}`);
|
|
36698
|
+
} finally {
|
|
36699
|
+
joining.delete(slot);
|
|
36700
|
+
}
|
|
36701
|
+
}
|
|
36702
|
+
}
|
|
36703
|
+
async function sweepSlots() {
|
|
36704
|
+
const presence = await presenceMap();
|
|
36705
|
+
for (const slot of listSlots()) await wakeSlot(slot, presence);
|
|
36706
|
+
}
|
|
37143
36707
|
const ACTIVITY_MS = 2e3;
|
|
37144
36708
|
const published = /* @__PURE__ */ new Map();
|
|
37145
36709
|
const streamActivity = () => {
|
|
37146
|
-
const publishTail = (token,
|
|
37147
|
-
void heartbeat(void 0, { token, activity: { lines, at: (/* @__PURE__ */ new Date()).toISOString() } }).catch(() => {
|
|
36710
|
+
const publishTail = (token, lines2) => {
|
|
36711
|
+
void heartbeat(void 0, { token, activity: { lines: lines2, at: (/* @__PURE__ */ new Date()).toISOString() } }).catch(() => {
|
|
37148
36712
|
});
|
|
37149
36713
|
};
|
|
37150
36714
|
for (const [key, r] of runs) {
|
|
37151
36715
|
if (!r.token) continue;
|
|
37152
|
-
const
|
|
36716
|
+
const lines2 = r.run.tail();
|
|
37153
36717
|
const was = published.get(key);
|
|
37154
|
-
if (was && sameTail(was.lines,
|
|
37155
|
-
published.set(key, { token: r.token, lines
|
|
37156
|
-
publishTail(r.token,
|
|
36718
|
+
if (was && sameTail(was.lines, lines2)) continue;
|
|
36719
|
+
published.set(key, { token: r.token, lines: lines2 });
|
|
36720
|
+
publishTail(r.token, lines2);
|
|
37157
36721
|
}
|
|
37158
36722
|
for (const [key, was] of published) {
|
|
37159
36723
|
if (runs.has(key)) continue;
|
|
@@ -37171,7 +36735,6 @@ function startHost(opts) {
|
|
|
37171
36735
|
const pulse = setInterval(beat, 6e4);
|
|
37172
36736
|
const spawnPoll = setInterval(() => {
|
|
37173
36737
|
void claimAndSpawn();
|
|
37174
|
-
void sweepSlots();
|
|
37175
36738
|
publish();
|
|
37176
36739
|
}, 5e3);
|
|
37177
36740
|
const activityTick = setInterval(streamActivity, ACTIVITY_MS);
|
|
@@ -37182,6 +36745,12 @@ function startHost(opts) {
|
|
|
37182
36745
|
}
|
|
37183
36746
|
runs.clear();
|
|
37184
36747
|
};
|
|
36748
|
+
const stopListening = async () => {
|
|
36749
|
+
const subs = [...listening.values()];
|
|
36750
|
+
listening.clear();
|
|
36751
|
+
await Promise.all(subs.map((sub) => sub.close().catch(() => {
|
|
36752
|
+
})));
|
|
36753
|
+
};
|
|
37185
36754
|
const api = {
|
|
37186
36755
|
sessions: () => [...runs.entries()].map(([id, r]) => [id, r.label]),
|
|
37187
36756
|
// Every identity this machine holds, plus what it's doing right now. Slots are the
|
|
@@ -37202,7 +36771,16 @@ function startHost(opts) {
|
|
|
37202
36771
|
slot,
|
|
37203
36772
|
tokenId: id.tokenId,
|
|
37204
36773
|
voice: id.voice,
|
|
37205
|
-
|
|
36774
|
+
// RUNNING IS ABOUT THE IDENTITY, NOT ABOUT US (owner, 2026-09-14). This was
|
|
36775
|
+
// `live.has(name)` — "this host spawned it and it has not exited" — which is a fact
|
|
36776
|
+
// about our own process table, not about the agent. An ordinary terminal session
|
|
36777
|
+
// heartbeating every 60 s read as NOT running, and after a host restart so did the
|
|
36778
|
+
// sessions we started ourselves. The server already knows: `last_seen_at` on the
|
|
36779
|
+
// agent's own token, the same evidence `wakeAction` stands back for. So: a run we
|
|
36780
|
+
// hold, OR a process the server heard from inside `PRESENCE_FRESH_MS`.
|
|
36781
|
+
running: live.has(name) || !!id.tokenId && Date.now() - (lastPresence.get(id.tokenId) ?? 0) < PRESENCE_FRESH_MS,
|
|
36782
|
+
// `working` stays ours alone — it means a run WE hold is mid-turn, which is the only
|
|
36783
|
+
// thing a tail can tell us. A process we did not spawn has no tail here to read.
|
|
37206
36784
|
working: live.get(name)?.working() ?? false
|
|
37207
36785
|
};
|
|
37208
36786
|
});
|
|
@@ -37212,6 +36790,7 @@ function startHost(opts) {
|
|
|
37212
36790
|
clearInterval(pulse);
|
|
37213
36791
|
clearInterval(spawnPoll);
|
|
37214
36792
|
stopSessions();
|
|
36793
|
+
void stopListening();
|
|
37215
36794
|
streamActivity();
|
|
37216
36795
|
clearInterval(activityTick);
|
|
37217
36796
|
try {
|
|
@@ -37221,6 +36800,8 @@ function startHost(opts) {
|
|
|
37221
36800
|
}
|
|
37222
36801
|
};
|
|
37223
36802
|
publish();
|
|
36803
|
+
void listenSlots().then(sweepSlots).catch(() => {
|
|
36804
|
+
});
|
|
37224
36805
|
return api;
|
|
37225
36806
|
}
|
|
37226
36807
|
|
|
@@ -37243,7 +36824,7 @@ function bridgesToInstall(adapters) {
|
|
|
37243
36824
|
function startBridgeInstalls(bridges, deps = {}) {
|
|
37244
36825
|
const spawn2 = deps.spawn ?? shSpawner;
|
|
37245
36826
|
const boundMs = deps.boundMs ?? BRIDGE_BOUND_MS;
|
|
37246
|
-
const
|
|
36827
|
+
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
37247
36828
|
const running = bridges.map((b) => {
|
|
37248
36829
|
let proc = null;
|
|
37249
36830
|
let outcome;
|
|
@@ -37262,7 +36843,7 @@ function startBridgeInstalls(bridges, deps = {}) {
|
|
|
37262
36843
|
started: running.length,
|
|
37263
36844
|
wait: async () => {
|
|
37264
36845
|
let timedOut = false;
|
|
37265
|
-
const bound =
|
|
36846
|
+
const bound = sleep3(boundMs).then(() => {
|
|
37266
36847
|
timedOut = true;
|
|
37267
36848
|
});
|
|
37268
36849
|
await Promise.race([Promise.all(running.map((r) => r.outcome)), bound]);
|
|
@@ -37601,7 +37182,6 @@ async function main() {
|
|
|
37601
37182
|
if (state && state.status !== "ready") {
|
|
37602
37183
|
const hint = state.hint ?? "setup incomplete";
|
|
37603
37184
|
console.error(`\u270B ${state.label}: ${hint}`);
|
|
37604
|
-
await nudgeSetup(state.label, hint);
|
|
37605
37185
|
process.exit(1);
|
|
37606
37186
|
}
|
|
37607
37187
|
const askTerminal = async (promptText) => {
|
|
@@ -37626,9 +37206,7 @@ async function main() {
|
|
|
37626
37206
|
mode: parsed.mode,
|
|
37627
37207
|
prompt: parsed.prompt,
|
|
37628
37208
|
...state?.bin ? { bin: state.bin } : {},
|
|
37629
|
-
|
|
37630
|
-
// phone-launched sessions (the opening-move rule).
|
|
37631
|
-
...identityToken ? { exclusive: true, token: identityToken } : {},
|
|
37209
|
+
...identityToken ? { token: identityToken } : {},
|
|
37632
37210
|
localAsk: {
|
|
37633
37211
|
permission: async (summary) => {
|
|
37634
37212
|
const said = await askTerminal(`\u23F8 blocked: ${summary} \u2014 allow? [y/n]`);
|