@kynver-app/mcp-agent-os 0.3.32 → 0.3.34
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 +1 -0
- package/dist/__tests__/agent-context.test.d.ts +1 -0
- package/dist/__tests__/agent-context.test.js +36 -0
- package/dist/lib/mcp-json-result.d.ts +12 -0
- package/dist/lib/mcp-json-result.js +30 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +23 -2
- package/dist/tools/command-center.d.ts +1 -1
- package/dist/tools/command-center.js +58 -10
- package/dist/tools/tasks.js +11 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ AgentTask board (Phase 2 — batch 1 CRUD + batch 1.5 steering/lease):
|
|
|
50
50
|
- `agent_os_task_abort`
|
|
51
51
|
- `agent_os_task_claim`
|
|
52
52
|
- `agent_os_task_renew_lease`
|
|
53
|
+
- `agent_os_task_operator_priority`
|
|
53
54
|
|
|
54
55
|
AgentPlan first-class plans (Phase 5A — versioned operational artifacts
|
|
55
56
|
separate from goals/projects/tasks):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveAgentContext } from "../server.js";
|
|
3
|
+
const OLD_CONTEXT = process.env.KYNVER_AGENT_CONTEXT;
|
|
4
|
+
const OLD_OS_CONTEXT = process.env.KYNVER_AGENT_OS_CONTEXT;
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
if (OLD_CONTEXT === undefined)
|
|
7
|
+
delete process.env.KYNVER_AGENT_CONTEXT;
|
|
8
|
+
else
|
|
9
|
+
process.env.KYNVER_AGENT_CONTEXT = OLD_CONTEXT;
|
|
10
|
+
if (OLD_OS_CONTEXT === undefined)
|
|
11
|
+
delete process.env.KYNVER_AGENT_OS_CONTEXT;
|
|
12
|
+
else
|
|
13
|
+
process.env.KYNVER_AGENT_OS_CONTEXT = OLD_OS_CONTEXT;
|
|
14
|
+
});
|
|
15
|
+
describe("resolveAgentContext", () => {
|
|
16
|
+
it("prefers explicit agentContext over environment defaults", () => {
|
|
17
|
+
process.env.KYNVER_AGENT_CONTEXT = "forge";
|
|
18
|
+
process.env.KYNVER_AGENT_OS_CONTEXT = "ghost";
|
|
19
|
+
expect(resolveAgentContext(" hermes-forge ")).toBe("hermes-forge");
|
|
20
|
+
});
|
|
21
|
+
it("defaults to KYNVER_AGENT_CONTEXT when omitted", () => {
|
|
22
|
+
process.env.KYNVER_AGENT_CONTEXT = " forge ";
|
|
23
|
+
process.env.KYNVER_AGENT_OS_CONTEXT = "ghost";
|
|
24
|
+
expect(resolveAgentContext()).toBe("forge");
|
|
25
|
+
});
|
|
26
|
+
it("falls back to KYNVER_AGENT_OS_CONTEXT for compatibility", () => {
|
|
27
|
+
delete process.env.KYNVER_AGENT_CONTEXT;
|
|
28
|
+
process.env.KYNVER_AGENT_OS_CONTEXT = " hermes-forge ";
|
|
29
|
+
expect(resolveAgentContext()).toBe("hermes-forge");
|
|
30
|
+
});
|
|
31
|
+
it("omits agentContext when neither explicit nor environment context is set", () => {
|
|
32
|
+
delete process.env.KYNVER_AGENT_CONTEXT;
|
|
33
|
+
delete process.env.KYNVER_AGENT_OS_CONTEXT;
|
|
34
|
+
expect(resolveAgentContext(" ")).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type ToolResult = {
|
|
2
|
+
content: {
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
}[];
|
|
6
|
+
structuredContent?: unknown;
|
|
7
|
+
};
|
|
8
|
+
/** Default MCP text result — pretty JSON for small payloads. */
|
|
9
|
+
export declare function jsonResult(data: unknown): ToolResult;
|
|
10
|
+
/** Truncation-safe MCP result — priority keys serialize first. */
|
|
11
|
+
export declare function jsonResultOrdered(data: Record<string, unknown>, priorityKeys: readonly string[]): ToolResult;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function orderedStringifyForMcp(value, priorityKeys) {
|
|
2
|
+
const ordered = {};
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
for (const key of priorityKeys) {
|
|
5
|
+
if (key in value) {
|
|
6
|
+
ordered[key] = value[key];
|
|
7
|
+
seen.add(key);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
for (const [key, val] of Object.entries(value)) {
|
|
11
|
+
if (!seen.has(key))
|
|
12
|
+
ordered[key] = val;
|
|
13
|
+
}
|
|
14
|
+
return JSON.stringify(ordered, null, 2);
|
|
15
|
+
}
|
|
16
|
+
/** Default MCP text result — pretty JSON for small payloads. */
|
|
17
|
+
export function jsonResult(data) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
20
|
+
structuredContent: data,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Truncation-safe MCP result — priority keys serialize first. */
|
|
24
|
+
export function jsonResultOrdered(data, priorityKeys) {
|
|
25
|
+
const text = orderedStringifyForMcp(data, priorityKeys);
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text }],
|
|
28
|
+
structuredContent: data,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
* `GET /api/agent-os` `{ primarySlug }` → list first item → legacy `"ghost"`.
|
|
8
8
|
*/
|
|
9
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
export declare function resolveAgentContext(explicitAgentContext?: string): string | undefined;
|
|
10
11
|
export declare function createAgentOsServer(): McpServer;
|
package/dist/server.js
CHANGED
|
@@ -36,6 +36,13 @@ function compact(source) {
|
|
|
36
36
|
return out;
|
|
37
37
|
}
|
|
38
38
|
const MCP_LEGACY_SLUG_FALLBACK = "ghost";
|
|
39
|
+
export function resolveAgentContext(explicitAgentContext) {
|
|
40
|
+
const explicit = explicitAgentContext?.trim();
|
|
41
|
+
if (explicit)
|
|
42
|
+
return explicit;
|
|
43
|
+
const fromEnv = process.env.KYNVER_AGENT_CONTEXT?.trim() || process.env.KYNVER_AGENT_OS_CONTEXT?.trim() || "";
|
|
44
|
+
return fromEnv || undefined;
|
|
45
|
+
}
|
|
39
46
|
export function createAgentOsServer() {
|
|
40
47
|
const server = new McpServer({ name: "kynver-mcp-agent-os", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
41
48
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -79,6 +86,14 @@ export function createAgentOsServer() {
|
|
|
79
86
|
.enum(["operator", "customer"])
|
|
80
87
|
.optional()
|
|
81
88
|
.describe("Response shape for memory reads. Owners/env admins may pass operator for full diagnostics or customer to preview grantee-safe JSON. Grantees always receive customer-safe fields regardless of this flag.");
|
|
89
|
+
const contextProjectionField = z
|
|
90
|
+
.enum(["brief", "full"])
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Response shape. Default brief returns compact identity/current-work context with follow-up hints. full returns the legacy broad stats payload and can be large/truncation-prone.");
|
|
93
|
+
const agentContextField = z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Active agent/runtime/persona context within the shared AgentOS workspace, e.g. ghost, forge, or hermes-forge. Do not use slug for this; slug selects the workspace.");
|
|
82
97
|
const sourceRefSchema = z
|
|
83
98
|
.object({
|
|
84
99
|
type: z
|
|
@@ -130,9 +145,15 @@ export function createAgentOsServer() {
|
|
|
130
145
|
sourceRefs: z.array(sourceRefSchema).optional(),
|
|
131
146
|
})
|
|
132
147
|
.passthrough();
|
|
133
|
-
register("agent_os_get_context", "Get
|
|
148
|
+
register("agent_os_get_context", "Get compact startup context: identity/Soul-equivalent, key preferences, current-work pointers, memory stats, and follow-up instructions. Default brief stays small; projection=full returns the legacy broad stats payload. Pass agentContext for persona-specific context inside a shared workspace.", { slug: slugField, projection: contextProjectionField, agentContext: agentContextField }, async (args) => {
|
|
134
149
|
const s = await resolveToolSlug(args.slug);
|
|
135
|
-
|
|
150
|
+
const params = new URLSearchParams();
|
|
151
|
+
params.set("projection", args.projection === "full" ? "full" : "brief");
|
|
152
|
+
const agentContext = resolveAgentContext(args.agentContext);
|
|
153
|
+
if (agentContext) {
|
|
154
|
+
params.set("agentContext", agentContext);
|
|
155
|
+
}
|
|
156
|
+
return jsonResult(await get(`/agent-os/${s}/stats?${params}`));
|
|
136
157
|
});
|
|
137
158
|
register("agent_os_context_envelope", "Build a compact context envelope for one anchor (plan | task | goal | project | session). Returns the resolved anchor, its goal + plan (with current version body) + most-relevant task + recent session + top related memories with deduplicated source refs. Use this instead of reading giant docs when picking up an in-flight piece of work — one round trip, scoped to your workspace.", {
|
|
138
159
|
anchorType: z.enum(["plan", "task", "goal", "project", "session"]).describe("Kind of anchor to load context around."),
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* agent_os_command_center_dashboard_contract_get proxies to
|
|
8
8
|
* `.../command-center/dashboard-contract` — slim verification payload (counts,
|
|
9
|
-
* packageVersionHealth, runtime capacity, activePlanRollups) without graph/hierarchy.
|
|
9
|
+
* packageVersionHealth, runtime capacity, activePlanRollups, blockerSlo) without graph/hierarchy.
|
|
10
10
|
*/
|
|
11
11
|
type ToolResult = {
|
|
12
12
|
content: {
|
|
@@ -6,10 +6,38 @@
|
|
|
6
6
|
*
|
|
7
7
|
* agent_os_command_center_dashboard_contract_get proxies to
|
|
8
8
|
* `.../command-center/dashboard-contract` — slim verification payload (counts,
|
|
9
|
-
* packageVersionHealth, runtime capacity, activePlanRollups) without graph/hierarchy.
|
|
9
|
+
* packageVersionHealth, runtime capacity, activePlanRollups, blockerSlo) without graph/hierarchy.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { get } from "../lib/kynver-client.js";
|
|
13
|
+
import { jsonResultOrdered } from "../lib/mcp-json-result.js";
|
|
14
|
+
const COMMAND_CENTER_MCP_PRIORITY_KEYS = [
|
|
15
|
+
"agentReadable",
|
|
16
|
+
"resultContract",
|
|
17
|
+
"agentOsId",
|
|
18
|
+
"rollupVersion",
|
|
19
|
+
"generatedAt",
|
|
20
|
+
"headsUp",
|
|
21
|
+
"runningTasks",
|
|
22
|
+
"nextActions",
|
|
23
|
+
"followUps",
|
|
24
|
+
"countSnapshot",
|
|
25
|
+
"readiness",
|
|
26
|
+
"attention",
|
|
27
|
+
"runtime",
|
|
28
|
+
"packageVersionHealth",
|
|
29
|
+
"dailyLandedPrs",
|
|
30
|
+
"usefulProgress",
|
|
31
|
+
"usefulProgressLedger",
|
|
32
|
+
"reviewLanes",
|
|
33
|
+
"activePlanRollups",
|
|
34
|
+
"tasksPreview",
|
|
35
|
+
"unparentedTasksPreview",
|
|
36
|
+
"harnessRunsPreview",
|
|
37
|
+
"freshness",
|
|
38
|
+
"sourceFreshness",
|
|
39
|
+
"dashboard",
|
|
40
|
+
];
|
|
13
41
|
async function resolveAgentOsId(slug) {
|
|
14
42
|
const row = await get(`/agent-os/${encodeURIComponent(slug)}`);
|
|
15
43
|
const id = typeof row.id === "string" ? row.id.trim() : "";
|
|
@@ -25,9 +53,10 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
|
|
|
25
53
|
"prReadyNotifications (journal pr_ready events), dailyLandedPrs (journal merge counts:",
|
|
26
54
|
"today/yesterday, rolling 24h/48h, pace + agentReadable summary), runtime capacity, and",
|
|
27
55
|
"packageVersionHealth (managed npm alignment for @kynver-app/runtime).",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
56
|
+
"Default projection=compact returns a tiny navigable brief (<12k UTF-8): heads-up counts,",
|
|
57
|
+
"capped running tasks, top next actions/blockers, and follow-up tool hints. Deep detail",
|
|
58
|
+
"requires agent_os_task_get, agent_os_plan_get, agent_os_tasks_list, or graph tools.",
|
|
59
|
+
"Pass projection=full only for admin/debug (expensive, truncation-prone browser rollup).",
|
|
31
60
|
"Same aggregate as the browser Command Center. Omit slug to use env/primary workspace;",
|
|
32
61
|
"pass agentOsId when already known from a prior identity fetch.",
|
|
33
62
|
].join(" "), {
|
|
@@ -51,6 +80,10 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
|
|
|
51
80
|
.number()
|
|
52
81
|
.optional()
|
|
53
82
|
.describe("Max harness runs in the rollup (server cap 200)."),
|
|
83
|
+
projection: z
|
|
84
|
+
.enum(["compact", "full"])
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Response shape. Default compact — tiny brief flight-board (<12k). Use full only for admin/debug."),
|
|
54
87
|
}, async (args) => {
|
|
55
88
|
const slug = await resolveToolSlug(args.slug);
|
|
56
89
|
const agentOsId = typeof args.agentOsId === "string" && args.agentOsId.trim()
|
|
@@ -65,14 +98,19 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
|
|
|
65
98
|
if (typeof args.harnessLimit === "number" && Number.isFinite(args.harnessLimit)) {
|
|
66
99
|
qs.set("harnessLimit", String(args.harnessLimit));
|
|
67
100
|
}
|
|
101
|
+
const projection = args.projection === "full" ? "full" : "compact";
|
|
102
|
+
qs.set("projection", projection);
|
|
68
103
|
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
69
|
-
|
|
104
|
+
const payload = await get(`/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center${suffix}`);
|
|
105
|
+
if (projection === "compact") {
|
|
106
|
+
return jsonResultOrdered(payload, COMMAND_CENTER_MCP_PRIORITY_KEYS);
|
|
107
|
+
}
|
|
108
|
+
return jsonResult(payload);
|
|
70
109
|
});
|
|
71
110
|
register("agent_os_command_center_dashboard_contract_get", [
|
|
72
|
-
"Fetch the
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"operationalGraph or hierarchy payloads (avoids MCP truncation on large boards).",
|
|
111
|
+
"Fetch the Command Center dashboard MCP brief (default projection=mcp): same tiny flight-board",
|
|
112
|
+
"as agent_os_command_center_get compact — heads-up counts, running tasks, next actions, follow-ups.",
|
|
113
|
+
"Pass projection=slim only when you need the full dashboard verification DTO (large on big boards).",
|
|
76
114
|
].join(" "), {
|
|
77
115
|
slug: z
|
|
78
116
|
.string()
|
|
@@ -90,6 +128,10 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
|
|
|
90
128
|
.number()
|
|
91
129
|
.optional()
|
|
92
130
|
.describe("Max tasks to load when building counts (server cap 10000)."),
|
|
131
|
+
projection: z
|
|
132
|
+
.enum(["mcp", "slim"])
|
|
133
|
+
.optional()
|
|
134
|
+
.describe("mcp (default) — tiny brief. slim — full dashboard verification DTO (countSnapshot, activePlanRollups, …)."),
|
|
93
135
|
}, async (args) => {
|
|
94
136
|
const slug = await resolveToolSlug(args.slug);
|
|
95
137
|
const agentOsId = typeof args.agentOsId === "string" && args.agentOsId.trim()
|
|
@@ -101,7 +143,13 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
|
|
|
101
143
|
if (typeof args.limit === "number" && Number.isFinite(args.limit)) {
|
|
102
144
|
qs.set("limit", String(args.limit));
|
|
103
145
|
}
|
|
146
|
+
const projection = args.projection === "slim" ? "slim" : "mcp";
|
|
147
|
+
qs.set("projection", projection === "slim" ? "slim" : "mcp");
|
|
104
148
|
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
105
|
-
|
|
149
|
+
const payload = await get(`/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`);
|
|
150
|
+
if (projection === "mcp") {
|
|
151
|
+
return jsonResultOrdered(payload, COMMAND_CENTER_MCP_PRIORITY_KEYS);
|
|
152
|
+
}
|
|
153
|
+
return jsonResult(payload);
|
|
106
154
|
});
|
|
107
155
|
}
|
package/dist/tools/tasks.js
CHANGED
|
@@ -27,17 +27,15 @@ const taskStatus = z
|
|
|
27
27
|
"ready→running; the dispatcher/executor lease owns that.");
|
|
28
28
|
const taskPriority = z
|
|
29
29
|
.enum(["low", "normal", "high", "critical"])
|
|
30
|
-
.describe("Priority
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"live production/security incident — pass priorityEscalationReason quoting him or the incident. The server " +
|
|
35
|
-
"downgrades unsolicited high/critical to normal. If everything is critical, nothing is — keep most tasks at normal/low.");
|
|
30
|
+
.describe("Priority is a worker-capacity queue signal, not importance. Default 'normal' (cleanup 'low'). " +
|
|
31
|
+
"Do NOT inherit plan/goal priority. Use 'high' only when harness workers are saturated and ready work waits for slots. " +
|
|
32
|
+
"Use 'critical' only for live production/security incidents — pass priorityEscalationReason or cite in title/description. " +
|
|
33
|
+
"When slots are free, omit priority and dispatch at normal. The API downgrades importance-style high/critical.");
|
|
36
34
|
const priorityEscalationReason = z
|
|
37
35
|
.string()
|
|
38
36
|
.optional()
|
|
39
|
-
.describe("Required
|
|
40
|
-
"
|
|
37
|
+
.describe("Required for critical: quote the requesting user or name the live production/security incident. " +
|
|
38
|
+
"High does not use this field — high requires worker-capacity contention at create time.");
|
|
41
39
|
const taskExecutor = z.enum(["inline", "harness", "acp", "manual"]);
|
|
42
40
|
const taskEventType = z.enum([
|
|
43
41
|
"created",
|
|
@@ -72,8 +70,10 @@ export function registerTaskTools({ register, resolveToolSlug, jsonResult, compa
|
|
|
72
70
|
.string()
|
|
73
71
|
.optional()
|
|
74
72
|
.describe("AgentOS slug. Omit to use env or the server-discovered primary.");
|
|
75
|
-
register("agent_os_task_create", "Create an AgentTask on the board. New tasks start `ready` (dispatchable now) unless you defer them: pass scheduledFor for a timed start (→ `scheduled`) or dependsOnTaskIds to gate on upstream work (→ `waiting`); both auto-promote to `ready` when their gate clears. Idempotent within the AgentOS: pass idempotencyKey (or requestId) from the foreground turn so a Telegram retry or Ghost restart returns the existing task instead of spawning a duplicate. Response includes `created` (false = an existing task was returned).", {
|
|
76
|
-
title: z
|
|
73
|
+
register("agent_os_task_create", "Create an AgentTask on the board. New tasks start `ready` (dispatchable now) unless you defer them: pass scheduledFor for a timed start (→ `scheduled`) or dependsOnTaskIds to gate on upstream work (→ `waiting`); both auto-promote to `ready` when their gate clears. Idempotent within the AgentOS: pass idempotencyKey (or requestId) from the foreground turn so a Telegram retry or Ghost restart returns the existing task instead of spawning a duplicate. Response includes `created` (false = an existing task was returned). Do NOT create closeout-only bookkeeping tasks whose purpose is merely to close/reconcile/clean up another task — complete/update/block/cancel the existing task (or spawn a child only for genuine unresolved implementation work).", {
|
|
74
|
+
title: z
|
|
75
|
+
.string()
|
|
76
|
+
.describe("Short task title (required). Never a closeout-only sweep — mutate/close the existing task instead."),
|
|
77
77
|
description: z.string().optional(),
|
|
78
78
|
priority: taskPriority.optional(),
|
|
79
79
|
priorityEscalationReason,
|
|
@@ -126,6 +126,7 @@ export function registerTaskTools({ register, resolveToolSlug, jsonResult, compa
|
|
|
126
126
|
description: z.string().optional(),
|
|
127
127
|
status: taskStatus.optional(),
|
|
128
128
|
priority: taskPriority.optional(),
|
|
129
|
+
priorityEscalationReason,
|
|
129
130
|
executor: taskExecutor.optional(),
|
|
130
131
|
executorRef: z.string().optional(),
|
|
131
132
|
parentTaskId: z.string().optional(),
|
package/package.json
CHANGED