agenthub-multiagent-mcp 1.60.0 → 1.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/brain/memvidBrain.d.ts +22 -0
- package/dist/brain/memvidBrain.d.ts.map +1 -1
- package/dist/brain/memvidBrain.js +41 -2
- package/dist/brain/memvidBrain.js.map +1 -1
- package/dist/brain/memvidBrain.test.js +90 -0
- package/dist/brain/memvidBrain.test.js.map +1 -1
- package/dist/brain/migrate.d.ts +43 -0
- package/dist/brain/migrate.d.ts.map +1 -1
- package/dist/brain/migrate.js +148 -23
- package/dist/brain/migrate.js.map +1 -1
- package/dist/brain/migrate.test.js +194 -1
- package/dist/brain/migrate.test.js.map +1 -1
- package/dist/channel.js +48 -6
- package/dist/channel.js.map +1 -1
- package/dist/channelDiagnostics.d.ts +53 -0
- package/dist/channelDiagnostics.d.ts.map +1 -1
- package/dist/channelDiagnostics.js +91 -0
- package/dist/channelDiagnostics.js.map +1 -1
- package/dist/channelDiagnostics.test.js +109 -1
- package/dist/channelDiagnostics.test.js.map +1 -1
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -0
- package/dist/client.js.map +1 -1
- package/dist/daemon.js +16 -22
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +36 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/pending-items.d.ts +63 -0
- package/dist/lib/pending-items.d.ts.map +1 -0
- package/dist/lib/pending-items.js +96 -0
- package/dist/lib/pending-items.js.map +1 -0
- package/dist/pinSweep.d.ts +83 -0
- package/dist/pinSweep.d.ts.map +1 -0
- package/dist/pinSweep.js +105 -0
- package/dist/pinSweep.js.map +1 -0
- package/dist/pinSweep.test.d.ts +12 -0
- package/dist/pinSweep.test.d.ts.map +1 -0
- package/dist/pinSweep.test.js +110 -0
- package/dist/pinSweep.test.js.map +1 -0
- package/dist/pinSweepCli.d.ts +20 -0
- package/dist/pinSweepCli.d.ts.map +1 -0
- package/dist/pinSweepCli.js +287 -0
- package/dist/pinSweepCli.js.map +1 -0
- package/dist/run-claude.sh +36 -5
- package/dist/sessionCapture.d.ts.map +1 -1
- package/dist/sessionCapture.js +7 -1
- package/dist/sessionCapture.js.map +1 -1
- package/dist/sessionCapture.test.js +20 -0
- package/dist/sessionCapture.test.js.map +1 -1
- package/dist/setup.js +11 -0
- package/dist/setup.js.map +1 -1
- package/dist/shiftFiles.d.ts +33 -0
- package/dist/shiftFiles.d.ts.map +1 -0
- package/dist/shiftFiles.js +57 -0
- package/dist/shiftFiles.js.map +1 -0
- package/dist/shiftFiles.test.d.ts +2 -0
- package/dist/shiftFiles.test.d.ts.map +1 -0
- package/dist/shiftFiles.test.js +141 -0
- package/dist/shiftFiles.test.js.map +1 -0
- package/dist/skillInstaller.d.ts.map +1 -1
- package/dist/skillInstaller.gate.test.js +2 -0
- package/dist/skillInstaller.gate.test.js.map +1 -1
- package/dist/skillInstaller.js +8 -0
- package/dist/skillInstaller.js.map +1 -1
- package/dist/skillInstaller.test.js +8 -1
- package/dist/skillInstaller.test.js.map +1 -1
- package/dist/tools/goshtiRead.d.ts +72 -0
- package/dist/tools/goshtiRead.d.ts.map +1 -0
- package/dist/tools/goshtiRead.js +274 -0
- package/dist/tools/goshtiRead.js.map +1 -0
- package/dist/tools/goshtiRead.test.d.ts +8 -0
- package/dist/tools/goshtiRead.test.d.ts.map +1 -0
- package/dist/tools/goshtiRead.test.js +182 -0
- package/dist/tools/goshtiRead.test.js.map +1 -0
- package/dist/tools/inboxBounds.d.ts +80 -0
- package/dist/tools/inboxBounds.d.ts.map +1 -0
- package/dist/tools/inboxBounds.js +105 -0
- package/dist/tools/inboxBounds.js.map +1 -0
- package/dist/tools/inboxBounds.test.d.ts +15 -0
- package/dist/tools/inboxBounds.test.d.ts.map +1 -0
- package/dist/tools/inboxBounds.test.js +135 -0
- package/dist/tools/inboxBounds.test.js.map +1 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +79 -18
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/intakeRequests.d.ts +35 -0
- package/dist/tools/intakeRequests.d.ts.map +1 -0
- package/dist/tools/intakeRequests.js +344 -0
- package/dist/tools/intakeRequests.js.map +1 -0
- package/dist/tools/intakeRequests.test.d.ts +8 -0
- package/dist/tools/intakeRequests.test.d.ts.map +1 -0
- package/dist/tools/intakeRequests.test.js +183 -0
- package/dist/tools/intakeRequests.test.js.map +1 -0
- package/dist/tools/pushedItemsSafety.test.d.ts +12 -0
- package/dist/tools/pushedItemsSafety.test.d.ts.map +1 -0
- package/dist/tools/pushedItemsSafety.test.js +179 -0
- package/dist/tools/pushedItemsSafety.test.js.map +1 -0
- package/dist/tools/tools.test.js +17 -5
- package/dist/tools/tools.test.js.map +1 -1
- package/dist/tools/wrapperCost.test.d.ts +13 -0
- package/dist/tools/wrapperCost.test.d.ts.map +1 -0
- package/dist/tools/wrapperCost.test.js +149 -0
- package/dist/tools/wrapperCost.test.js.map +1 -0
- package/dist/websocket.d.ts +30 -1
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +40 -3
- package/dist/websocket.js.map +1 -1
- package/dist/websocketQueue.test.d.ts +10 -0
- package/dist/websocketQueue.test.d.ts.map +1 -0
- package/dist/websocketQueue.test.js +78 -0
- package/dist/websocketQueue.test.js.map +1 -0
- package/package.json +4 -4
- package/skills/catalog.json +23 -10
- package/skills/commands/close-session.md +144 -144
- package/skills/commands/start-session.md +25 -6
- package/skills/manifest.json +8 -1
- package/skills/skills/code-brain-hook/SKILL.md +61 -61
- package/skills/skills/file-bug/SKILL.md +75 -75
- package/skills/skills/inbox/SKILL.md +99 -0
- package/skills/skills/multiagent-brainstorm/SKILL.md +164 -164
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds for the check_inbox payload (fix-inbox-delivery-and-tool-wrapping §1).
|
|
3
|
+
*
|
|
4
|
+
* check_inbox used to pass no `limit`, so the server applied its own default of
|
|
5
|
+
* 50 messages with full bodies and nothing truncated anywhere in the path. One
|
|
6
|
+
* such call measured 115,769 characters across 787 lines, exceeded the
|
|
7
|
+
* tool-result token budget, and never reached the model — the harness spilled it
|
|
8
|
+
* to a file. From the model's seat an unusable result is indistinguishable from
|
|
9
|
+
* a hang, which is how it was first reported.
|
|
10
|
+
*
|
|
11
|
+
* The cap alone would not be a fix. A caller that silently receives 10 of 40
|
|
12
|
+
* messages has no way to know, and the loss happened precisely because a
|
|
13
|
+
* slash-command bulk-marked read a result nobody had actually seen. So every
|
|
14
|
+
* bound here is paired with a signal: `truncated`, `omitted_count`, and a
|
|
15
|
+
* `_truncated` marker naming the original byte length on any clipped body.
|
|
16
|
+
*
|
|
17
|
+
* The rule that follows — never bulk-ack a truncated result — belongs to the
|
|
18
|
+
* caller, and is why the bundled `inbox` skill is part of this change.
|
|
19
|
+
*/
|
|
20
|
+
import type { Message } from "../client.js";
|
|
21
|
+
/** Messages returned when the caller names no limit. */
|
|
22
|
+
export declare const CHECK_INBOX_DEFAULT_LIMIT = 10;
|
|
23
|
+
/**
|
|
24
|
+
* Hard ceiling on messages per call, whatever the caller asks for. The server
|
|
25
|
+
* clamps too; this stops a client from making the request in the first place.
|
|
26
|
+
*/
|
|
27
|
+
export declare const CHECK_INBOX_MAX_LIMIT = 50;
|
|
28
|
+
/**
|
|
29
|
+
* Per-message body cap. Generous enough for a normal A2A message to arrive
|
|
30
|
+
* whole — the bodies that blew the budget were multi-kilobyte bug reports — and
|
|
31
|
+
* small enough that a full page of them cannot.
|
|
32
|
+
*/
|
|
33
|
+
export declare const CHECK_INBOX_BODY_CAP_BYTES = 4000;
|
|
34
|
+
/** A message body clipped by the cap, carrying its true length. */
|
|
35
|
+
export interface TruncationMarker {
|
|
36
|
+
original_bytes: number;
|
|
37
|
+
}
|
|
38
|
+
export interface BoundedInbox {
|
|
39
|
+
messages: (Message & {
|
|
40
|
+
_truncated?: TruncationMarker;
|
|
41
|
+
})[];
|
|
42
|
+
/** Total matching the query server-side, not the number returned. */
|
|
43
|
+
total: number;
|
|
44
|
+
returned: number;
|
|
45
|
+
omitted_count: number;
|
|
46
|
+
/** True when any message was withheld OR any body was clipped. */
|
|
47
|
+
truncated: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the limit to send to the server.
|
|
51
|
+
*
|
|
52
|
+
* A missing, non-numeric, zero or negative limit takes the default rather than
|
|
53
|
+
* meaning "unbounded" — "no limit" is the bug this exists to remove, and it must
|
|
54
|
+
* not be reachable by typo or by a caller passing 0. This mirrors
|
|
55
|
+
* `requestTimeoutMs`, where a malformed override also falls back rather than
|
|
56
|
+
* disabling the deadline.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveInboxLimit(requested?: unknown): number;
|
|
59
|
+
/**
|
|
60
|
+
* Bound an inbox response and declare what was withheld.
|
|
61
|
+
*
|
|
62
|
+
* `total` is the server's count for the query, which may exceed the rows
|
|
63
|
+
* returned; `omitted_count` is the difference. A clipped body also sets
|
|
64
|
+
* `truncated`, because a caller deciding whether it is safe to acknowledge
|
|
65
|
+
* these messages needs one flag that means "you did not get everything",
|
|
66
|
+
* not two it might check only one of.
|
|
67
|
+
*/
|
|
68
|
+
export declare function boundInbox(raw: {
|
|
69
|
+
messages?: Message[];
|
|
70
|
+
total?: number;
|
|
71
|
+
/**
|
|
72
|
+
* How many matched the query server-side, independent of the page size. The
|
|
73
|
+
* server's `total` has always meant "how many are in this response", so it
|
|
74
|
+
* cannot answer "what was withheld" — `matching_total` was added for that.
|
|
75
|
+
* Absent against an older server, in which case the omitted count collapses
|
|
76
|
+
* to 0 and only body clipping can set `truncated`.
|
|
77
|
+
*/
|
|
78
|
+
matching_total?: number;
|
|
79
|
+
}): BoundedInbox;
|
|
80
|
+
//# sourceMappingURL=inboxBounds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inboxBounds.d.ts","sourceRoot":"","sources":["../../src/tools/inboxBounds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,CAAC,OAAO,GAAG;QAAE,UAAU,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC,EAAE,CAAC;IAC1D,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAO7D;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE;IAC9B,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,YAAY,CAyBf"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds for the check_inbox payload (fix-inbox-delivery-and-tool-wrapping §1).
|
|
3
|
+
*
|
|
4
|
+
* check_inbox used to pass no `limit`, so the server applied its own default of
|
|
5
|
+
* 50 messages with full bodies and nothing truncated anywhere in the path. One
|
|
6
|
+
* such call measured 115,769 characters across 787 lines, exceeded the
|
|
7
|
+
* tool-result token budget, and never reached the model — the harness spilled it
|
|
8
|
+
* to a file. From the model's seat an unusable result is indistinguishable from
|
|
9
|
+
* a hang, which is how it was first reported.
|
|
10
|
+
*
|
|
11
|
+
* The cap alone would not be a fix. A caller that silently receives 10 of 40
|
|
12
|
+
* messages has no way to know, and the loss happened precisely because a
|
|
13
|
+
* slash-command bulk-marked read a result nobody had actually seen. So every
|
|
14
|
+
* bound here is paired with a signal: `truncated`, `omitted_count`, and a
|
|
15
|
+
* `_truncated` marker naming the original byte length on any clipped body.
|
|
16
|
+
*
|
|
17
|
+
* The rule that follows — never bulk-ack a truncated result — belongs to the
|
|
18
|
+
* caller, and is why the bundled `inbox` skill is part of this change.
|
|
19
|
+
*/
|
|
20
|
+
/** Messages returned when the caller names no limit. */
|
|
21
|
+
export const CHECK_INBOX_DEFAULT_LIMIT = 10;
|
|
22
|
+
/**
|
|
23
|
+
* Hard ceiling on messages per call, whatever the caller asks for. The server
|
|
24
|
+
* clamps too; this stops a client from making the request in the first place.
|
|
25
|
+
*/
|
|
26
|
+
export const CHECK_INBOX_MAX_LIMIT = 50;
|
|
27
|
+
/**
|
|
28
|
+
* Per-message body cap. Generous enough for a normal A2A message to arrive
|
|
29
|
+
* whole — the bodies that blew the budget were multi-kilobyte bug reports — and
|
|
30
|
+
* small enough that a full page of them cannot.
|
|
31
|
+
*/
|
|
32
|
+
export const CHECK_INBOX_BODY_CAP_BYTES = 4000;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the limit to send to the server.
|
|
35
|
+
*
|
|
36
|
+
* A missing, non-numeric, zero or negative limit takes the default rather than
|
|
37
|
+
* meaning "unbounded" — "no limit" is the bug this exists to remove, and it must
|
|
38
|
+
* not be reachable by typo or by a caller passing 0. This mirrors
|
|
39
|
+
* `requestTimeoutMs`, where a malformed override also falls back rather than
|
|
40
|
+
* disabling the deadline.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveInboxLimit(requested) {
|
|
43
|
+
const n = typeof requested === "number"
|
|
44
|
+
? requested
|
|
45
|
+
: Number.parseInt(String(requested ?? ""), 10);
|
|
46
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
47
|
+
return CHECK_INBOX_DEFAULT_LIMIT;
|
|
48
|
+
return Math.min(Math.trunc(n), CHECK_INBOX_MAX_LIMIT);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Apply the body cap to one message, attaching a marker when it bites.
|
|
52
|
+
*
|
|
53
|
+
* Measured in UTF-16 code units (`String.length`), matching how the payload is
|
|
54
|
+
* counted downstream. `original_bytes` keeps the field name the session-event
|
|
55
|
+
* truncation marker already uses, so there is one truncation idiom in the
|
|
56
|
+
* codebase rather than two.
|
|
57
|
+
*/
|
|
58
|
+
function capBody(msg) {
|
|
59
|
+
const body = typeof msg.body === "string" ? msg.body : "";
|
|
60
|
+
if (body.length <= CHECK_INBOX_BODY_CAP_BYTES)
|
|
61
|
+
return { msg, clipped: false };
|
|
62
|
+
return {
|
|
63
|
+
msg: {
|
|
64
|
+
...msg,
|
|
65
|
+
body: body.slice(0, CHECK_INBOX_BODY_CAP_BYTES),
|
|
66
|
+
_truncated: { original_bytes: body.length },
|
|
67
|
+
},
|
|
68
|
+
clipped: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Bound an inbox response and declare what was withheld.
|
|
73
|
+
*
|
|
74
|
+
* `total` is the server's count for the query, which may exceed the rows
|
|
75
|
+
* returned; `omitted_count` is the difference. A clipped body also sets
|
|
76
|
+
* `truncated`, because a caller deciding whether it is safe to acknowledge
|
|
77
|
+
* these messages needs one flag that means "you did not get everything",
|
|
78
|
+
* not two it might check only one of.
|
|
79
|
+
*/
|
|
80
|
+
export function boundInbox(raw) {
|
|
81
|
+
const messages = raw.messages ?? [];
|
|
82
|
+
let anyClipped = false;
|
|
83
|
+
const capped = messages.map((m) => {
|
|
84
|
+
const { msg, clipped } = capBody(m);
|
|
85
|
+
if (clipped)
|
|
86
|
+
anyClipped = true;
|
|
87
|
+
return msg;
|
|
88
|
+
});
|
|
89
|
+
const returned = capped.length;
|
|
90
|
+
// Prefer matching_total; fall back to total for an older server. Trust the
|
|
91
|
+
// larger of the two either way: a count below the rows actually sent is
|
|
92
|
+
// wrong, and a negative omitted count would read as "nothing withheld" — the
|
|
93
|
+
// one answer that must never be wrong in this direction.
|
|
94
|
+
const reported = raw.matching_total ?? raw.total ?? returned;
|
|
95
|
+
const total = Math.max(reported, returned);
|
|
96
|
+
const omitted = total - returned;
|
|
97
|
+
return {
|
|
98
|
+
messages: capped,
|
|
99
|
+
total,
|
|
100
|
+
returned,
|
|
101
|
+
omitted_count: omitted,
|
|
102
|
+
truncated: omitted > 0 || anyClipped,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=inboxBounds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inboxBounds.js","sourceRoot":"","sources":["../../src/tools/inboxBounds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,wDAAwD;AACxD,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAE5C;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAiB/C;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAmB;IACnD,MAAM,CAAC,GACL,OAAO,SAAS,KAAK,QAAQ;QAC3B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,yBAAyB,CAAC;IACpE,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CACd,GAAY;IAEZ,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,IAAI,IAAI,CAAC,MAAM,IAAI,0BAA0B;QAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9E,OAAO;QACL,GAAG,EAAE;YACH,GAAG,GAAG;YACN,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,0BAA0B,CAAC;YAC/C,UAAU,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,MAAM,EAAE;SAC5C;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,GAW1B;IACC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;IACpC,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAChC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,OAAO;YAAE,UAAU,GAAG,IAAI,CAAC;QAC/B,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/B,2EAA2E;IAC3E,wEAAwE;IACxE,6EAA6E;IAC7E,yDAAyD;IACzD,MAAM,QAAQ,GAAG,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,KAAK,IAAI,QAAQ,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;IAEjC,OAAO;QACL,QAAQ,EAAE,MAAM;QAChB,KAAK;QACL,QAAQ;QACR,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,OAAO,GAAG,CAAC,IAAI,UAAU;KACrC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fix-inbox-delivery-and-tool-wrapping §1 — check_inbox bounds its payload and
|
|
3
|
+
* declares what it withheld.
|
|
4
|
+
*
|
|
5
|
+
* Why this file exists: check_inbox passed no `limit`, so the server returned
|
|
6
|
+
* its default 50 messages with full bodies and nothing truncated anywhere. One
|
|
7
|
+
* such call measured 115,769 characters, exceeded the tool-result token budget
|
|
8
|
+
* and never reached the model. A slash-command then bulk-marked those messages
|
|
9
|
+
* read, and because `unread_only` only returns 'pending'/'delivered' rows they
|
|
10
|
+
* became invisible for good. Bounding the payload is half the fix; SAYING what
|
|
11
|
+
* was withheld is the other half, because a silent cap turns loud breakage into
|
|
12
|
+
* quiet breakage.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=inboxBounds.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inboxBounds.test.d.ts","sourceRoot":"","sources":["../../src/tools/inboxBounds.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fix-inbox-delivery-and-tool-wrapping §1 — check_inbox bounds its payload and
|
|
3
|
+
* declares what it withheld.
|
|
4
|
+
*
|
|
5
|
+
* Why this file exists: check_inbox passed no `limit`, so the server returned
|
|
6
|
+
* its default 50 messages with full bodies and nothing truncated anywhere. One
|
|
7
|
+
* such call measured 115,769 characters, exceeded the tool-result token budget
|
|
8
|
+
* and never reached the model. A slash-command then bulk-marked those messages
|
|
9
|
+
* read, and because `unread_only` only returns 'pending'/'delivered' rows they
|
|
10
|
+
* became invisible for good. Bounding the payload is half the fix; SAYING what
|
|
11
|
+
* was withheld is the other half, because a silent cap turns loud breakage into
|
|
12
|
+
* quiet breakage.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
15
|
+
import { handleToolCall } from "./index.js";
|
|
16
|
+
import { CHECK_INBOX_DEFAULT_LIMIT, CHECK_INBOX_MAX_LIMIT, CHECK_INBOX_BODY_CAP_BYTES, } from "./inboxBounds.js";
|
|
17
|
+
vi.mock("../state.js", () => ({
|
|
18
|
+
loadState: vi.fn(() => null),
|
|
19
|
+
saveState: vi.fn(),
|
|
20
|
+
deleteState: vi.fn(),
|
|
21
|
+
stateExists: vi.fn(() => false),
|
|
22
|
+
getCurrentOwner: vi.fn(() => "test-user"),
|
|
23
|
+
}));
|
|
24
|
+
vi.mock("../hooks/sessionState.js", async (importOriginal) => {
|
|
25
|
+
const actual = (await importOriginal());
|
|
26
|
+
return { ...actual, isEnabled: vi.fn(() => false) };
|
|
27
|
+
});
|
|
28
|
+
const mockClient = {
|
|
29
|
+
getBaseUrl: vi.fn(() => "http://localhost:8787/api"),
|
|
30
|
+
getInbox: vi.fn(),
|
|
31
|
+
getPendingTasks: vi.fn(),
|
|
32
|
+
};
|
|
33
|
+
/** n messages, each body `bodyBytes` long, newest first like GetInbox returns. */
|
|
34
|
+
function makeMessages(n, bodyBytes = 10) {
|
|
35
|
+
return Array.from({ length: n }, (_, i) => ({
|
|
36
|
+
id: `msg-${i}`,
|
|
37
|
+
from_agent: "sender",
|
|
38
|
+
to_agent: "test-agent",
|
|
39
|
+
broadcast: false,
|
|
40
|
+
type: "question",
|
|
41
|
+
subject: `subject ${i}`,
|
|
42
|
+
body: "x".repeat(bodyBytes),
|
|
43
|
+
status: "delivered",
|
|
44
|
+
priority: "normal",
|
|
45
|
+
created_at: new Date(Date.now() - i * 1000).toISOString(),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
describe("check_inbox payload bounds", () => {
|
|
49
|
+
let context;
|
|
50
|
+
let agentId;
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
vi.clearAllMocks();
|
|
53
|
+
agentId = "test-agent";
|
|
54
|
+
context = {
|
|
55
|
+
getCurrentAgentId: () => agentId,
|
|
56
|
+
setCurrentAgentId: (id) => {
|
|
57
|
+
agentId = id;
|
|
58
|
+
},
|
|
59
|
+
stopHeartbeat: () => { },
|
|
60
|
+
getWorkingDir: () => "/tmp",
|
|
61
|
+
setWorkingDir: () => { },
|
|
62
|
+
getPushedItems: () => ({ tasks: [], messages: [] }),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
it("applies a default limit and reports what it withheld", async () => {
|
|
66
|
+
// The regression: 40 unread messages, no limit given by the caller.
|
|
67
|
+
vi.mocked(mockClient.getInbox).mockResolvedValue({
|
|
68
|
+
messages: makeMessages(CHECK_INBOX_DEFAULT_LIMIT),
|
|
69
|
+
total: 40,
|
|
70
|
+
});
|
|
71
|
+
const result = (await handleToolCall("check_inbox", { unread_only: true }, mockClient, context));
|
|
72
|
+
// The limit must reach the client call — omitting it is the original bug.
|
|
73
|
+
expect(mockClient.getInbox).toHaveBeenCalledWith("test-agent", true, CHECK_INBOX_DEFAULT_LIMIT);
|
|
74
|
+
expect(result.total).toBe(40);
|
|
75
|
+
expect(result.returned).toBe(CHECK_INBOX_DEFAULT_LIMIT);
|
|
76
|
+
expect(result.omitted_count).toBe(40 - CHECK_INBOX_DEFAULT_LIMIT);
|
|
77
|
+
expect(result.truncated).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it("clips an oversized body and names its original length", async () => {
|
|
80
|
+
const original = CHECK_INBOX_BODY_CAP_BYTES + 1000;
|
|
81
|
+
vi.mocked(mockClient.getInbox).mockResolvedValue({
|
|
82
|
+
messages: makeMessages(1, original),
|
|
83
|
+
total: 1,
|
|
84
|
+
});
|
|
85
|
+
const result = (await handleToolCall("check_inbox", { unread_only: true }, mockClient, context));
|
|
86
|
+
const msg = result.messages[0];
|
|
87
|
+
expect(msg.body.length).toBeLessThanOrEqual(CHECK_INBOX_BODY_CAP_BYTES);
|
|
88
|
+
expect(msg._truncated).toEqual({ original_bytes: original });
|
|
89
|
+
// A clipped body is still a withheld thing: the caller must be told.
|
|
90
|
+
expect(result.truncated).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it("reports nothing withheld when nothing was withheld", async () => {
|
|
93
|
+
vi.mocked(mockClient.getInbox).mockResolvedValue({
|
|
94
|
+
messages: makeMessages(3),
|
|
95
|
+
total: 3,
|
|
96
|
+
});
|
|
97
|
+
const result = (await handleToolCall("check_inbox", { unread_only: true }, mockClient, context));
|
|
98
|
+
expect(result.truncated).toBe(false);
|
|
99
|
+
expect(result.omitted_count).toBe(0);
|
|
100
|
+
expect(result.returned).toBe(3);
|
|
101
|
+
for (const m of result.messages) {
|
|
102
|
+
expect(m._truncated).toBeUndefined();
|
|
103
|
+
expect(m.body.length).toBe(10);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
it("clamps a caller-supplied limit above the maximum", async () => {
|
|
107
|
+
vi.mocked(mockClient.getInbox).mockResolvedValue({
|
|
108
|
+
messages: makeMessages(CHECK_INBOX_MAX_LIMIT),
|
|
109
|
+
total: 500,
|
|
110
|
+
});
|
|
111
|
+
await handleToolCall("check_inbox", { unread_only: true, limit: 10_000 }, mockClient, context);
|
|
112
|
+
expect(mockClient.getInbox).toHaveBeenCalledWith("test-agent", true, CHECK_INBOX_MAX_LIMIT);
|
|
113
|
+
});
|
|
114
|
+
it("honours a caller-supplied limit below the maximum", async () => {
|
|
115
|
+
vi.mocked(mockClient.getInbox).mockResolvedValue({
|
|
116
|
+
messages: makeMessages(2),
|
|
117
|
+
total: 2,
|
|
118
|
+
});
|
|
119
|
+
await handleToolCall("check_inbox", { unread_only: true, limit: 2 }, mockClient, context);
|
|
120
|
+
expect(mockClient.getInbox).toHaveBeenCalledWith("test-agent", true, 2);
|
|
121
|
+
});
|
|
122
|
+
it("keeps the payload well under the budget for an inbox that once blew it", async () => {
|
|
123
|
+
// 787 rows averaging ~147 bytes of body is what produced the 115,769-char
|
|
124
|
+
// response observed on 2026-10-17. Same corpus, bounded output.
|
|
125
|
+
vi.mocked(mockClient.getInbox).mockResolvedValue({
|
|
126
|
+
messages: makeMessages(CHECK_INBOX_DEFAULT_LIMIT, 8000),
|
|
127
|
+
total: 787,
|
|
128
|
+
});
|
|
129
|
+
const result = (await handleToolCall("check_inbox", { unread_only: true }, mockClient, context));
|
|
130
|
+
expect(JSON.stringify(result).length).toBeLessThan(60_000);
|
|
131
|
+
expect(result.truncated).toBe(true);
|
|
132
|
+
expect(result.omitted_count).toBe(787 - CHECK_INBOX_DEFAULT_LIMIT);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=inboxBounds.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inboxBounds.test.js","sourceRoot":"","sources":["../../src/tools/inboxBounds.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAe,MAAM,YAAY,CAAC;AACzD,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAG1B,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;IAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;IAClB,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE;IACpB,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;IAC/B,eAAe,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC;CAC1C,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,0BAA0B,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IAC3D,MAAM,MAAM,GAAG,CAAC,MAAM,cAAc,EAAE,CAA4B,CAAC;IACnE,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG;IACjB,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,2BAA2B,CAAC;IACpD,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE;IACjB,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE;CACD,CAAC;AAE1B,kFAAkF;AAClF,SAAS,YAAY,CAAC,CAAS,EAAE,SAAS,GAAG,EAAE;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1C,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,YAAY;QACtB,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,UAAmB;QACzB,OAAO,EAAE,WAAW,CAAC,EAAE;QACvB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;QAC3B,MAAM,EAAE,WAAoB;QAC5B,QAAQ,EAAE,QAAiB;QAC3B,UAAU,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;KAC1D,CAAC,CAAC,CAAC;AACN,CAAC;AAED,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,IAAI,OAAoB,CAAC;IACzB,IAAI,OAAsB,CAAC;IAE3B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,GAAG,YAAY,CAAC;QACvB,OAAO,GAAG;YACR,iBAAiB,EAAE,GAAG,EAAE,CAAC,OAAO;YAChC,iBAAiB,EAAE,CAAC,EAAU,EAAE,EAAE;gBAChC,OAAO,GAAG,EAAE,CAAC;YACf,CAAC;YACD,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;YACvB,aAAa,EAAE,GAAG,EAAE,CAAC,MAAM;YAC3B,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;YACvB,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;SAC1B,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,oEAAoE;QACpE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC;YAC/C,QAAQ,EAAE,YAAY,CAAC,yBAAyB,CAAC;YACjD,KAAK,EAAE,EAAE;SACV,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,CAAC,MAAM,cAAc,CAClC,aAAa,EACb,EAAE,WAAW,EAAE,IAAI,EAAE,EACrB,UAAU,EACV,OAAO,CACR,CAA4B,CAAC;QAE9B,0EAA0E;QAC1E,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAC9C,YAAY,EACZ,IAAI,EACJ,yBAAyB,CAC1B,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,yBAAyB,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,QAAQ,GAAG,0BAA0B,GAAG,IAAI,CAAC;QACnD,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC;YAC/C,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC;YACnC,KAAK,EAAE,CAAC;SACT,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,CAAC,MAAM,cAAc,CAClC,aAAa,EACb,EAAE,WAAW,EAAE,IAAI,EAAE,EACrB,UAAU,EACV,OAAO,CACR,CAA4B,CAAC;QAE9B,MAAM,GAAG,GAAI,MAAM,CAAC,QAAsC,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAE,GAAG,CAAC,IAAe,CAAC,MAAM,CAAC,CAAC,mBAAmB,CACrD,0BAA0B,CAC3B,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7D,qEAAqE;QACrE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC;YAC/C,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;YACzB,KAAK,EAAE,CAAC;SACT,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,CAAC,MAAM,cAAc,CAClC,aAAa,EACb,EAAE,WAAW,EAAE,IAAI,EAAE,EACrB,UAAU,EACV,OAAO,CACR,CAA4B,CAAC;QAE9B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAqC,EAAE,CAAC;YAC7D,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,CAAC;YACrC,MAAM,CAAE,CAAC,CAAC,IAAe,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC;YAC/C,QAAQ,EAAE,YAAY,CAAC,qBAAqB,CAAC;YAC7C,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;QAEH,MAAM,cAAc,CAClB,aAAa,EACb,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EACpC,UAAU,EACV,OAAO,CACR,CAAC;QAEF,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAC9C,YAAY,EACZ,IAAI,EACJ,qBAAqB,CACtB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC;YAC/C,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;YACzB,KAAK,EAAE,CAAC;SACT,CAAC,CAAC;QAEH,MAAM,cAAc,CAClB,aAAa,EACb,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAC/B,UAAU,EACV,OAAO,CACR,CAAC;QAEF,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,KAAK,IAAI,EAAE;QACtF,0EAA0E;QAC1E,gEAAgE;QAChE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC;YAC/C,QAAQ,EAAE,YAAY,CAAC,yBAAyB,EAAE,IAAI,CAAC;YACvD,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,CAAC,MAAM,cAAc,CAClC,aAAa,EACb,EAAE,WAAW,EAAE,IAAI,EAAE,EACrB,UAAU,EACV,OAAO,CACR,CAA4B,CAAC;QAE9B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,yBAAyB,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -13,7 +13,18 @@ export interface ToolContext {
|
|
|
13
13
|
stopHeartbeat: () => void;
|
|
14
14
|
getWorkingDir: () => string;
|
|
15
15
|
setWorkingDir: (dir: string) => void;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Destructive read; see peekPushedItems
|
|
18
|
+
* (fix-inbox-delivery-and-tool-wrapping §2). Kept so older callers compile.
|
|
19
|
+
*/
|
|
16
20
|
getPushedItems: () => PushedItems;
|
|
21
|
+
/** Read pushed items without consuming them. */
|
|
22
|
+
peekPushedItems?: () => PushedItems;
|
|
23
|
+
/** Consume exactly the items that made it into a built response. */
|
|
24
|
+
commitPushedItems?: (consumed: {
|
|
25
|
+
taskIds?: string[];
|
|
26
|
+
messageIds?: string[];
|
|
27
|
+
}) => void;
|
|
17
28
|
}
|
|
18
29
|
interface PendingTask {
|
|
19
30
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAqD,MAAM,cAAc,CAAC;AAOrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAqD,MAAM,cAAc,CAAC;AAOrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAkG/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,cAAc,EAAE,MAAM,WAAW,CAAC;IAClC,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,WAAW,CAAC;IACpC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE;QAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,KAAK,IAAI,CAAC;CACZ;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyLD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CAk2FtC;AA2BD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAyuFlB"}
|
package/dist/tools/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* MCP tool definitions and handlers
|
|
3
3
|
*/
|
|
4
4
|
import { resolveAgentIdentity, missingStateGuidance, credentialIsDead, } from "../agentIdentity.js";
|
|
5
|
+
import { boundInbox, resolveInboxLimit, CHECK_INBOX_DEFAULT_LIMIT, CHECK_INBOX_MAX_LIMIT, } from "./inboxBounds.js";
|
|
5
6
|
import * as state from "../state.js";
|
|
6
7
|
import * as contextModule from "../context.js";
|
|
7
8
|
import { hookManager } from "../hooks/index.js";
|
|
@@ -9,9 +10,12 @@ import * as brainCapture from "../hooks/brainCapture.js";
|
|
|
9
10
|
import { getBrain } from "../brain/backend.js";
|
|
10
11
|
import open from "open";
|
|
11
12
|
import { registerOpenSpecVizTools, handleOpenSpecVizToolCall } from "./openspec-viz/index.js";
|
|
13
|
+
import { registerIntakeRequestTools, handleIntakeRequestToolCall, isIntakeRequestTool, } from "./intakeRequests.js";
|
|
12
14
|
import { registerSessionControlTools, handleSessionControlToolCall, isSessionControlTool, } from "./sessionControl.js";
|
|
15
|
+
import { registerGoshtiReadTools, handleGoshtiReadToolCall, isGoshtiReadTool, } from "./goshtiRead.js";
|
|
13
16
|
import { installPackFromArchive } from "../skillPackInstaller.js";
|
|
14
17
|
import { getServerTime, useTimeInjection } from "../lib/server-time.js";
|
|
18
|
+
import { getPendingItems } from "../lib/pending-items.js";
|
|
15
19
|
import { ENG_IC_CHARTER } from "../employee.js";
|
|
16
20
|
import { promises as fsp } from "node:fs";
|
|
17
21
|
import * as os from "node:os";
|
|
@@ -78,16 +82,23 @@ async function wrapWithPendingItems(client, agentId, result, context) {
|
|
|
78
82
|
if (!agentId)
|
|
79
83
|
return result;
|
|
80
84
|
try {
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
// Read items pushed via WebSocket WITHOUT consuming them
|
|
86
|
+
// (fix-inbox-delivery-and-tool-wrapping §2.3). The old code drained the
|
|
87
|
+
// queue here, before the two fetches below — so a fetch that threw or timed
|
|
88
|
+
// out sent us to the catch with those messages already gone. They are
|
|
89
|
+
// consumed at the bottom of this function, once a response exists to carry
|
|
90
|
+
// them. peekPushedItems is optional on the context so older callers (and
|
|
91
|
+
// tests constructing a partial context) keep working.
|
|
92
|
+
const pushedItems = context?.peekPushedItems?.() ??
|
|
93
|
+
context?.getPushedItems?.() ?? { tasks: [], messages: [] };
|
|
94
|
+
// Read pending items from cache; never wait on the network
|
|
95
|
+
// (fix-inbox-delivery-and-tool-wrapping §3.2). This used to await two HTTP
|
|
96
|
+
// calls on every one of the 58 wrapped tools, each with a 45s deadline, so
|
|
97
|
+
// an unrelated tool could stall ~90s before doing its own work. A refresh
|
|
98
|
+
// now runs in the background and a cold cache simply carries nothing.
|
|
99
|
+
const cachedItems = getPendingItems(client, agentId);
|
|
100
|
+
const fetchedMessages = cachedItems.messages;
|
|
101
|
+
const fetchedTasks = cachedItems.tasks;
|
|
91
102
|
// Deduplicate messages
|
|
92
103
|
const messageIds = new Set(fetchedMessages.map((m) => m.id));
|
|
93
104
|
const messages = [
|
|
@@ -128,7 +139,7 @@ async function wrapWithPendingItems(client, agentId, result, context) {
|
|
|
128
139
|
urgentAction = {
|
|
129
140
|
required: true,
|
|
130
141
|
instruction: buildUrgentInstruction(urgentMsg),
|
|
131
|
-
|
|
142
|
+
message_id: urgentMsg.id,
|
|
132
143
|
};
|
|
133
144
|
}
|
|
134
145
|
}
|
|
@@ -148,10 +159,20 @@ async function wrapWithPendingItems(client, agentId, result, context) {
|
|
|
148
159
|
console.warn("server_time injection failed:", err);
|
|
149
160
|
}
|
|
150
161
|
}
|
|
162
|
+
// The response now exists and carries these items, so they may be consumed
|
|
163
|
+
// (§2.3). Only the ids we actually peeked are named: anything pushed since
|
|
164
|
+
// then stays queued for the next tool call rather than being cleared away
|
|
165
|
+
// unseen. Reaching the catch above consumes nothing, which is the whole
|
|
166
|
+
// point of the split.
|
|
167
|
+
context?.commitPushedItems?.({
|
|
168
|
+
taskIds: pushedItems.tasks.map((t) => t.id),
|
|
169
|
+
messageIds: pushedItems.messages.map((m) => m.id),
|
|
170
|
+
});
|
|
151
171
|
return wrapped;
|
|
152
172
|
}
|
|
153
173
|
catch (error) {
|
|
154
|
-
// Don't break tool call if fetch fails
|
|
174
|
+
// Don't break tool call if fetch fails. Pushed items were only peeked, so
|
|
175
|
+
// they are still queued and will be delivered by the next call.
|
|
155
176
|
console.warn("Failed to fetch pending items for wrapping:", error);
|
|
156
177
|
return result;
|
|
157
178
|
}
|
|
@@ -167,13 +188,16 @@ function isUrgentMessage(msg) {
|
|
|
167
188
|
* Build instruction text for urgent messages
|
|
168
189
|
*/
|
|
169
190
|
function buildUrgentInstruction(msg) {
|
|
191
|
+
// No body here (fix-inbox-delivery-and-tool-wrapping §3.4). Interpolating
|
|
192
|
+
// ${msg.body} made this the second of three copies of the same text in one
|
|
193
|
+
// response — the others being pending_messages[] and urgent_action.message.
|
|
194
|
+
// The instruction's job is to say what to do and which message to do it
|
|
195
|
+
// about; the body is one lookup away in pending_messages.
|
|
170
196
|
return `URGENT: Agent [${msg.from_agent}] needs immediate assistance.
|
|
171
197
|
|
|
172
198
|
Query: ${msg.subject}
|
|
173
199
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Spawn a subagent with Task tool to research and respond to this query. Use send_message to reply when done.`;
|
|
200
|
+
The full message is in this response's pending_messages[], id ${msg.id}. Read it there, then reply with the reply tool (message_id ${msg.id}).`;
|
|
177
201
|
}
|
|
178
202
|
/**
|
|
179
203
|
* Build instruction text for pending tasks
|
|
@@ -690,7 +714,12 @@ export function registerTools() {
|
|
|
690
714
|
},
|
|
691
715
|
{
|
|
692
716
|
name: "check_inbox",
|
|
693
|
-
description: "Check for incoming messages"
|
|
717
|
+
description: "Check for incoming messages. The response is bounded: it carries " +
|
|
718
|
+
"`total`, `returned`, `omitted_count` and `truncated`, and a clipped " +
|
|
719
|
+
"body carries `_truncated.original_bytes`. When `truncated` is true you " +
|
|
720
|
+
"did NOT receive everything — do not mark_read anything you did not " +
|
|
721
|
+
"actually read, or it becomes invisible to everyone (unread_only never " +
|
|
722
|
+
"returns a read message again). Page with `limit` instead.",
|
|
694
723
|
inputSchema: {
|
|
695
724
|
type: "object",
|
|
696
725
|
properties: {
|
|
@@ -698,6 +727,13 @@ export function registerTools() {
|
|
|
698
727
|
type: "boolean",
|
|
699
728
|
description: "Only return unread messages (default: false)",
|
|
700
729
|
},
|
|
730
|
+
limit: {
|
|
731
|
+
type: "number",
|
|
732
|
+
description: `Max messages to return (default: ${CHECK_INBOX_DEFAULT_LIMIT}, ` +
|
|
733
|
+
`max: ${CHECK_INBOX_MAX_LIMIT}). Values above the max are clamped; ` +
|
|
734
|
+
`0, negative and non-numeric values take the default — there is no ` +
|
|
735
|
+
`way to request an unbounded inbox.`,
|
|
736
|
+
},
|
|
701
737
|
},
|
|
702
738
|
},
|
|
703
739
|
},
|
|
@@ -2116,6 +2152,10 @@ export function registerTools() {
|
|
|
2116
2152
|
},
|
|
2117
2153
|
// Session steering + takeover (add-multiplayer-sessions §4.7, §5.9)
|
|
2118
2154
|
...registerSessionControlTools(),
|
|
2155
|
+
// Intake requests: file, triage, scope check (add-intake-requests §6.1)
|
|
2156
|
+
...registerIntakeRequestTools(),
|
|
2157
|
+
// Reading Goshti as the agent's owner (add-agent-goshti-read §3.1)
|
|
2158
|
+
...registerGoshtiReadTools(),
|
|
2119
2159
|
// OpenSpec Visualizer tools
|
|
2120
2160
|
...registerOpenSpecVizTools(),
|
|
2121
2161
|
// Intel layer tools (§2.5 — read-only MCP surface for intel.search + source catalog)
|
|
@@ -3006,6 +3046,18 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
3006
3046
|
if (isSessionControlTool(name)) {
|
|
3007
3047
|
return handleSessionControlToolCall(name, args, client);
|
|
3008
3048
|
}
|
|
3049
|
+
// Intake requests (add-intake-requests §6.1). Same module pattern: the module
|
|
3050
|
+
// owns its tool names and turns refusals into structured results.
|
|
3051
|
+
if (isIntakeRequestTool(name)) {
|
|
3052
|
+
return handleIntakeRequestToolCall(name, args, client, () => state.loadState(context.getWorkingDir())?.project_id);
|
|
3053
|
+
}
|
|
3054
|
+
// Goshti reads (add-agent-goshti-read §3.1). Same module pattern. The owner's
|
|
3055
|
+
// grant, the per-agent switch and the fixed tool list are all the server's
|
|
3056
|
+
// decisions; this only shapes the call and labels the answer as other
|
|
3057
|
+
// people's words.
|
|
3058
|
+
if (isGoshtiReadTool(name)) {
|
|
3059
|
+
return handleGoshtiReadToolCall(name, args, client);
|
|
3060
|
+
}
|
|
3009
3061
|
switch (name) {
|
|
3010
3062
|
// Registration
|
|
3011
3063
|
case "agent_register": {
|
|
@@ -3682,10 +3734,19 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
3682
3734
|
return wrapWithPendingItems(client, agentId, result, context);
|
|
3683
3735
|
}
|
|
3684
3736
|
case "check_inbox": {
|
|
3685
|
-
// No wrapping - already fetching messages
|
|
3737
|
+
// No wrapping - already fetching messages.
|
|
3738
|
+
//
|
|
3739
|
+
// The limit is explicit (fix-inbox-delivery-and-tool-wrapping §1.2).
|
|
3740
|
+
// Omitting it let the server apply its own default of 50 full-bodied
|
|
3741
|
+
// messages; one such response measured 115,769 characters, exceeded the
|
|
3742
|
+
// tool-result token budget and never reached the model at all. boundInbox
|
|
3743
|
+
// then declares anything it withheld, so a caller can never acknowledge
|
|
3744
|
+
// messages it did not actually receive.
|
|
3686
3745
|
if (!agentId)
|
|
3687
3746
|
throw new Error("Not registered. Call agent_register first.");
|
|
3688
|
-
|
|
3747
|
+
const inboxLimit = resolveInboxLimit(args.limit);
|
|
3748
|
+
const raw = await client.getInbox(agentId, args.unread_only, inboxLimit);
|
|
3749
|
+
return boundInbox(raw);
|
|
3689
3750
|
}
|
|
3690
3751
|
case "mark_read": {
|
|
3691
3752
|
const result = await client.markRead(args.message_id);
|