@sleep2agi/commhub-server 0.9.0-preview.87 → 0.9.0-preview.89
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/package.json +1 -1
- package/src/agent-notice.ts +48 -0
- package/src/db.ts +2 -2
- package/src/http-gzip.ts +3 -1
- package/src/model-auth-notify.ts +7 -23
- package/src/node-health-store.ts +16 -1
- package/src/requirements-errors-mcp-http.test.ts +163 -0
- package/src/requirements-errors.ts +129 -0
- package/src/requirements-list-mcp-slim-http.test.ts +155 -0
- package/src/requirements-owner-strict-mcp-http.test.ts +145 -0
- package/src/requirements-search.ts +9 -2
- package/src/requirements.ts +35 -25
- package/src/scheduled-stuck-http.test.ts +238 -0
- package/src/scheduled-stuck.ts +153 -0
- package/src/scheduled-tasks.ts +32 -2
- package/src/server.ts +19 -3
- package/src/status-read-cache-http.test.ts +229 -0
- package/src/status-read-cache.ts +108 -0
- package/src/tools.ts +39 -19
package/src/tools.ts
CHANGED
|
@@ -5833,6 +5833,8 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
5833
5833
|
isAdmin: false,
|
|
5834
5834
|
isNodeToken: callerTokenIsNetwork,
|
|
5835
5835
|
scope,
|
|
5836
|
+
// #470:经 MCP 的写一律严格 —— owner 传节点回 400 owner_must_be_human(+ hint),不走旧 App 的兼容改写。
|
|
5837
|
+
strictOwner: true,
|
|
5836
5838
|
});
|
|
5837
5839
|
const data = res ? await res.json() : { ok: false, error: "not_found" };
|
|
5838
5840
|
return { content: [{ type: "text" as const, text: JSON.stringify(res && !res.ok && data && typeof data === "object" ? { ...data, status: res.status } : data) }] };
|
|
@@ -5849,7 +5851,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
5849
5851
|
description: z.string().max(20_000).optional().describe("markdown"),
|
|
5850
5852
|
checklist: z.array(reqChecklistItem).max(100).optional().describe("replaces the whole list"),
|
|
5851
5853
|
project_id: z.string().max(200).nullable().optional(),
|
|
5852
|
-
owner: reqPerson.nullable().optional().describe("
|
|
5854
|
+
owner: reqPerson.nullable().optional().describe("负责人 (a person): must be {kind:'user', id}; a node here is rejected with 400 owner_must_be_human — use agent_owner for nodes"),
|
|
5853
5855
|
agent_owner: reqPerson.nullable().optional().describe("负责 Agent: must be {kind:'node'}"),
|
|
5854
5856
|
participants: z.array(reqPerson).max(100).optional(),
|
|
5855
5857
|
external_url: z.string().max(500).nullable().optional(),
|
|
@@ -5859,31 +5861,49 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
5859
5861
|
const REQ_WRITE_KEYS = [...Object.keys(reqFields), "external_ref", "archived"];
|
|
5860
5862
|
const REQ_ID_DESC = "requirement id (req_…) or its short number \"#N\" (e.g. \"#42\", per network; a user token that sees several networks must also pass network_id, else 409 ambiguous_seq)";
|
|
5861
5863
|
|
|
5862
|
-
|
|
5864
|
+
// #471 —— MCP 的列表默认省流:summary 视图 + 50 张一页(REST 的默认 full + 500 不变,App 靠它)。
|
|
5865
|
+
// 生产实测 122 张卡:旧默认(full、500)148 KB,summary 81 KB —— 一次调用就塞满 Agent 的上下文。
|
|
5866
|
+
// 输入 schema 严格:写错 / 不认识的参数(比如以前的 tag)原来会被静默丢掉、回整张表;现在回 -32602 并列出能用的参数。
|
|
5867
|
+
const REQ_LIST_MCP_DEFAULT_LIMIT = 50;
|
|
5868
|
+
const reqListShape = {
|
|
5869
|
+
network_id: z.string().max(200).optional(),
|
|
5870
|
+
seq: z.number().int().positive().optional().describe("short number (#N) of one task in this network"),
|
|
5871
|
+
status: z.enum(["pool", "doing", "done"]).optional(),
|
|
5872
|
+
project_id: z.string().max(200).optional(),
|
|
5873
|
+
owner: z.string().max(210).optional(),
|
|
5874
|
+
agent_owner: z.string().max(210).optional(),
|
|
5875
|
+
tag: z.string().min(1).max(80).optional().describe("only tasks carrying this label (exact, case-sensitive)"),
|
|
5876
|
+
updated_since: z.string().max(40).optional(),
|
|
5877
|
+
external_ref: z.string().max(200).optional(),
|
|
5878
|
+
include_archived: z.boolean().optional(),
|
|
5879
|
+
parent_id: z.string().max(200).optional().describe("children of this requirement ('none' = top level)"),
|
|
5880
|
+
top_level: z.boolean().optional(), q: z.string().max(200).optional(),
|
|
5881
|
+
limit: z.number().int().min(1).max(1000).optional().describe(`page size, default ${REQ_LIST_MCP_DEFAULT_LIMIT}`),
|
|
5882
|
+
cursor: z.string().max(600).optional().describe("next_cursor from the previous page"),
|
|
5883
|
+
view: z.enum(["full", "summary"]).optional().describe("default summary (no description / checklist bodies; has_description, checklist_count instead); full = everything"),
|
|
5884
|
+
changes: z.boolean().optional().describe("with updated_since: only tasks changed since then (archived included) + deleted ids + server_time"),
|
|
5885
|
+
};
|
|
5886
|
+
const reqListKeys = Object.keys(reqListShape);
|
|
5887
|
+
server.registerTool(
|
|
5863
5888
|
"requirements_list",
|
|
5864
|
-
"List requirement tasks in your network. Filters: seq (the per-network short number shown as #N), status (pool/doing/done), project_id ('none' = no project), owner / agent_owner ('user:<id>' / 'node:<id>' / 'none'), updated_since (ISO), external_ref, parent_id / top_level, include_archived; q = search title / description / owner, agent and participant names / project name / tags (space-separated terms are ANDed). Newest first, 500 per page by default (limit ≤ 1000); has_more + next_cursor → pass cursor for the next page. Each task carries children {total, done}. view='summary' drops description and checklist bodies (adds has_description, checklist_count) — much smaller; use requirements_get for one task's full text. changes=true (needs updated_since) returns only tasks changed since then, archived ones included, plus deleted ids and server_time to pass as the next updated_since.",
|
|
5865
5889
|
{
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
external_ref: z.string().max(200).optional(),
|
|
5874
|
-
include_archived: z.boolean().optional(),
|
|
5875
|
-
parent_id: z.string().max(200).optional().describe("children of this requirement ('none' = top level)"),
|
|
5876
|
-
top_level: z.boolean().optional(), q: z.string().max(200).optional(), limit: z.number().int().min(1).max(1000).optional(), cursor: z.string().max(600).optional(),
|
|
5877
|
-
view: z.enum(["full", "summary"]).optional().describe("summary = no description / checklist bodies (has_description, checklist_count instead)"),
|
|
5878
|
-
changes: z.boolean().optional().describe("with updated_since: only tasks changed since then (archived included) + deleted ids + server_time"),
|
|
5890
|
+
description: `List requirement tasks in your network. Defaults (MCP): view='summary' and ${REQ_LIST_MCP_DEFAULT_LIMIT} per page, newest first — about 0.5 KB per task, so a page stays small. has_more + next_cursor → pass cursor for the next page. Summary rows drop description and checklist bodies (has_description, checklist_count instead); for full text read one task with requirements_get, or pass view='full' (several KB per task — keep limit small). Filters: seq (the per-network short number shown as #N), status (pool/doing/done), project_id ('none' = no project), owner / agent_owner ('user:<id>' / 'node:<id>' / 'none'), tag (exact label), updated_since (ISO), external_ref, parent_id / top_level, include_archived; q = search title / description / owner, agent and participant names / project name / tags (space-separated terms are ANDed). limit ≤ 1000. Each task carries children {total, done}. changes=true (needs updated_since) returns only tasks changed since then, archived ones included, plus deleted ids and server_time to pass as the next updated_since. Unknown parameters are rejected (-32602) with the list of valid ones.`,
|
|
5891
|
+
// Strict: registerTool honours a constructed object's unknownKeys (server.tool() would strip them silently).
|
|
5892
|
+
inputSchema: z.strictObject(reqListShape, {
|
|
5893
|
+
error: (iss: any) => iss.code === "unrecognized_keys"
|
|
5894
|
+
? `unknown parameter(s): ${(iss.keys ?? []).join(", ")}; valid parameters: ${reqListKeys.join(", ")}`
|
|
5895
|
+
: undefined,
|
|
5896
|
+
}) as any,
|
|
5879
5897
|
},
|
|
5880
|
-
async (args) => {
|
|
5898
|
+
async (args: any) => {
|
|
5881
5899
|
const q = new URLSearchParams();
|
|
5882
|
-
for (const k of ["seq", "status", "project_id", "owner", "agent_owner", "updated_since", "external_ref", "parent_id", "q", "limit", "cursor", "view"] as const) if (args[k] !== undefined) q.set(k, String(args[k]));
|
|
5900
|
+
for (const k of ["seq", "status", "project_id", "owner", "agent_owner", "tag", "updated_since", "external_ref", "parent_id", "q", "limit", "cursor", "view"] as const) if (args[k] !== undefined) q.set(k, String(args[k]));
|
|
5901
|
+
if (!q.has("view")) q.set("view", "summary");
|
|
5902
|
+
if (!q.has("limit")) q.set("limit", String(REQ_LIST_MCP_DEFAULT_LIMIT));
|
|
5883
5903
|
if (args.include_archived) q.set("include_archived", "1");
|
|
5884
5904
|
if (args.top_level) q.set("top_level", "1");
|
|
5885
5905
|
if (args.changes) q.set("changes", "1");
|
|
5886
|
-
return requirementsCall("GET", `/api/requirements
|
|
5906
|
+
return requirementsCall("GET", `/api/requirements?${q}`, args.network_id);
|
|
5887
5907
|
},
|
|
5888
5908
|
);
|
|
5889
5909
|
|