@mininglamp-oss/cc-channel-octo 1.0.3-dev.3135d38 → 1.0.3-dev.3332be0
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/config.bot.example.json +2 -1
- package/config.example.json +6 -1
- package/dist/config.d.ts +52 -0
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/group-context.d.ts +18 -0
- package/dist/group-context.js +77 -6
- package/dist/group-context.js.map +1 -1
- package/dist/group-md-cache.d.ts +50 -0
- package/dist/group-md-cache.js +79 -0
- package/dist/group-md-cache.js.map +1 -1
- package/dist/group-md-events.d.ts +36 -4
- package/dist/group-md-events.js +36 -5
- package/dist/group-md-events.js.map +1 -1
- package/dist/group-md-tool.d.ts +80 -0
- package/dist/group-md-tool.js +181 -0
- package/dist/group-md-tool.js.map +1 -0
- package/dist/group-md-writeback.d.ts +183 -0
- package/dist/group-md-writeback.js +223 -0
- package/dist/group-md-writeback.js.map +1 -0
- package/dist/group-md.d.ts +47 -24
- package/dist/group-md.js +112 -23
- package/dist/group-md.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +112 -17
- package/dist/index.js.map +1 -1
- package/dist/octo/api.d.ts +59 -0
- package/dist/octo/api.js +53 -0
- package/dist/octo/api.js.map +1 -1
- package/dist/prompt-safety.d.ts +20 -0
- package/dist/prompt-safety.js +24 -0
- package/dist/prompt-safety.js.map +1 -1
- package/dist/session-router.d.ts +30 -2
- package/dist/session-router.js +72 -9
- package/dist/session-router.js.map +1 -1
- package/package.json +1 -1
package/dist/group-md-events.js
CHANGED
|
@@ -27,17 +27,48 @@
|
|
|
27
27
|
/**
|
|
28
28
|
* Provisional `event.type` literals treated as a GROUP.md change. PROVISIONAL —
|
|
29
29
|
* see the calibration note above. Overridable via `config.serverMdEventTypes`.
|
|
30
|
+
*
|
|
31
|
+
* Both an UPDATE and a DELETE of the server GROUP.md drive the same action —
|
|
32
|
+
* INVALIDATE the cached entry so the next turn re-fetches (an updated entry is
|
|
33
|
+
* re-read; a deleted one 404s and cleanly degrades to the local fallback), so
|
|
34
|
+
* `group_md_deleted` sits alongside `group_md_updated` here (P3-2 deleted tail).
|
|
30
35
|
*/
|
|
31
|
-
export const DEFAULT_GROUP_MD_EVENT_TYPES = [
|
|
36
|
+
export const DEFAULT_GROUP_MD_EVENT_TYPES = [
|
|
37
|
+
'group_md_updated',
|
|
38
|
+
'group_md_deleted',
|
|
39
|
+
];
|
|
32
40
|
/**
|
|
33
|
-
* True iff this event signals a GROUP.md change
|
|
34
|
-
* refresh. All other system events (group join/leave, etc.) return
|
|
35
|
-
* are dropped unchanged by the router. `eventTypes` lets the operator
|
|
36
|
-
* the provisional literal(s) without a code change.
|
|
41
|
+
* True iff this event signals a GROUP.md change (update or delete) and should
|
|
42
|
+
* drive a cache refresh. All other system events (group join/leave, etc.) return
|
|
43
|
+
* false and are dropped unchanged by the router. `eventTypes` lets the operator
|
|
44
|
+
* override the provisional literal(s) without a code change.
|
|
37
45
|
*/
|
|
38
46
|
export function isGroupMdUpdateEvent(event, eventTypes = DEFAULT_GROUP_MD_EVENT_TYPES) {
|
|
39
47
|
if (!event || typeof event.type !== 'string' || event.type === '')
|
|
40
48
|
return false;
|
|
41
49
|
return eventTypes.includes(event.type);
|
|
42
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Provisional `event.type` literals treated as a THREAD.md change (P3-2). Same
|
|
53
|
+
* PROVISIONAL status as the group literals above — the exact wire literal is not
|
|
54
|
+
* yet confirmed from a captured event, so it is named after the design and is
|
|
55
|
+
* overridable via `config.threadMdEventTypes` without a code change. As on the
|
|
56
|
+
* group side, both update and delete map to the same invalidate action.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_THREAD_MD_EVENT_TYPES = [
|
|
59
|
+
'thread_md_updated',
|
|
60
|
+
'thread_md_deleted',
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* True iff this event signals a THREAD.md change (update or delete). The literal
|
|
64
|
+
* sets for group vs thread are DISJOINT (`group_md_*` vs `thread_md_*`), so a
|
|
65
|
+
* thread event never trips the group invalidation and vice versa — the two
|
|
66
|
+
* classifiers stay mutually exclusive, matching the read/write mutual-exclusion
|
|
67
|
+
* contract of #88 P3.
|
|
68
|
+
*/
|
|
69
|
+
export function isThreadMdUpdateEvent(event, eventTypes = DEFAULT_THREAD_MD_EVENT_TYPES) {
|
|
70
|
+
if (!event || typeof event.type !== 'string' || event.type === '')
|
|
71
|
+
return false;
|
|
72
|
+
return eventTypes.includes(event.type);
|
|
73
|
+
}
|
|
43
74
|
//# sourceMappingURL=group-md-events.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-md-events.js","sourceRoot":"","sources":["../src/group-md-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH
|
|
1
|
+
{"version":3,"file":"group-md-events.js","sourceRoot":"","sources":["../src/group-md-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAsB;IAC7D,kBAAkB;IAClB,kBAAkB;CACnB,CAAC;AAQF;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAmC,EACnC,aAAgC,4BAA4B;IAE5D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAChF,OAAO,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAsB;IAC9D,mBAAmB;IACnB,mBAAmB;CACpB,CAAC;AAcF;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAoC,EACpC,aAAgC,6BAA6B;IAE7D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAChF,OAAO,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2-C: GROUP.md write-back tool — an in-process MCP server letting the agent
|
|
3
|
+
* persist an updated GROUP.md back to the server. The tool surfaces to the model
|
|
4
|
+
* as `mcp__group_md__update_group_md`.
|
|
5
|
+
*
|
|
6
|
+
* The server is built PER TURN (`createGroupMdToolServer`) with the current
|
|
7
|
+
* message's channel coords + the bot owner uid, so:
|
|
8
|
+
* - the write targets the PARENT group of the channel the agent is in
|
|
9
|
+
* (`extractParentGroupNo` — a thread shares its parent group's GROUP.md);
|
|
10
|
+
* - invocation is GATED to the bot owner (registerBot.owner_uid). The group's
|
|
11
|
+
* octo_tag token has server-side write permission, but the agent is driven by
|
|
12
|
+
* untrusted IM users, so this owner gate — not LLM judgment, and independent
|
|
13
|
+
* of the token's group-role permission — is what stops a prompt-injected
|
|
14
|
+
* agent from rewriting the operator's trusted GROUP.md from any chat.
|
|
15
|
+
*
|
|
16
|
+
* Concurrency, the byte ceiling, and the cache refresh are owned by the shared
|
|
17
|
+
* {@link GroupMdWriteback} coordinator (group-md-writeback.ts); this layer is
|
|
18
|
+
* only the owner-gate policy + the MCP surface.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { type GroupMdWriteback, type ThreadMdWriteback } from './group-md-writeback.js';
|
|
22
|
+
/** MCP server name; the tool surfaces as `mcp__group_md__update_group_md`. */
|
|
23
|
+
export declare const GROUP_MD_TOOL_SERVER_NAME = "group_md";
|
|
24
|
+
/** MCP server name; the tool surfaces as `mcp__thread_md__update_thread_md`. */
|
|
25
|
+
export declare const THREAD_MD_TOOL_SERVER_NAME = "thread_md";
|
|
26
|
+
/** Raw coords of the session invoking the tool — gates the call + targets the group. */
|
|
27
|
+
export interface GroupMdSessionCoords {
|
|
28
|
+
/** Full channelId (may be a `<groupNo>____<shortId>` thread composite). */
|
|
29
|
+
channelId: string;
|
|
30
|
+
fromUid: string;
|
|
31
|
+
fromName?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Shared deps the tool needs to perform a write-back. */
|
|
34
|
+
export interface GroupMdToolDeps {
|
|
35
|
+
writeback: GroupMdWriteback;
|
|
36
|
+
apiUrl: string;
|
|
37
|
+
botToken: string;
|
|
38
|
+
}
|
|
39
|
+
/** Shared deps the thread write-back tool needs. */
|
|
40
|
+
export interface ThreadMdToolDeps {
|
|
41
|
+
writeback: ThreadMdWriteback;
|
|
42
|
+
apiUrl: string;
|
|
43
|
+
botToken: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the GROUP.md tool DEFINITIONS for one agent turn. Exported separately
|
|
47
|
+
* from the server so tests can invoke the handler directly. `coords` targets the
|
|
48
|
+
* group + supplies the caller uid; `ownerUid` is the owner gate.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildGroupMdTools(deps: GroupMdToolDeps, coords: GroupMdSessionCoords, ownerUid: string): import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
|
|
51
|
+
content: z.ZodString;
|
|
52
|
+
}>[];
|
|
53
|
+
/**
|
|
54
|
+
* Build the GROUP.md write-back MCP server for one agent turn. `coords` targets
|
|
55
|
+
* the group + supplies the caller uid; `ownerUid` is the owner gate.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createGroupMdToolServer(deps: GroupMdToolDeps, coords: GroupMdSessionCoords, ownerUid: string): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
|
|
58
|
+
/**
|
|
59
|
+
* Build the THREAD.md tool DEFINITIONS for one agent turn (P3-2). The thread
|
|
60
|
+
* analogue of {@link buildGroupMdTools}: the tool surfaces as
|
|
61
|
+
* `mcp__thread_md__update_thread_md` and writes THIS thread's OWN THREAD.md
|
|
62
|
+
* (PUT /v1/bot/groups/{groupNo}/threads/{shortId}/md), NEVER the parent group's
|
|
63
|
+
* GROUP.md — the thread/group split is mutually exclusive (#88 P3).
|
|
64
|
+
* index.ts only ever wires the group tool OR this thread tool for a turn (chosen
|
|
65
|
+
* by channelId shape), so the two never co-exist in one session.
|
|
66
|
+
*
|
|
67
|
+
* `coords.channelId` MUST be a thread composite (`<groupNo>____<shortId>`); the
|
|
68
|
+
* caller (index.ts) guarantees this by routing on `isThreadChannelId`. The
|
|
69
|
+
* owner-gate is identical to the group tool (bot-owner-only, independent of the
|
|
70
|
+
* token's server-side thread permission).
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildThreadMdTools(deps: ThreadMdToolDeps, coords: GroupMdSessionCoords, ownerUid: string): import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
|
|
73
|
+
content: z.ZodString;
|
|
74
|
+
}>[];
|
|
75
|
+
/**
|
|
76
|
+
* Build the THREAD.md write-back MCP server for one agent turn. `coords` targets
|
|
77
|
+
* the thread (its composite channelId) + supplies the caller uid; `ownerUid` is
|
|
78
|
+
* the owner gate.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createThreadMdToolServer(deps: ThreadMdToolDeps, coords: GroupMdSessionCoords, ownerUid: string): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2-C: GROUP.md write-back tool — an in-process MCP server letting the agent
|
|
3
|
+
* persist an updated GROUP.md back to the server. The tool surfaces to the model
|
|
4
|
+
* as `mcp__group_md__update_group_md`.
|
|
5
|
+
*
|
|
6
|
+
* The server is built PER TURN (`createGroupMdToolServer`) with the current
|
|
7
|
+
* message's channel coords + the bot owner uid, so:
|
|
8
|
+
* - the write targets the PARENT group of the channel the agent is in
|
|
9
|
+
* (`extractParentGroupNo` — a thread shares its parent group's GROUP.md);
|
|
10
|
+
* - invocation is GATED to the bot owner (registerBot.owner_uid). The group's
|
|
11
|
+
* octo_tag token has server-side write permission, but the agent is driven by
|
|
12
|
+
* untrusted IM users, so this owner gate — not LLM judgment, and independent
|
|
13
|
+
* of the token's group-role permission — is what stops a prompt-injected
|
|
14
|
+
* agent from rewriting the operator's trusted GROUP.md from any chat.
|
|
15
|
+
*
|
|
16
|
+
* Concurrency, the byte ceiling, and the cache refresh are owned by the shared
|
|
17
|
+
* {@link GroupMdWriteback} coordinator (group-md-writeback.ts); this layer is
|
|
18
|
+
* only the owner-gate policy + the MCP surface.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
|
22
|
+
import { extractParentGroupNo, extractThreadShortId, isThreadChannelId, } from './octo/channel-id.js';
|
|
23
|
+
import { GroupMdContentTooLargeError, MAX_GROUP_MD_CONTENT_BYTES, ThreadMdContentTooLargeError, MAX_THREAD_MD_CONTENT_BYTES, } from './group-md-writeback.js';
|
|
24
|
+
/** MCP server name; the tool surfaces as `mcp__group_md__update_group_md`. */
|
|
25
|
+
export const GROUP_MD_TOOL_SERVER_NAME = 'group_md';
|
|
26
|
+
/** MCP server name; the tool surfaces as `mcp__thread_md__update_thread_md`. */
|
|
27
|
+
export const THREAD_MD_TOOL_SERVER_NAME = 'thread_md';
|
|
28
|
+
function jsonResult(value) {
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
|
|
30
|
+
}
|
|
31
|
+
function errResult(msg) {
|
|
32
|
+
return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the GROUP.md tool DEFINITIONS for one agent turn. Exported separately
|
|
36
|
+
* from the server so tests can invoke the handler directly. `coords` targets the
|
|
37
|
+
* group + supplies the caller uid; `ownerUid` is the owner gate.
|
|
38
|
+
*/
|
|
39
|
+
export function buildGroupMdTools(deps, coords, ownerUid) {
|
|
40
|
+
const isOwner = coords.fromUid === ownerUid && ownerUid !== '';
|
|
41
|
+
return [
|
|
42
|
+
tool('update_group_md', 'Persist new GROUP.md content for THIS group on the server (it becomes the ' +
|
|
43
|
+
"group's trusted operator instructions on the next turn). `content` is the " +
|
|
44
|
+
'FULL replacement document, not a diff. Hard limit: 10240 bytes UTF-8. Only ' +
|
|
45
|
+
'the bot owner may call this; a non-owner request is rejected. Last write ' +
|
|
46
|
+
'wins server-side — compose the complete updated document, do not assume a ' +
|
|
47
|
+
'concurrent edit merged.', {
|
|
48
|
+
content: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe('Full replacement GROUP.md document (≤10240 bytes UTF-8).'),
|
|
51
|
+
}, async (args) => {
|
|
52
|
+
try {
|
|
53
|
+
if (!isOwner) {
|
|
54
|
+
return errResult('Only the bot owner can update GROUP.md.');
|
|
55
|
+
}
|
|
56
|
+
const groupNo = extractParentGroupNo(coords.channelId);
|
|
57
|
+
if (!groupNo) {
|
|
58
|
+
return errResult('Could not resolve a group number from this channel.');
|
|
59
|
+
}
|
|
60
|
+
// Surface a friendly over-limit message before the coordinator throws
|
|
61
|
+
// (it re-checks as the authoritative boundary; this is just for UX).
|
|
62
|
+
const bytes = Buffer.byteLength(args.content, 'utf-8');
|
|
63
|
+
if (bytes > MAX_GROUP_MD_CONTENT_BYTES) {
|
|
64
|
+
return errResult(`content is ${bytes} bytes, over the ${MAX_GROUP_MD_CONTENT_BYTES}-byte ` +
|
|
65
|
+
`UTF-8 limit — trim it before writing (the server would reject it).`);
|
|
66
|
+
}
|
|
67
|
+
const res = await deps.writeback.writeBack({
|
|
68
|
+
apiUrl: deps.apiUrl,
|
|
69
|
+
botToken: deps.botToken,
|
|
70
|
+
groupNo,
|
|
71
|
+
content: args.content,
|
|
72
|
+
});
|
|
73
|
+
return jsonResult({
|
|
74
|
+
updated: { groupNo: res.groupNo, version: res.version, bytes: res.bytes },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (err instanceof GroupMdContentTooLargeError) {
|
|
79
|
+
return errResult(`content is ${err.bytes} bytes, over the ${MAX_GROUP_MD_CONTENT_BYTES}-byte UTF-8 limit.`);
|
|
80
|
+
}
|
|
81
|
+
return errResult(err instanceof Error ? err.message : String(err));
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the GROUP.md write-back MCP server for one agent turn. `coords` targets
|
|
88
|
+
* the group + supplies the caller uid; `ownerUid` is the owner gate.
|
|
89
|
+
*/
|
|
90
|
+
export function createGroupMdToolServer(deps, coords, ownerUid) {
|
|
91
|
+
return createSdkMcpServer({
|
|
92
|
+
name: GROUP_MD_TOOL_SERVER_NAME,
|
|
93
|
+
version: '1.0.0',
|
|
94
|
+
tools: buildGroupMdTools(deps, coords, ownerUid),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build the THREAD.md tool DEFINITIONS for one agent turn (P3-2). The thread
|
|
99
|
+
* analogue of {@link buildGroupMdTools}: the tool surfaces as
|
|
100
|
+
* `mcp__thread_md__update_thread_md` and writes THIS thread's OWN THREAD.md
|
|
101
|
+
* (PUT /v1/bot/groups/{groupNo}/threads/{shortId}/md), NEVER the parent group's
|
|
102
|
+
* GROUP.md — the thread/group split is mutually exclusive (#88 P3).
|
|
103
|
+
* index.ts only ever wires the group tool OR this thread tool for a turn (chosen
|
|
104
|
+
* by channelId shape), so the two never co-exist in one session.
|
|
105
|
+
*
|
|
106
|
+
* `coords.channelId` MUST be a thread composite (`<groupNo>____<shortId>`); the
|
|
107
|
+
* caller (index.ts) guarantees this by routing on `isThreadChannelId`. The
|
|
108
|
+
* owner-gate is identical to the group tool (bot-owner-only, independent of the
|
|
109
|
+
* token's server-side thread permission).
|
|
110
|
+
*/
|
|
111
|
+
export function buildThreadMdTools(deps, coords, ownerUid) {
|
|
112
|
+
const isOwner = coords.fromUid === ownerUid && ownerUid !== '';
|
|
113
|
+
return [
|
|
114
|
+
tool('update_thread_md', 'Persist new THREAD.md content for THIS thread (subarea) on the server (it ' +
|
|
115
|
+
"becomes the thread's trusted operator instructions on the next turn). " +
|
|
116
|
+
'`content` is the FULL replacement document, not a diff. Hard limit: 10240 ' +
|
|
117
|
+
'bytes UTF-8. Only the bot owner may call this; a non-owner request is ' +
|
|
118
|
+
'rejected. This writes the thread\'s OWN THREAD.md, never the parent ' +
|
|
119
|
+
"group's GROUP.md. Last write wins server-side — compose the complete " +
|
|
120
|
+
'updated document, do not assume a concurrent edit merged.', {
|
|
121
|
+
content: z
|
|
122
|
+
.string()
|
|
123
|
+
.describe('Full replacement THREAD.md document (≤10240 bytes UTF-8).'),
|
|
124
|
+
}, async (args) => {
|
|
125
|
+
try {
|
|
126
|
+
if (!isOwner) {
|
|
127
|
+
return errResult('Only the bot owner can update THREAD.md.');
|
|
128
|
+
}
|
|
129
|
+
if (!isThreadChannelId(coords.channelId)) {
|
|
130
|
+
return errResult('This channel is not a thread — THREAD.md is only writable from a thread.');
|
|
131
|
+
}
|
|
132
|
+
const groupNo = extractParentGroupNo(coords.channelId);
|
|
133
|
+
const shortId = extractThreadShortId(coords.channelId);
|
|
134
|
+
if (!groupNo || !shortId) {
|
|
135
|
+
return errResult('Could not resolve a thread (groupNo/shortId) from this channel.');
|
|
136
|
+
}
|
|
137
|
+
// Friendly over-limit message before the coordinator throws (it re-checks
|
|
138
|
+
// as the authoritative boundary; this is just for UX).
|
|
139
|
+
const bytes = Buffer.byteLength(args.content, 'utf-8');
|
|
140
|
+
if (bytes > MAX_THREAD_MD_CONTENT_BYTES) {
|
|
141
|
+
return errResult(`content is ${bytes} bytes, over the ${MAX_THREAD_MD_CONTENT_BYTES}-byte ` +
|
|
142
|
+
`UTF-8 limit — trim it before writing (the server would reject it).`);
|
|
143
|
+
}
|
|
144
|
+
const res = await deps.writeback.writeBack({
|
|
145
|
+
apiUrl: deps.apiUrl,
|
|
146
|
+
botToken: deps.botToken,
|
|
147
|
+
groupNo,
|
|
148
|
+
shortId,
|
|
149
|
+
content: args.content,
|
|
150
|
+
});
|
|
151
|
+
return jsonResult({
|
|
152
|
+
updated: {
|
|
153
|
+
groupNo: res.groupNo,
|
|
154
|
+
shortId: res.shortId,
|
|
155
|
+
version: res.version,
|
|
156
|
+
bytes: res.bytes,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (err instanceof ThreadMdContentTooLargeError) {
|
|
162
|
+
return errResult(`content is ${err.bytes} bytes, over the ${MAX_THREAD_MD_CONTENT_BYTES}-byte UTF-8 limit.`);
|
|
163
|
+
}
|
|
164
|
+
return errResult(err instanceof Error ? err.message : String(err));
|
|
165
|
+
}
|
|
166
|
+
}),
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Build the THREAD.md write-back MCP server for one agent turn. `coords` targets
|
|
171
|
+
* the thread (its composite channelId) + supplies the caller uid; `ownerUid` is
|
|
172
|
+
* the owner gate.
|
|
173
|
+
*/
|
|
174
|
+
export function createThreadMdToolServer(deps, coords, ownerUid) {
|
|
175
|
+
return createSdkMcpServer({
|
|
176
|
+
name: THREAD_MD_TOOL_SERVER_NAME,
|
|
177
|
+
version: '1.0.0',
|
|
178
|
+
tools: buildThreadMdTools(deps, coords, ownerUid),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=group-md-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-md-tool.js","sourceRoot":"","sources":["../src/group-md-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GROUP.md write-back (P2-C): the app-layer coordinator that persists an
|
|
3
|
+
* agent-authored GROUP.md back to the server (PUT /v1/bot/groups/{groupNo}/md)
|
|
4
|
+
* via A's `updateGroupMd` client, then refreshes A's in-memory cache so the next
|
|
5
|
+
* resolve does not TTL-refetch a stale copy.
|
|
6
|
+
*
|
|
7
|
+
* Two server-contract facts (XIN-201, measured — NOT inferred) shape this:
|
|
8
|
+
*
|
|
9
|
+
* 1. content has a HARD ≤10240-byte (UTF-8) limit; the server answers an
|
|
10
|
+
* oversized body with 400 err.server.bot_api.content_too_large. We reject
|
|
11
|
+
* locally BEFORE the PUT so a too-large write never reaches the server.
|
|
12
|
+
*
|
|
13
|
+
* 2. `version` is a server-side monotonic counter and the PUT does NOT do
|
|
14
|
+
* compare-and-swap (last-write-wins, no CAS). Concurrent writers therefore
|
|
15
|
+
* silently clobber each other server-side. We cannot fix a foreign writer
|
|
16
|
+
* (operator console, another gateway), but we CAN guarantee that this
|
|
17
|
+
* gateway never races itself: all write-backs for the same groupNo are
|
|
18
|
+
* serialized through a per-groupNo promise-chain lock, so the read-modify-
|
|
19
|
+
* write (PUT + cache update) for one call completes before the next starts.
|
|
20
|
+
* Cross-source last-write-wins remains possible by design — see the caveat
|
|
21
|
+
* on {@link GroupMdWriteback}.
|
|
22
|
+
*
|
|
23
|
+
* Owner-gating lives in the MCP tool layer (group-md-tool.ts), not here: this
|
|
24
|
+
* module is the mechanism, the tool is the policy boundary.
|
|
25
|
+
*
|
|
26
|
+
* Never persists anything to disk — the cache it updates is the same in-memory-
|
|
27
|
+
* only cache A's resolver reads (group-md-cache.ts), so this introduces no new
|
|
28
|
+
* durable-trust surface.
|
|
29
|
+
*/
|
|
30
|
+
import { updateGroupMd, updateThreadMd } from './octo/api.js';
|
|
31
|
+
import type { GroupMdCache, ThreadMdCache } from './group-md-cache.js';
|
|
32
|
+
/**
|
|
33
|
+
* Hard UTF-8 byte ceiling for GROUP.md content, per the XIN-201 measured
|
|
34
|
+
* server contract. A body above this is rejected locally (a server PUT would
|
|
35
|
+
* answer 400 err.server.bot_api.content_too_large).
|
|
36
|
+
*/
|
|
37
|
+
export declare const MAX_GROUP_MD_CONTENT_BYTES = 10240;
|
|
38
|
+
/**
|
|
39
|
+
* Hard UTF-8 byte ceiling for THREAD.md content. The thread md endpoint shares
|
|
40
|
+
* the group md limit (P3-2 contract check: the official Octo web client edits
|
|
41
|
+
* both group and thread md through the SAME editor, whose `MAX_BYTES` is 10240),
|
|
42
|
+
* so this is an alias of {@link MAX_GROUP_MD_CONTENT_BYTES} rather than a second
|
|
43
|
+
* magic number — if the server limit ever diverges, the two can split here.
|
|
44
|
+
*/
|
|
45
|
+
export declare const MAX_THREAD_MD_CONTENT_BYTES = 10240;
|
|
46
|
+
/** Thrown when content exceeds {@link MAX_GROUP_MD_CONTENT_BYTES}. */
|
|
47
|
+
export declare class GroupMdContentTooLargeError extends Error {
|
|
48
|
+
readonly bytes: number;
|
|
49
|
+
constructor(bytes: number);
|
|
50
|
+
}
|
|
51
|
+
/** The `updateGroupMd` client signature, injectable for testing. */
|
|
52
|
+
export type UpdateGroupMdFn = typeof updateGroupMd;
|
|
53
|
+
/** Outcome of a successful write-back. */
|
|
54
|
+
export interface GroupMdWriteResult {
|
|
55
|
+
groupNo: string;
|
|
56
|
+
/** Server-assigned version after the PUT. */
|
|
57
|
+
version: number;
|
|
58
|
+
/** UTF-8 byte length of the content that was written. */
|
|
59
|
+
bytes: number;
|
|
60
|
+
}
|
|
61
|
+
export interface GroupMdWriteParams {
|
|
62
|
+
apiUrl: string;
|
|
63
|
+
botToken: string;
|
|
64
|
+
groupNo: string;
|
|
65
|
+
content: string;
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Serializes GROUP.md write-backs per groupNo and keeps A's cache in sync.
|
|
70
|
+
*
|
|
71
|
+
* Constructed ONCE per bot (alongside the GroupMdCache it updates) and shared
|
|
72
|
+
* across turns, so the per-groupNo lock actually spans concurrent agent turns —
|
|
73
|
+
* a per-turn instance would defeat the lock. The MCP tool server (built per
|
|
74
|
+
* turn) borrows this shared instance.
|
|
75
|
+
*
|
|
76
|
+
* CAVEAT (documented per XIN-201 item 4): the lock only covers THIS gateway. A
|
|
77
|
+
* write from another source (operator console, a second gateway process) is not
|
|
78
|
+
* coordinated and, because the server has no CAS, last-write-wins across sources
|
|
79
|
+
* — that is an accepted limitation, not a bug.
|
|
80
|
+
*/
|
|
81
|
+
export declare class GroupMdWriteback {
|
|
82
|
+
private readonly cache;
|
|
83
|
+
/** Injectable PUT client (defaults to the real octo client). */
|
|
84
|
+
private readonly updateFn;
|
|
85
|
+
/** Tail of the in-flight write chain per groupNo (serializes same-key writes). */
|
|
86
|
+
private readonly tails;
|
|
87
|
+
constructor(cache: GroupMdCache,
|
|
88
|
+
/** Injectable PUT client (defaults to the real octo client). */
|
|
89
|
+
updateFn?: UpdateGroupMdFn);
|
|
90
|
+
/**
|
|
91
|
+
* Run `fn` after every previously-queued op for `key` has settled, so bodies
|
|
92
|
+
* for the same key never overlap. Different keys run independently. `fn`'s
|
|
93
|
+
* rejection is surfaced to its own caller; the chain tail swallows it so a
|
|
94
|
+
* failed write does not wedge later writes.
|
|
95
|
+
*/
|
|
96
|
+
private withLock;
|
|
97
|
+
/**
|
|
98
|
+
* Persist `content` as the group's GROUP.md and refresh the cache.
|
|
99
|
+
*
|
|
100
|
+
* Rejects with {@link GroupMdContentTooLargeError} (before any server call)
|
|
101
|
+
* when content exceeds the UTF-8 byte limit; propagates the client error when
|
|
102
|
+
* the PUT itself fails (cache is left untouched in that case).
|
|
103
|
+
*/
|
|
104
|
+
writeBack(params: GroupMdWriteParams): Promise<GroupMdWriteResult>;
|
|
105
|
+
}
|
|
106
|
+
/** The `updateThreadMd` client signature, injectable for testing. */
|
|
107
|
+
export type UpdateThreadMdFn = typeof updateThreadMd;
|
|
108
|
+
/** Thrown when THREAD.md content exceeds {@link MAX_THREAD_MD_CONTENT_BYTES}. */
|
|
109
|
+
export declare class ThreadMdContentTooLargeError extends Error {
|
|
110
|
+
readonly bytes: number;
|
|
111
|
+
constructor(bytes: number);
|
|
112
|
+
}
|
|
113
|
+
/** Outcome of a successful THREAD.md write-back. */
|
|
114
|
+
export interface ThreadMdWriteResult {
|
|
115
|
+
groupNo: string;
|
|
116
|
+
shortId: string;
|
|
117
|
+
/** Server-assigned version after the PUT. */
|
|
118
|
+
version: number;
|
|
119
|
+
/** UTF-8 byte length of the content that was written. */
|
|
120
|
+
bytes: number;
|
|
121
|
+
}
|
|
122
|
+
export interface ThreadMdWriteParams {
|
|
123
|
+
apiUrl: string;
|
|
124
|
+
botToken: string;
|
|
125
|
+
groupNo: string;
|
|
126
|
+
shortId: string;
|
|
127
|
+
content: string;
|
|
128
|
+
signal?: AbortSignal;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Thread analogue of {@link GroupMdWriteback} (P3-2): persists an agent-authored
|
|
132
|
+
* THREAD.md back to the server (PUT /v1/bot/groups/{groupNo}/threads/{shortId}/md
|
|
133
|
+
* via `updateThreadMd`), then refreshes the in-memory {@link ThreadMdCache} so
|
|
134
|
+
* the next resolve does not TTL-refetch a stale copy.
|
|
135
|
+
*
|
|
136
|
+
* Carries over every P2-C safety property, adapted to the thread key:
|
|
137
|
+
*
|
|
138
|
+
* 1. Same HARD ≤10240-byte (UTF-8) limit ({@link MAX_THREAD_MD_CONTENT_BYTES},
|
|
139
|
+
* confirmed to equal the group limit — see that constant). Rejected locally
|
|
140
|
+
* BEFORE the PUT so an oversized body never reaches the server.
|
|
141
|
+
*
|
|
142
|
+
* 2. The thread PUT, like the group PUT, does NOT compare-and-swap (body is
|
|
143
|
+
* just `{ content }`, reply is `{ version }`). All write-backs for the same
|
|
144
|
+
* thread are serialized through a per-`groupNo::shortId` promise-chain lock
|
|
145
|
+
* so this gateway never races itself. Cross-source last-write-wins remains
|
|
146
|
+
* possible by design, identical to the group caveat.
|
|
147
|
+
*
|
|
148
|
+
* Keyed by the COMPOSITE `groupNo::shortId` — the SAME key the thread cache and
|
|
149
|
+
* the P3-1 read side use — so a thread's write lock and cache refresh can never
|
|
150
|
+
* be confused with its parent group's (which the group coordinator locks by the
|
|
151
|
+
* bare groupNo). `::` is outside the safe-id charset, so the two key spaces are
|
|
152
|
+
* disjoint. Owner-gating lives in the MCP tool layer (group-md-tool.ts), not
|
|
153
|
+
* here. Never persists to disk — the cache it updates is the same memory-only
|
|
154
|
+
* cache the resolver reads.
|
|
155
|
+
*/
|
|
156
|
+
export declare class ThreadMdWriteback {
|
|
157
|
+
private readonly cache;
|
|
158
|
+
/** Injectable PUT client (defaults to the real octo client). */
|
|
159
|
+
private readonly updateFn;
|
|
160
|
+
/** Tail of the in-flight write chain per composite key (serializes same-thread writes). */
|
|
161
|
+
private readonly tails;
|
|
162
|
+
constructor(cache: ThreadMdCache,
|
|
163
|
+
/** Injectable PUT client (defaults to the real octo client). */
|
|
164
|
+
updateFn?: UpdateThreadMdFn);
|
|
165
|
+
/** Composite lock key; mirrors ThreadMdCache's `groupNo::shortId`. */
|
|
166
|
+
private lockKey;
|
|
167
|
+
/**
|
|
168
|
+
* Run `fn` after every previously-queued op for `key` has settled, so bodies
|
|
169
|
+
* for the same key never overlap. Different keys run independently. `fn`'s
|
|
170
|
+
* rejection is surfaced to its own caller; the chain tail swallows it so a
|
|
171
|
+
* failed write does not wedge later writes. Identical policy to
|
|
172
|
+
* {@link GroupMdWriteback}.
|
|
173
|
+
*/
|
|
174
|
+
private withLock;
|
|
175
|
+
/**
|
|
176
|
+
* Persist `content` as the thread's THREAD.md and refresh the thread cache.
|
|
177
|
+
*
|
|
178
|
+
* Rejects with {@link ThreadMdContentTooLargeError} (before any server call)
|
|
179
|
+
* when content exceeds the UTF-8 byte limit; propagates the client error when
|
|
180
|
+
* the PUT itself fails (cache is left untouched in that case).
|
|
181
|
+
*/
|
|
182
|
+
writeBack(params: ThreadMdWriteParams): Promise<ThreadMdWriteResult>;
|
|
183
|
+
}
|