@sleep2agi/commhub-server 0.9.0-preview.139 → 0.9.0-preview.140
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-department-http.test.ts +148 -0
- package/src/chain-attempt-dedup.test.ts +447 -0
- package/src/create-node-tool-schema.test.ts +1 -0
- package/src/create-node-validate.test.ts +6 -0
- package/src/create-node-validate.ts +7 -2
- package/src/db.ts +190 -21
- package/src/departments.ts +16 -0
- package/src/env-transport-gate-http.test.ts +146 -0
- package/src/env-transport-gate.ts +74 -0
- package/src/env-transport-policy.ts +21 -0
- package/src/host-supervisor-identity-http.test.ts +20 -4
- package/src/hub-catalog-read-http.test.ts +187 -0
- package/src/node-secret-rest-http.test.ts +190 -0
- package/src/observer-avatar-http.test.ts +13 -2
- package/src/report-status-resume-id-handover.test.ts +34 -0
- package/src/report-status-secret-keys.test.ts +140 -0
- package/src/requirements-http.test.ts +1 -1
- package/src/requirements-people.ts +8 -4
- package/src/rest-projections.ts +2 -0
- package/src/schedule-agent.ts +1 -1
- package/src/scheduled-backoff-http.test.ts +193 -0
- package/src/scheduled-backoff.ts +64 -0
- package/src/scheduled-outcome-guidance-http.test.ts +139 -0
- package/src/scheduled-outcome-guidance.ts +9 -0
- package/src/scheduled-requirement-wake-http.test.ts +264 -0
- package/src/scheduled-requirement-wake.ts +125 -0
- package/src/scheduled-tasks.ts +78 -23
- package/src/scheduled-work-outcome-http.test.ts +154 -0
- package/src/scheduled-work-outcome.ts +50 -0
- package/src/scoped-secrets.ts +15 -0
- package/src/server.ts +162 -3
- package/src/tools.ts +99 -16
package/src/tools.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseScheduledWorkOutcome, qualifyScheduledWorkOutcome, saveScheduledWorkOutcome } from "./scheduled-work-outcome.js";
|
|
1
2
|
import { selectHumanInboxIds } from "./human-inbox.js";
|
|
2
3
|
import { hostSupervisorIdentityRows } from "./host-supervisor-identity.js";
|
|
3
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -728,12 +729,12 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
728
729
|
|
|
729
730
|
server.tool(
|
|
730
731
|
"report_status",
|
|
731
|
-
"Report agent status. Returns inbox_count
|
|
732
|
+
"Report agent status. Returns inbox_count.",
|
|
732
733
|
{
|
|
733
734
|
resume_id: z.string().min(1).max(200).describe("Claude Code session UUID (unique per session)"),
|
|
734
735
|
alias: z.string().min(1).max(200).describe("Human-readable session name for dispatching (e.g. 指挥室/知识哥)"),
|
|
735
736
|
status: z.enum(["working", "idle", "blocked", "error", "waiting_input", "offline"]),
|
|
736
|
-
task: z.string().max(TASK_CONTENT_MAX).optional().describe("
|
|
737
|
+
task: z.string().max(TASK_CONTENT_MAX).optional().describe("Task (session stores 200 chars; full text matches tasks.content)"),
|
|
737
738
|
output: z.string().max(50000).optional().describe("Recent output (max 4000 chars stored)"),
|
|
738
739
|
score: z.number().min(0).max(10).optional().describe("Self-score 1-10"),
|
|
739
740
|
progress: z.number().min(0).max(100).optional().describe("Progress 0-100"),
|
|
@@ -741,6 +742,15 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
741
742
|
hostname: z.string().max(200).optional().describe("Agent hostname"),
|
|
742
743
|
agent: z.string().max(100).optional().describe("Agent type (claude-code / codex / opencode)"),
|
|
743
744
|
project_dir: z.string().max(1000).optional().describe("Agent working directory"),
|
|
745
|
+
// #1198 — OS account the process runs as (platform identity only; no env fallback).
|
|
746
|
+
os_user: z.string().min(1).max(256).optional().describe("OS account"),
|
|
747
|
+
// #1474 — names of the env keys this node actually has. Never values; the
|
|
748
|
+
// node-side collector parses and drops them. Capped at 50 * 64 chars so a
|
|
749
|
+
// pathological secrets.env cannot grow this row without bound.
|
|
750
|
+
secret_keys: z.array(z.string().regex(/^[A-Z_][A-Z0-9_]{0,63}$/)).max(50).optional().describe("Env key names only"),
|
|
751
|
+
// #1510 — how the node reaches this hub. Reported by the node because only
|
|
752
|
+
// it knows the last hop. Reporting only; no write path reads it yet.
|
|
753
|
+
env_transport: z.enum(["loopback", "https", "plain"]).optional().describe("Hub link kind"),
|
|
744
754
|
version: z.string().max(100).optional().describe("Agent version"),
|
|
745
755
|
tmux_name: z.string().max(200).optional().describe("tmux session name"),
|
|
746
756
|
// V2 fields
|
|
@@ -963,7 +973,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
963
973
|
// instead of rejecting the whole report (see node-health-store.ts).
|
|
964
974
|
health: nodeHealthSchema,
|
|
965
975
|
},
|
|
966
|
-
async ({ resume_id, alias, status, task, output, score, progress, server: srv, hostname: hn, agent: ag, project_dir: pd, version: ver, tmux_name: tmux, node_id, session_id, config_path, channels, model: mdl, node_name: nn, network_id: netId, host, process_telemetry: proc, external_schedules: externalSchedules, config_snapshot: cfgSnap, rules_file_capable: rulesFileCapable, skills_capable: skillsCapable, files_capable: filesCapable, logs_capable: logsCapable, health: nodeHealth }) => {
|
|
976
|
+
async ({ resume_id, alias, status, task, output, score, progress, server: srv, hostname: hn, agent: ag, project_dir: pd, os_user: osUser, version: ver, tmux_name: tmux, node_id, session_id, config_path, channels, model: mdl, node_name: nn, network_id: netId, host, process_telemetry: proc, external_schedules: externalSchedules, config_snapshot: cfgSnap, rules_file_capable: rulesFileCapable, skills_capable: skillsCapable, files_capable: filesCapable, logs_capable: logsCapable, health: nodeHealth, secret_keys: secretKeys, env_transport: envTransport }) => {
|
|
967
977
|
const effectiveNetId = getNetworkId(netId);
|
|
968
978
|
const sessionNetId = effectiveNetId ?? "default";
|
|
969
979
|
if (!callerTokenIsNetwork || !enforceNetworkId) {
|
|
@@ -1110,7 +1120,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
1110
1120
|
channels, model, cpu_load_1min, cpu_cores, mem_total_gb, mem_used_gb, mem_avail_gb, disk_total_gb,
|
|
1111
1121
|
disk_used_gb, disk_avail_gb, process_rss_bytes, process_rss_mb, process_cpu_pct,
|
|
1112
1122
|
process_uptime_seconds, process_in_flight_count, external_schedules, registered_at,
|
|
1113
|
-
rules_file_capable, skills_capable, files_capable, logs_capable
|
|
1123
|
+
rules_file_capable, skills_capable, files_capable, logs_capable, os_user, secret_keys
|
|
1114
1124
|
FROM sessions WHERE alias = ?1 AND resume_id != ?2 AND network_id = ?3
|
|
1115
1125
|
ORDER BY updated_at DESC LIMIT 1`,
|
|
1116
1126
|
effectiveAlias, resume_id, sessionNetId,
|
|
@@ -1155,6 +1165,43 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
1155
1165
|
last_seen_at = datetime('now'), updated_at = datetime('now')`,
|
|
1156
1166
|
[resume_id, effectiveAlias, tmux ?? null, srv ?? null, hostIp, hostHostname, ag ?? null, pd ?? null, ver ?? null, storedStatus, sessionTask, trimmedOutput ?? null, progress ?? null, score ?? null, node_id ?? null, session_id ?? null, config_path ?? null, channels ?? null, sessionNetId, mdl ?? null, cpuLoad1m, cpuCores, memTotalGb, memUsedGb, memAvailGb, diskTotalGb, diskUsedGb, diskAvailGb, processRssBytes, processRssMb, processCpuPct, processUptimeSeconds, processInFlightCount, externalSchedulesJson, peerReplyInboxCapable ? 1 : 0]
|
|
1157
1167
|
);
|
|
1168
|
+
// #1198 — os_user is its own UPDATE so the INSERT/UPSERT shape stays
|
|
1169
|
+
// pinned (test698). Absent means "not reported": keep the stored value.
|
|
1170
|
+
// A resume_id handover DELETE+INSERTs a row that starts NULL, so copy
|
|
1171
|
+
// the replaced row only when this report omits the field. COALESCE
|
|
1172
|
+
// keeps a value already on the surviving row.
|
|
1173
|
+
if (osUser) {
|
|
1174
|
+
db.run(
|
|
1175
|
+
`UPDATE sessions SET os_user = ?2, updated_at = datetime('now') WHERE resume_id = ?1`,
|
|
1176
|
+
[resume_id, osUser],
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
// #1474 — its own UPDATE for the same reason as os_user: the INSERT /
|
|
1180
|
+
// UPSERT placeholder bytes stay untouched. An EMPTY array is a real
|
|
1181
|
+
// report ("this node has no secrets") and must overwrite the old list;
|
|
1182
|
+
// only `undefined` (field absent) keeps the previous value.
|
|
1183
|
+
if (envTransport) {
|
|
1184
|
+
db.run(
|
|
1185
|
+
`UPDATE sessions SET env_transport = ?2, updated_at = datetime('now') WHERE resume_id = ?1`,
|
|
1186
|
+
[resume_id, envTransport],
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
if (Array.isArray(secretKeys)) {
|
|
1190
|
+
db.run(
|
|
1191
|
+
`UPDATE sessions SET secret_keys = ?2, updated_at = datetime('now') WHERE resume_id = ?1`,
|
|
1192
|
+
[resume_id, JSON.stringify(secretKeys)],
|
|
1193
|
+
);
|
|
1194
|
+
} else if (typeof handover?.secret_keys === "string" && handover.secret_keys) {
|
|
1195
|
+
db.run(
|
|
1196
|
+
`UPDATE sessions SET secret_keys = COALESCE(secret_keys, ?2) WHERE resume_id = ?1`,
|
|
1197
|
+
[resume_id, handover.secret_keys],
|
|
1198
|
+
);
|
|
1199
|
+
} else if (typeof handover?.os_user === "string" && handover.os_user) {
|
|
1200
|
+
db.run(
|
|
1201
|
+
`UPDATE sessions SET os_user = COALESCE(os_user, ?2) WHERE resume_id = ?1`,
|
|
1202
|
+
[resume_id, handover.os_user],
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1158
1205
|
if (handover) {
|
|
1159
1206
|
// 换 resume_id 那条路径上 ON CONFLICT 不会触发;INSERT 完再把没上报(仍为 NULL)的
|
|
1160
1207
|
// 描述性列从被替换的那行接手。INSERT 语句与参数列表保持原样 —— test698 的变异
|
|
@@ -1908,8 +1955,8 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
1908
1955
|
// (db.ts) write back into A's task result + inbox — cross-tenant
|
|
1909
1956
|
// write. Verify ownership, reject on mismatch.
|
|
1910
1957
|
if (parentIn) {
|
|
1911
|
-
const parentRow = db.get<{ network_id: string | null }>(
|
|
1912
|
-
"SELECT network_id FROM tasks WHERE task_id = ?1",
|
|
1958
|
+
const parentRow = db.get<{ network_id: string | null; from_name: string; to_name: string }>(
|
|
1959
|
+
"SELECT network_id, from_name, to_name FROM tasks WHERE task_id = ?1",
|
|
1913
1960
|
[parentIn]
|
|
1914
1961
|
);
|
|
1915
1962
|
if (!parentRow) {
|
|
@@ -1924,6 +1971,24 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
1924
1971
|
ok: false, error: "cross_network_parent",
|
|
1925
1972
|
message: "parent_task_id belongs to a different network",
|
|
1926
1973
|
}) }] };
|
|
1974
|
+
} else {
|
|
1975
|
+
// Node-TMAI#4 (D): same-network is NOT sufficient. The dispatcher
|
|
1976
|
+
// must be a party to the task it claims as parent — either its
|
|
1977
|
+
// originator or its assignee. Without this, a same-network caller
|
|
1978
|
+
// could attach its dispatch to any third party's task and the
|
|
1979
|
+
// chain would later surface that child's result to the third
|
|
1980
|
+
// party's originator. Compare canonical aliases so a committed
|
|
1981
|
+
// rename on either side cannot defeat the check.
|
|
1982
|
+
const parentFromAlias = resolveCanonicalAlias(effectiveNetId, parentRow.from_name).alias;
|
|
1983
|
+
const parentToAlias = resolveCanonicalAlias(effectiveNetId, parentRow.to_name).alias;
|
|
1984
|
+
const childFromAlias = resolveCanonicalAlias(effectiveNetId, from_session).alias;
|
|
1985
|
+
if (childFromAlias !== parentFromAlias && childFromAlias !== parentToAlias) {
|
|
1986
|
+
console.log(`[${ts()}] 🚫 send_task: non-participant parent rejected, parent=${parentIn.slice(0, 8)} caller=${childFromAlias}`);
|
|
1987
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({
|
|
1988
|
+
ok: false, error: "parent_not_participant",
|
|
1989
|
+
message: "parent_task_id is not a task this sender took part in",
|
|
1990
|
+
}) }] };
|
|
1991
|
+
}
|
|
1927
1992
|
}
|
|
1928
1993
|
}
|
|
1929
1994
|
|
|
@@ -2212,6 +2277,9 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
2212
2277
|
};
|
|
2213
2278
|
const handleReply = async (args: any, peerCapabilityRequired: boolean) => {
|
|
2214
2279
|
const { alias, text, in_reply_to, status: replyStatus = "replied", from_session: _fromIn, network_id: netId, attachments, meta, thread_id: threadId, turn_id: turnId } = args;
|
|
2280
|
+
const workSignal = parseScheduledWorkOutcome(meta, replyStatus);
|
|
2281
|
+
if (!workSignal.ok) return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: workSignal.error, reply_queued: false }) }] };
|
|
2282
|
+
if (workSignal.outcome && !in_reply_to) return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: "scheduled_work_outcome_task_required", reply_queued: false }) }] };
|
|
2215
2283
|
const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
|
|
2216
2284
|
const effectiveNetId = getNetworkId(netId);
|
|
2217
2285
|
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
@@ -2297,6 +2365,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
2297
2365
|
lateSql = addScope(lateSql, lateParams, effectiveNetId);
|
|
2298
2366
|
const lateCandidate = db.get<LateTask>(lateSql, ...lateParams) ?? null;
|
|
2299
2367
|
if (lateCandidate && !["created", "delivered", "acked", "running"].includes(lateCandidate.status)) {
|
|
2368
|
+
if (workSignal.outcome) return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: "scheduled_work_outcome_terminal", reply_queued: false }) }] };
|
|
2300
2369
|
// Old runtimes do not send turn identity and retain the old result.
|
|
2301
2370
|
if (!threadId || !turnId) {
|
|
2302
2371
|
return { content: [{ type: "text" as const, text: JSON.stringify({
|
|
@@ -2460,6 +2529,17 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
2460
2529
|
|
|
2461
2530
|
}
|
|
2462
2531
|
|
|
2532
|
+
let workBinding: Extract<ReturnType<typeof qualifyScheduledWorkOutcome>, { ok: true }> | undefined;
|
|
2533
|
+
if (workSignal.outcome) {
|
|
2534
|
+
if (!in_reply_to || !callerTokenIsNetwork || !callerTokenId || !enforceNetworkId || effectiveNetId !== enforceNetworkId)
|
|
2535
|
+
return { ok: false as const, error: "scheduled_work_outcome_bound_node_required" };
|
|
2536
|
+
const token = db.get<{ bound_node_id: string | null }>(
|
|
2537
|
+
"SELECT bound_node_id FROM api_tokens WHERE token_id = ?1 AND network_id = ?2", callerTokenId, enforceNetworkId);
|
|
2538
|
+
const qualified = qualifyScheduledWorkOutcome(in_reply_to, enforceNetworkId, token?.bound_node_id ?? null);
|
|
2539
|
+
if (!qualified.ok) return qualified;
|
|
2540
|
+
workBinding = qualified;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2463
2543
|
// #507 — write meta_json on inbox insert (parity with send_task L952).
|
|
2464
2544
|
// Prior to this the attachments field on a send_reply call was
|
|
2465
2545
|
// silently stripped by MCP Zod, leaving `ok:true` with no persisted
|
|
@@ -2514,11 +2594,12 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
2514
2594
|
if (result.changes === 0) {
|
|
2515
2595
|
throw new Error(`reply_atomic_cas_failed:${in_reply_to}`);
|
|
2516
2596
|
}
|
|
2597
|
+
if (workBinding && workSignal.outcome) saveScheduledWorkOutcome(workBinding.binding, workSignal.outcome);
|
|
2517
2598
|
syncScheduledRunForTask(in_reply_to, effectiveNetId);
|
|
2518
2599
|
// #1548 —— 终态回复是最强的活性证据:一个还标着 blocked 的发送方在此回到 idle。
|
|
2519
2600
|
// 只出 blocked;working 可能真在忙别的任务,不碰。
|
|
2520
2601
|
releaseBlockedSession(from_session, effectiveNetId);
|
|
2521
|
-
return { ok: true as const, replyLogged: true };
|
|
2602
|
+
return { ok: true as const, replyLogged: true, ...(workSignal.outcome ? { workOutcome: workSignal.outcome } : {}) };
|
|
2522
2603
|
}
|
|
2523
2604
|
return { ok: true as const, replyLogged: false };
|
|
2524
2605
|
});
|
|
@@ -2632,6 +2713,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
2632
2713
|
text: JSON.stringify({
|
|
2633
2714
|
ok: true,
|
|
2634
2715
|
message_id: id,
|
|
2716
|
+
...("workOutcome" in replyOutcome ? { scheduled_work_outcome: replyOutcome.workOutcome } : {}),
|
|
2635
2717
|
session_status: session?.status ?? "unknown",
|
|
2636
2718
|
...(warning ? { warning } : {}),
|
|
2637
2719
|
...(attachmentsSaved !== null ? { attachments_saved: attachmentsSaved } : {}),
|
|
@@ -4389,6 +4471,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
4389
4471
|
// These options belong under the strict flags envelope.
|
|
4390
4472
|
opencodeGeneration: z.never().optional(),
|
|
4391
4473
|
opencodeUnsafeTools: z.never().optional(),
|
|
4474
|
+
opencodeNewServer: z.never().optional(),
|
|
4392
4475
|
channels: z.array(z.unknown()).optional(),
|
|
4393
4476
|
// app「新建节点」确认页的工作目录(绝对路径或 ~/…,daemon 侧展开并校验)。
|
|
4394
4477
|
// 缺席 = 老行为:落 daemon 的 cwd。
|
|
@@ -6556,7 +6639,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
6556
6639
|
project_id: z.string().max(200).nullable().optional(),
|
|
6557
6640
|
owner: reqPerson.nullable().optional().describe("负责人 (a person): {kind:'user', id | username} (ids / usernames from requirements_people; names resolve in the task's network); a node is rejected with owner_must_be_human — use agent_owner"),
|
|
6558
6641
|
agent_owner: reqPerson.nullable().optional().describe("负责 Agent: {kind:'node', id | alias}"),
|
|
6559
|
-
participants: z.array(reqPerson).max(100).optional().describe("REPLACES the whole list (
|
|
6642
|
+
participants: z.array(reqPerson).max(100).optional().describe("REPLACES the whole list (like owner/agent_owner; omitted people are removed, [] clears). For a few, use participants_add/participants_remove."),
|
|
6560
6643
|
external_url: z.string().max(500).nullable().optional(),
|
|
6561
6644
|
parent_id: z.string().regex(/^[A-Za-z0-9_-]{1,200}$/).nullable().optional().describe("parent requirement (same network, no cycles, ≤ 5 levels); null detaches"),
|
|
6562
6645
|
};
|
|
@@ -6591,13 +6674,13 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
6591
6674
|
parent_id: z.string().max(200).optional().describe("children of this requirement ('none' = top level)"),
|
|
6592
6675
|
department_id: z.string().max(64).optional().describe("owner (or its Agent's owner) in this department or below"),
|
|
6593
6676
|
top_level: z.boolean().optional(), q: z.string().max(200).optional(),
|
|
6594
|
-
overdue: z.boolean().optional().describe("true = open tasks
|
|
6677
|
+
overdue: z.boolean().optional().describe("true = open tasks whose due is past (date-only: before today, Asia/Shanghai); false = rest"),
|
|
6595
6678
|
due_within_days: z.number().int().min(0).max(365).optional().describe("open tasks not yet overdue, due by the end of today + N days (0 = later today)"),
|
|
6596
6679
|
limit: z.number().int().min(1).max(1000).optional().describe(`page size, default ${REQ_LIST_MCP_DEFAULT_LIMIT}`),
|
|
6597
6680
|
cursor: z.string().max(600).optional().describe("next_cursor from the previous page"),
|
|
6598
|
-
view: z.enum(["full", "summary"]).optional().describe("default summary (no
|
|
6599
|
-
changes: z.boolean().optional().describe("with updated_since: only tasks changed since then (archived
|
|
6600
|
-
include_last_event: z.boolean().optional().describe("default false; true adds last_event per row (
|
|
6681
|
+
view: z.enum(["full", "summary"]).optional().describe("default summary (no bodies; has_description/checklist_count instead); full = everything"),
|
|
6682
|
+
changes: z.boolean().optional().describe("with updated_since: only tasks changed since then (incl. archived; +deleted ids +server_time; with include_last_event also newer events)"),
|
|
6683
|
+
include_last_event: z.boolean().optional().describe("default false; true adds last_event per row (~150 B/row: type, field, actor, at, summary; null if none)"),
|
|
6601
6684
|
};
|
|
6602
6685
|
const reqListKeys = Object.keys(reqListShape);
|
|
6603
6686
|
server.registerTool(
|
|
@@ -6791,16 +6874,16 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
6791
6874
|
const schedId = z.string();
|
|
6792
6875
|
const schedFields = {
|
|
6793
6876
|
name: z.string(), task: z.string(), target_node_id: z.string().optional(),
|
|
6794
|
-
schedule: z.any().describe("once
|
|
6795
|
-
timezone: z.string().optional(), priority: z.string().optional(),
|
|
6877
|
+
schedule: z.any().describe("once|interval|daily|weekly"),
|
|
6878
|
+
timezone: z.string().optional(), priority: z.string().optional(), no_change_backoff: z.boolean().optional(),
|
|
6796
6879
|
};
|
|
6797
6880
|
server.tool("schedule_list", "Hub schedules for or by you.", { status: z.string().optional() },
|
|
6798
6881
|
async ({ status }) => scheduleCall(id => agentListSchedules(id, status)));
|
|
6799
6882
|
server.tool("schedule_get", "One schedule.", { schedule_id: schedId },
|
|
6800
6883
|
async ({ schedule_id }) => scheduleCall(id => agentGetSchedule(id, schedule_id)));
|
|
6801
|
-
server.tool("schedule_create", "
|
|
6884
|
+
server.tool("schedule_create", "Create your schedule.", schedFields,
|
|
6802
6885
|
async (args) => scheduleCall((id, w) => agentCreateSchedule(id, w, args as Record<string, unknown>)));
|
|
6803
|
-
server.tool("schedule_update", "Edit
|
|
6886
|
+
server.tool("schedule_update", "Edit yours.",
|
|
6804
6887
|
{ ...schedFields, schedule_id: schedId, base_revision: z.number().int().min(0).optional().describe("Reject if current revision differs."), name: z.string().optional(), task: z.string().optional(), schedule: z.any().optional(), status: z.string().optional() },
|
|
6805
6888
|
async ({ schedule_id, ...rest }) => scheduleCall((id, w) => agentUpdateSchedule(id, w, schedule_id, rest as Record<string, unknown>)));
|
|
6806
6889
|
server.tool("schedule_batch_interval", "Batch interval edit; per-ID results.",
|