@kontextmind/kxm 0.6.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/.claude-plugin/marketplace.json +19 -0
- package/.kxm/README.md +14 -0
- package/.kxm/assets/README.md +5 -0
- package/.kxm/assets/retrospectives/README.md +5 -0
- package/.kxm/config/README.md +5 -0
- package/.kxm/config/agents.json +43 -0
- package/.kxm/config/env.example +56 -0
- package/.kxm/config/update.example.yaml +9 -0
- package/.kxm/config/workflows/fix.json +160 -0
- package/.kxm/config/workflows/jira-development.json +116 -0
- package/.kxm/config/workflows/provenance-quorum.json +150 -0
- package/.kxm/config/workflows/v04-dogfood.json +72 -0
- package/CHANGELOG.md +465 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/SECURITY.md +72 -0
- package/docs/README.md +48 -0
- package/docs/agent-communication-envelopes-and-gates.md +553 -0
- package/docs/architecture.md +242 -0
- package/docs/assignment-runner.md +241 -0
- package/docs/configuration.md +361 -0
- package/docs/continuous-improvement.md +114 -0
- package/docs/getting-started.md +253 -0
- package/docs/kxm-handbook.md +1090 -0
- package/docs/operations.md +205 -0
- package/docs/provenance-gates.md +291 -0
- package/docs/skills.md +45 -0
- package/docs/templates/README.md +95 -0
- package/docs/templates/adr.md +88 -0
- package/docs/templates/architecture.md +120 -0
- package/docs/templates/bug-fix.md +109 -0
- package/docs/templates/feature.md +108 -0
- package/docs/templates/handoff.md +72 -0
- package/docs/templates/postmortem.md +77 -0
- package/docs/templates/research.md +100 -0
- package/docs/templates/review.md +85 -0
- package/docs/templates/runbook.md +73 -0
- package/docs/templates/test-plan.md +87 -0
- package/docs/templates/test-report.md +72 -0
- package/docs/test-matrix.md +121 -0
- package/docs/troubleshooting.md +249 -0
- package/docs/vnext/README.md +62 -0
- package/docs/vnext/architecture.md +185 -0
- package/docs/vnext/effects-and-recovery.md +172 -0
- package/docs/vnext/lifecycles.md +235 -0
- package/docs/vnext/migration.md +220 -0
- package/docs/vnext/routing.md +184 -0
- package/docs/vnext/synchronization.md +172 -0
- package/docs/vnext/terminology.md +240 -0
- package/docs/vnext/validation.md +335 -0
- package/docs/webhook-workflows.md +240 -0
- package/docs/workflow-guide.md +1150 -0
- package/examples/README.md +102 -0
- package/examples/provenance-workflow.json +40 -0
- package/examples/requester.ts +30 -0
- package/examples/reviewer-agent.ts +29 -0
- package/examples/roundtrip.ts +46 -0
- package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
- package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
- package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
- package/examples/vnext/.kxm/agents/planner.yaml +13 -0
- package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
- package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
- package/examples/vnext/.kxm/gates.yaml +8 -0
- package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
- package/examples/vnext/.kxm/models/implementation.yaml +14 -0
- package/examples/vnext/.kxm/models/primary.yaml +17 -0
- package/examples/vnext/.kxm/prices.yaml +111 -0
- package/examples/vnext/.kxm/project/env.yaml +7 -0
- package/examples/vnext/.kxm/project.yaml +32 -0
- package/examples/vnext/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/.kxm/workflows/default.yaml +92 -0
- package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
- package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
- package/examples/vnext/README.md +53 -0
- package/examples/vnext/records/assignment-result-recorded.json +63 -0
- package/examples/vnext/records/assignment-result.json +46 -0
- package/examples/vnext/records/context-candidate.json +42 -0
- package/examples/vnext/records/delivery-manifest.json +66 -0
- package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
- package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
- package/examples/vnext/records/run-created.json +54 -0
- package/examples/vnext/records/sync-event.json +65 -0
- package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
- package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
- package/examples/workflow-signal.ts +63 -0
- package/package.json +129 -0
- package/plugins/kxm/.claude-plugin/plugin.json +73 -0
- package/plugins/kxm/.mcp.json +19 -0
- package/plugins/kxm/README.md +93 -0
- package/plugins/kxm/dist/cli.js +42853 -0
- package/plugins/kxm/dist/client.js +416 -0
- package/plugins/kxm/dist/core.js +1823 -0
- package/plugins/kxm/dist/extension.js +3797 -0
- package/plugins/kxm/dist/mcp-server.js +17104 -0
- package/plugins/kxm/dist/runtime.js +23361 -0
- package/plugins/kxm/dist/server.js +13640 -0
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
- package/plugins/kxm/package.json +12 -0
- package/plugins/kxm/skills/kxm/SKILL.md +97 -0
- package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
- package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
- package/plugins/kxm/src/arbiter.ts +355 -0
- package/plugins/kxm/src/artifacts-exist.ts +62 -0
- package/plugins/kxm/src/autocomplete.ts +236 -0
- package/plugins/kxm/src/cli.ts +3707 -0
- package/plugins/kxm/src/client.ts +614 -0
- package/plugins/kxm/src/commands.ts +1063 -0
- package/plugins/kxm/src/config.ts +290 -0
- package/plugins/kxm/src/context/providers.ts +101 -0
- package/plugins/kxm/src/context-packet.ts +332 -0
- package/plugins/kxm/src/context.ts +499 -0
- package/plugins/kxm/src/core.ts +6 -0
- package/plugins/kxm/src/database.ts +563 -0
- package/plugins/kxm/src/diagnostics.ts +184 -0
- package/plugins/kxm/src/envelope.ts +118 -0
- package/plugins/kxm/src/extension.ts +895 -0
- package/plugins/kxm/src/external-effects.ts +299 -0
- package/plugins/kxm/src/github-watch.ts +255 -0
- package/plugins/kxm/src/hub-binding.ts +160 -0
- package/plugins/kxm/src/hub.ts +2502 -0
- package/plugins/kxm/src/improve.ts +383 -0
- package/plugins/kxm/src/inbox.ts +10 -0
- package/plugins/kxm/src/kxm-install-kind.ts +113 -0
- package/plugins/kxm/src/kxm-update-config.ts +39 -0
- package/plugins/kxm/src/kxm-update.ts +238 -0
- package/plugins/kxm/src/local-snapshot.ts +406 -0
- package/plugins/kxm/src/logger.ts +198 -0
- package/plugins/kxm/src/mcp-server.ts +143 -0
- package/plugins/kxm/src/memory.ts +385 -0
- package/plugins/kxm/src/nous-pi.ts +287 -0
- package/plugins/kxm/src/nous-provider.ts +729 -0
- package/plugins/kxm/src/price-calc.ts +87 -0
- package/plugins/kxm/src/prices.ts +121 -0
- package/plugins/kxm/src/protocol.ts +172 -0
- package/plugins/kxm/src/recovery.ts +211 -0
- package/plugins/kxm/src/redact.ts +26 -0
- package/plugins/kxm/src/retrospective.ts +400 -0
- package/plugins/kxm/src/routing.ts +830 -0
- package/plugins/kxm/src/runtime.ts +9 -0
- package/plugins/kxm/src/server.ts +117 -0
- package/plugins/kxm/src/session-work.ts +571 -0
- package/plugins/kxm/src/session.ts +184 -0
- package/plugins/kxm/src/skills.ts +535 -0
- package/plugins/kxm/src/state.ts +326 -0
- package/plugins/kxm/src/store.ts +637 -0
- package/plugins/kxm/src/studio-layout.ts +268 -0
- package/plugins/kxm/src/suggest.ts +162 -0
- package/plugins/kxm/src/task-manager.ts +244 -0
- package/plugins/kxm/src/telemetry.ts +116 -0
- package/plugins/kxm/src/tui.ts +1046 -0
- package/plugins/kxm/src/vnext-bindings.ts +403 -0
- package/plugins/kxm/src/vnext-config.ts +1646 -0
- package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
- package/plugins/kxm/src/vnext-engine-command.ts +533 -0
- package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
- package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
- package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
- package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
- package/plugins/kxm/src/vnext-engine.ts +2458 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
- package/plugins/kxm/src/vnext-harness.ts +1142 -0
- package/plugins/kxm/src/vnext-init.ts +430 -0
- package/plugins/kxm/src/vnext-migrate.ts +1848 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
- package/plugins/kxm/src/vnext-permission.ts +936 -0
- package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
- package/plugins/kxm/src/vnext-repair.ts +1094 -0
- package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
- package/plugins/kxm/src/vnext-runtime.ts +663 -0
- package/plugins/kxm/src/vnext-template.ts +247 -0
- package/plugins/kxm/src/wiki.ts +313 -0
- package/plugins/kxm/src/workflow.ts +1548 -0
- package/schemas/vnext/README.md +46 -0
- package/schemas/vnext/agent.schema.json +40 -0
- package/schemas/vnext/assignment-result.schema.json +66 -0
- package/schemas/vnext/backup-manifest.schema.json +89 -0
- package/schemas/vnext/candidate.schema.json +109 -0
- package/schemas/vnext/common.schema.json +422 -0
- package/schemas/vnext/context-candidate.schema.json +76 -0
- package/schemas/vnext/context-packet.schema.json +192 -0
- package/schemas/vnext/delivery-manifest.schema.json +159 -0
- package/schemas/vnext/environment.schema.json +66 -0
- package/schemas/vnext/gate-registry.schema.json +109 -0
- package/schemas/vnext/handoff-manifest.schema.json +146 -0
- package/schemas/vnext/init-operation.schema.json +61 -0
- package/schemas/vnext/local-repository-bindings.schema.json +30 -0
- package/schemas/vnext/memory-record.schema.json +45 -0
- package/schemas/vnext/migration-decision.schema.json +26 -0
- package/schemas/vnext/migration-plan.schema.json +123 -0
- package/schemas/vnext/migration-receipt.schema.json +52 -0
- package/schemas/vnext/model.schema.json +42 -0
- package/schemas/vnext/permission-diff.schema.json +57 -0
- package/schemas/vnext/prices.schema.json +115 -0
- package/schemas/vnext/project.schema.json +85 -0
- package/schemas/vnext/repository.schema.json +24 -0
- package/schemas/vnext/run-event.schema.json +460 -0
- package/schemas/vnext/session-brief.schema.json +153 -0
- package/schemas/vnext/sync-event.schema.json +234 -0
- package/schemas/vnext/template-provenance.schema.json +38 -0
- package/schemas/vnext/workflow.schema.json +248 -0
- package/scripts/assignment-run.d.mts +354 -0
- package/scripts/assignment-run.mjs +4451 -0
- package/scripts/build-runtime.mjs +56 -0
- package/scripts/check-generated.mjs +77 -0
- package/scripts/check-versions.mjs +34 -0
- package/scripts/emit-codex-artifacts.d.mts +9 -0
- package/scripts/emit-codex-artifacts.mjs +91 -0
- package/scripts/harness-run.d.mts +83 -0
- package/scripts/harness-run.mjs +2095 -0
- package/scripts/kxm-hub.mjs +105 -0
- package/scripts/kxm-publish-npm.mjs +327 -0
- package/scripts/kxm-release-github.mjs +472 -0
- package/scripts/kxm-runtime-supervisor.mjs +7 -0
- package/scripts/kxm-worker.mjs +1127 -0
- package/scripts/kxm.mjs +27 -0
- package/scripts/roster-policy.d.mts +20 -0
- package/scripts/roster-policy.mjs +161 -0
- package/scripts/smoke-multi-pi.mjs +479 -0
|
@@ -0,0 +1,1063 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { HubClient, HubHttpError } from "./client.ts";
|
|
3
|
+
import type {
|
|
4
|
+
DeliveryMode,
|
|
5
|
+
ImprovementArea,
|
|
6
|
+
JournalCategory,
|
|
7
|
+
MessageRecord,
|
|
8
|
+
WorkflowCheckpointStatus,
|
|
9
|
+
WorkflowEvidenceInput,
|
|
10
|
+
WorkflowEvidenceReferenceInput,
|
|
11
|
+
} from "./protocol.ts";
|
|
12
|
+
|
|
13
|
+
export interface CommandSchemaProperty {
|
|
14
|
+
type: "string" | "number" | "integer" | "boolean" | "object" | "array";
|
|
15
|
+
description?: string;
|
|
16
|
+
enum?: readonly string[];
|
|
17
|
+
default?: unknown;
|
|
18
|
+
minimum?: number;
|
|
19
|
+
maximum?: number;
|
|
20
|
+
minItems?: number;
|
|
21
|
+
maxItems?: number;
|
|
22
|
+
maxProperties?: number;
|
|
23
|
+
items?: Record<string, unknown>;
|
|
24
|
+
patternProperties?: Record<string, unknown>;
|
|
25
|
+
properties?: Record<string, unknown>;
|
|
26
|
+
required?: readonly string[];
|
|
27
|
+
additionalProperties?: boolean | Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CommandJsonSchema {
|
|
31
|
+
type: "object";
|
|
32
|
+
properties: Record<string, CommandSchemaProperty>;
|
|
33
|
+
required?: readonly string[];
|
|
34
|
+
additionalProperties?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ToolPolicy {
|
|
38
|
+
preset?: string;
|
|
39
|
+
allow?: string[];
|
|
40
|
+
deny?: string[];
|
|
41
|
+
allowedTools?: string[];
|
|
42
|
+
deniedTools?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AttemptTokenPayload {
|
|
46
|
+
schema: "kxm.attempt-token.v1";
|
|
47
|
+
runId: string;
|
|
48
|
+
stepId: string;
|
|
49
|
+
stepAttempt: number;
|
|
50
|
+
assignmentId: string;
|
|
51
|
+
attemptId: string;
|
|
52
|
+
agentId: string;
|
|
53
|
+
stageId?: string;
|
|
54
|
+
attempt?: number;
|
|
55
|
+
toolPolicy?: ToolPolicy;
|
|
56
|
+
issuedAt?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SessionTokenPayload {
|
|
60
|
+
schema: "kxm.session-token.v1";
|
|
61
|
+
sessionId: string;
|
|
62
|
+
agentName?: string;
|
|
63
|
+
toolPolicy?: ToolPolicy;
|
|
64
|
+
issuedAt: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CommandExecutionContext {
|
|
68
|
+
signal?: AbortSignal | undefined;
|
|
69
|
+
inbox?: Map<string, MessageRecord> | undefined;
|
|
70
|
+
notifiedInbox?: Set<string> | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AgentCommand {
|
|
74
|
+
/** Tool name exposed in MCP and Pi, e.g. "kxm_send" */
|
|
75
|
+
name: string;
|
|
76
|
+
/** Primary CLI group, e.g. "peer" or "workflow" or "context" */
|
|
77
|
+
group: "peer" | "workflow" | "context";
|
|
78
|
+
/** Subcommand verb in CLI, e.g. "send" or "checkpoint" */
|
|
79
|
+
verb: string;
|
|
80
|
+
/** Label for UI displays */
|
|
81
|
+
label: string;
|
|
82
|
+
/** Human-readable and LLM description */
|
|
83
|
+
description: string;
|
|
84
|
+
/** JSON Schema parameters */
|
|
85
|
+
parameters: CommandJsonSchema;
|
|
86
|
+
/** Shared implementation over HubClient */
|
|
87
|
+
execute: (
|
|
88
|
+
client: HubClient,
|
|
89
|
+
args: Record<string, unknown>,
|
|
90
|
+
context?: CommandExecutionContext,
|
|
91
|
+
) => Promise<unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function requiredString(value: unknown, name: string): string {
|
|
95
|
+
if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
|
|
96
|
+
return value.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function optionalString(value: unknown): string | undefined {
|
|
100
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
104
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
105
|
+
? (value as Record<string, unknown>)
|
|
106
|
+
: {};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function optionalWorkflowContext(
|
|
110
|
+
value: unknown,
|
|
111
|
+
): { runId: string; stageId: string; requirementKey: string; attempt: number } | undefined {
|
|
112
|
+
if (value === undefined) return undefined;
|
|
113
|
+
const context = asRecord(value);
|
|
114
|
+
if (
|
|
115
|
+
!Number.isInteger(context.attempt)
|
|
116
|
+
|| (context.attempt as number) < 1
|
|
117
|
+
|| (context.attempt as number) > 20
|
|
118
|
+
) {
|
|
119
|
+
throw new Error("workflowContext.attempt must be an integer between 1 and 20");
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
runId: requiredString(context.runId, "workflowContext.runId"),
|
|
123
|
+
stageId: requiredString(context.stageId, "workflowContext.stageId"),
|
|
124
|
+
requirementKey: requiredString(context.requirementKey, "workflowContext.requirementKey"),
|
|
125
|
+
attempt: context.attempt as number,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function optionalEvidenceRefs(value: unknown): WorkflowEvidenceReferenceInput | undefined {
|
|
130
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
131
|
+
? (value as WorkflowEvidenceReferenceInput)
|
|
132
|
+
: undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isTerminalMessageError(error: unknown): boolean {
|
|
136
|
+
return (
|
|
137
|
+
error instanceof HubHttpError
|
|
138
|
+
&& (error.statusCode === 409
|
|
139
|
+
|| (error.statusCode === 404 && error.code === "message_not_found"))
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isTerminalMessage(message: MessageRecord): boolean {
|
|
144
|
+
return (
|
|
145
|
+
message.status === "replied"
|
|
146
|
+
|| message.status === "cancelled"
|
|
147
|
+
|| message.status === "expired"
|
|
148
|
+
|| message.status === "error"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function reconcileInbox(
|
|
153
|
+
client: HubClient,
|
|
154
|
+
inbox: Map<string, MessageRecord>,
|
|
155
|
+
notifiedInbox?: Set<string>,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
await Promise.all(
|
|
158
|
+
[...inbox.keys()].map(async (messageId) => {
|
|
159
|
+
try {
|
|
160
|
+
const current = await client.getMessage(messageId);
|
|
161
|
+
if (isTerminalMessage(current)) {
|
|
162
|
+
inbox.delete(messageId);
|
|
163
|
+
notifiedInbox?.delete(messageId);
|
|
164
|
+
} else {
|
|
165
|
+
inbox.set(messageId, current);
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (isTerminalMessageError(error)) {
|
|
169
|
+
inbox.delete(messageId);
|
|
170
|
+
notifiedInbox?.delete(messageId);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveProject(client: HubClient, projectArg: unknown): string {
|
|
180
|
+
const proj = optionalString(projectArg) ?? client.agent?.project;
|
|
181
|
+
if (!proj) {
|
|
182
|
+
throw new Error('missing required parameter "project"');
|
|
183
|
+
}
|
|
184
|
+
return proj;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const AGENT_COMMANDS: readonly AgentCommand[] = [
|
|
188
|
+
{
|
|
189
|
+
name: "kxm_list",
|
|
190
|
+
group: "peer",
|
|
191
|
+
verb: "list",
|
|
192
|
+
label: "List hub peers",
|
|
193
|
+
description: "List online peer agents in this project's hub pool, including their names and purposes.",
|
|
194
|
+
parameters: {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {},
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
},
|
|
199
|
+
async execute(client) {
|
|
200
|
+
return { agents: await client.listAgents() };
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "kxm_send",
|
|
205
|
+
group: "peer",
|
|
206
|
+
verb: "send",
|
|
207
|
+
label: "Send peer request",
|
|
208
|
+
description:
|
|
209
|
+
"Send a focused request to a peer agent. Returns a message ID for kxm_get or kxm_await. For durable peer evidence, workflowContext is the hub-authorized provenance scope; correlation and idempotency are transport concerns and do not establish evidence provenance.",
|
|
210
|
+
parameters: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
target: { type: "string", description: "Peer name or agent ID" },
|
|
214
|
+
content: { type: "string", description: "Focused request with the expected response or artifact" },
|
|
215
|
+
delivery: {
|
|
216
|
+
type: "string",
|
|
217
|
+
enum: ["steer", "followUp", "nextTurn"],
|
|
218
|
+
default: "followUp",
|
|
219
|
+
description: "followUp is the safe default; use steer only for active blockers",
|
|
220
|
+
},
|
|
221
|
+
correlationId: {
|
|
222
|
+
type: "string",
|
|
223
|
+
description: "Optional task grouping; the hub binds it to runId when workflowContext is supplied",
|
|
224
|
+
},
|
|
225
|
+
idempotencyKey: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "Retry/deduplication key only; not a workflow security or evidence binding",
|
|
228
|
+
},
|
|
229
|
+
workflowContext: {
|
|
230
|
+
type: "object",
|
|
231
|
+
description: "Requested provenance scope; the hub authorizes and persists the canonical binding",
|
|
232
|
+
properties: {
|
|
233
|
+
runId: { type: "string", description: "Active durable workflow run ID" },
|
|
234
|
+
stageId: { type: "string", description: "Active workflow stage ID" },
|
|
235
|
+
requirementKey: { type: "string", description: "Required evidence identity this peer reply may satisfy" },
|
|
236
|
+
attempt: { type: "integer", minimum: 1, maximum: 20, description: "Current one-based stage attempt" },
|
|
237
|
+
},
|
|
238
|
+
required: ["runId", "stageId", "requirementKey", "attempt"],
|
|
239
|
+
additionalProperties: false,
|
|
240
|
+
},
|
|
241
|
+
ttlMs: { type: "number", minimum: 1_000, maximum: 604_800_000, description: "Message TTL in milliseconds" },
|
|
242
|
+
},
|
|
243
|
+
required: ["target", "content"],
|
|
244
|
+
additionalProperties: false,
|
|
245
|
+
},
|
|
246
|
+
async execute(client, args) {
|
|
247
|
+
const delivery = optionalString(args.delivery) as DeliveryMode | undefined;
|
|
248
|
+
const correlationId = optionalString(args.correlationId);
|
|
249
|
+
const idempotencyKey = optionalString(args.idempotencyKey);
|
|
250
|
+
const workflowContext = optionalWorkflowContext(args.workflowContext);
|
|
251
|
+
const message = await client.send({
|
|
252
|
+
target: requiredString(args.target, "target"),
|
|
253
|
+
content: requiredString(args.content, "content"),
|
|
254
|
+
...(delivery ? { delivery } : {}),
|
|
255
|
+
...(correlationId ? { correlationId } : {}),
|
|
256
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
257
|
+
...(workflowContext ? { workflowContext } : {}),
|
|
258
|
+
...(typeof args.ttlMs === "number" ? { ttlMs: args.ttlMs } : {}),
|
|
259
|
+
});
|
|
260
|
+
return { messageId: message.id, status: message.status, target: message.toName };
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "kxm_get",
|
|
265
|
+
group: "peer",
|
|
266
|
+
verb: "get",
|
|
267
|
+
label: "Get peer request",
|
|
268
|
+
description: "Check the status and optional reply for a previously sent request.",
|
|
269
|
+
parameters: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
messageId: { type: "string", description: "Message ID of the sent request" },
|
|
273
|
+
},
|
|
274
|
+
required: ["messageId"],
|
|
275
|
+
additionalProperties: false,
|
|
276
|
+
},
|
|
277
|
+
async execute(client, args) {
|
|
278
|
+
return await client.getMessage(requiredString(args.messageId, "messageId"));
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "kxm_fanout",
|
|
283
|
+
group: "peer",
|
|
284
|
+
verb: "fanout",
|
|
285
|
+
label: "Fanout peer requests",
|
|
286
|
+
description:
|
|
287
|
+
"Ask one through three peers independently and return replies for comparison and synthesis. A local timeout or request cancellation returns a pending response with a durable messageId for kxm_get or an exact retry.",
|
|
288
|
+
parameters: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
targets: {
|
|
292
|
+
type: "array",
|
|
293
|
+
items: { type: "string" },
|
|
294
|
+
minItems: 1,
|
|
295
|
+
maxItems: 3,
|
|
296
|
+
description: "One through three target peer names or agent IDs",
|
|
297
|
+
},
|
|
298
|
+
content: { type: "string", description: "Task description sent to all targets" },
|
|
299
|
+
correlationId: {
|
|
300
|
+
type: "string",
|
|
301
|
+
description: "Optional task grouping; the hub binds it to runId when workflowContext is supplied",
|
|
302
|
+
},
|
|
303
|
+
idempotencyKeyPrefix: {
|
|
304
|
+
type: "string",
|
|
305
|
+
description: "Stable retry/deduplication prefix only; not a workflow security or evidence binding",
|
|
306
|
+
},
|
|
307
|
+
workflowContext: {
|
|
308
|
+
type: "object",
|
|
309
|
+
description: "Requested provenance scope shared by each request",
|
|
310
|
+
properties: {
|
|
311
|
+
runId: { type: "string", description: "Active durable workflow run ID" },
|
|
312
|
+
stageId: { type: "string", description: "Active workflow stage ID" },
|
|
313
|
+
requirementKey: { type: "string", description: "Required evidence identity these peer replies may satisfy" },
|
|
314
|
+
attempt: { type: "integer", minimum: 1, maximum: 20, description: "Current one-based stage attempt" },
|
|
315
|
+
},
|
|
316
|
+
required: ["runId", "stageId", "requirementKey", "attempt"],
|
|
317
|
+
additionalProperties: false,
|
|
318
|
+
},
|
|
319
|
+
ttlMs: { type: "number", minimum: 1_000, maximum: 604_800_000, description: "Message TTL in milliseconds" },
|
|
320
|
+
timeoutMs: { type: "number", minimum: 100, maximum: 1_800_000, description: "Client wait timeout in milliseconds" },
|
|
321
|
+
},
|
|
322
|
+
required: ["targets", "content"],
|
|
323
|
+
additionalProperties: false,
|
|
324
|
+
},
|
|
325
|
+
async execute(client, args, context) {
|
|
326
|
+
const targets = Array.isArray(args.targets)
|
|
327
|
+
? args.targets.map((t) => requiredString(t, "target"))
|
|
328
|
+
: [];
|
|
329
|
+
return {
|
|
330
|
+
responses: await client.fanout({
|
|
331
|
+
targets,
|
|
332
|
+
content: requiredString(args.content, "content"),
|
|
333
|
+
...(optionalString(args.correlationId) ? { correlationId: optionalString(args.correlationId)! } : {}),
|
|
334
|
+
...(optionalString(args.idempotencyKeyPrefix)
|
|
335
|
+
? { idempotencyKeyPrefix: optionalString(args.idempotencyKeyPrefix)! }
|
|
336
|
+
: {}),
|
|
337
|
+
...(optionalWorkflowContext(args.workflowContext)
|
|
338
|
+
? { workflowContext: optionalWorkflowContext(args.workflowContext)! }
|
|
339
|
+
: {}),
|
|
340
|
+
...(typeof args.ttlMs === "number" ? { ttlMs: args.ttlMs } : {}),
|
|
341
|
+
...(typeof args.timeoutMs === "number" ? { timeoutMs: args.timeoutMs } : {}),
|
|
342
|
+
...(context?.signal ? { signal: context.signal } : {}),
|
|
343
|
+
}),
|
|
344
|
+
};
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: "kxm_await",
|
|
349
|
+
group: "peer",
|
|
350
|
+
verb: "await",
|
|
351
|
+
label: "Await peer response",
|
|
352
|
+
description:
|
|
353
|
+
"Wait until a sent request receives a reply or reaches a terminal error. Capped at 60 seconds (60000ms); longer waits are workflow wait steps.",
|
|
354
|
+
parameters: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
messageId: { type: "string", description: "Message ID of the sent request" },
|
|
358
|
+
timeoutMs: {
|
|
359
|
+
type: "number",
|
|
360
|
+
minimum: 100,
|
|
361
|
+
maximum: 60_000,
|
|
362
|
+
default: 60_000,
|
|
363
|
+
description: "Timeout in milliseconds (capped at 60 seconds)",
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
required: ["messageId"],
|
|
367
|
+
additionalProperties: false,
|
|
368
|
+
},
|
|
369
|
+
async execute(client, args, context) {
|
|
370
|
+
const timeoutMs = Math.min(
|
|
371
|
+
typeof args.timeoutMs === "number" ? args.timeoutMs : 60_000,
|
|
372
|
+
60_000,
|
|
373
|
+
);
|
|
374
|
+
return await client.awaitResponse(
|
|
375
|
+
requiredString(args.messageId, "messageId"),
|
|
376
|
+
timeoutMs,
|
|
377
|
+
context?.signal,
|
|
378
|
+
);
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: "kxm_cancel",
|
|
383
|
+
group: "peer",
|
|
384
|
+
verb: "cancel",
|
|
385
|
+
label: "Cancel peer request",
|
|
386
|
+
description: "Cancel a queued or delivered request sent by this agent.",
|
|
387
|
+
parameters: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
messageId: { type: "string", description: "Message ID of the request to cancel" },
|
|
391
|
+
},
|
|
392
|
+
required: ["messageId"],
|
|
393
|
+
additionalProperties: false,
|
|
394
|
+
},
|
|
395
|
+
async execute(client, args) {
|
|
396
|
+
return await client.cancel(requiredString(args.messageId, "messageId"));
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: "kxm_inbox",
|
|
401
|
+
group: "peer",
|
|
402
|
+
verb: "inbox",
|
|
403
|
+
label: "List inbound requests",
|
|
404
|
+
description: "List inbound peer requests awaiting a reply.",
|
|
405
|
+
parameters: {
|
|
406
|
+
type: "object",
|
|
407
|
+
properties: {},
|
|
408
|
+
additionalProperties: false,
|
|
409
|
+
},
|
|
410
|
+
async execute(client, _args, context) {
|
|
411
|
+
if (context?.inbox) {
|
|
412
|
+
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
413
|
+
return { messages: [...context.inbox.values()] };
|
|
414
|
+
}
|
|
415
|
+
return { messages: [] };
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: "kxm_reply",
|
|
420
|
+
group: "peer",
|
|
421
|
+
verb: "reply",
|
|
422
|
+
label: "Reply to peer request",
|
|
423
|
+
description: "Reply to an inbound peer request using its message ID.",
|
|
424
|
+
parameters: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
messageId: { type: "string", description: "Message ID of the inbound request" },
|
|
428
|
+
content: { type: "string", description: "Final response with evidence and remaining risks" },
|
|
429
|
+
},
|
|
430
|
+
required: ["messageId", "content"],
|
|
431
|
+
additionalProperties: false,
|
|
432
|
+
},
|
|
433
|
+
async execute(client, args, context) {
|
|
434
|
+
const messageId = requiredString(args.messageId, "messageId");
|
|
435
|
+
try {
|
|
436
|
+
const message = await client.reply(messageId, requiredString(args.content, "content"));
|
|
437
|
+
if (context?.inbox) {
|
|
438
|
+
context.inbox.delete(messageId);
|
|
439
|
+
context.notifiedInbox?.delete(messageId);
|
|
440
|
+
}
|
|
441
|
+
return { messageId, status: message.status, recipient: message.fromName };
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (context?.inbox && isTerminalMessageError(error)) {
|
|
444
|
+
context.inbox.delete(messageId);
|
|
445
|
+
context.notifiedInbox?.delete(messageId);
|
|
446
|
+
}
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "kxm_workflow_list",
|
|
453
|
+
group: "workflow",
|
|
454
|
+
verb: "runs",
|
|
455
|
+
label: "List workflow runs",
|
|
456
|
+
description: "List durable webhook workflows assigned to this agent.",
|
|
457
|
+
parameters: {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {},
|
|
460
|
+
additionalProperties: false,
|
|
461
|
+
},
|
|
462
|
+
async execute(client) {
|
|
463
|
+
return { runs: await client.listWorkflows() };
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: "kxm_workflow_get",
|
|
468
|
+
group: "workflow",
|
|
469
|
+
verb: "run",
|
|
470
|
+
label: "Get workflow run",
|
|
471
|
+
description: "Get a workflow's stages and journal of plans, decisions, contradictions, errors, and lessons.",
|
|
472
|
+
parameters: {
|
|
473
|
+
type: "object",
|
|
474
|
+
properties: {
|
|
475
|
+
runId: { type: "string", description: "Workflow run ID" },
|
|
476
|
+
},
|
|
477
|
+
required: ["runId"],
|
|
478
|
+
additionalProperties: false,
|
|
479
|
+
},
|
|
480
|
+
async execute(client, args) {
|
|
481
|
+
return await client.getWorkflow(requiredString(args.runId, "runId"));
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "kxm_workflow_checkpoint",
|
|
486
|
+
group: "workflow",
|
|
487
|
+
verb: "checkpoint",
|
|
488
|
+
label: "Checkpoint workflow stage",
|
|
489
|
+
description:
|
|
490
|
+
"Record a stage result with evidence keyed by the stage's required evidence identities. Cite peer-reply requirements through evidenceRefs so the hub can verify provenance and quorum; caller-authored evidence strings cannot satisfy those policies. Warnings and failures require another attempt until passed or exhausted.",
|
|
491
|
+
parameters: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
runId: { type: "string", description: "Active durable workflow run ID" },
|
|
495
|
+
stageId: { type: "string", description: "Active stage ID" },
|
|
496
|
+
status: { type: "string", enum: ["passed", "warning", "failed"], description: "Stage outcome" },
|
|
497
|
+
summary: { type: "string", description: "Summary of changes, verification, and remaining risks" },
|
|
498
|
+
evidence: {
|
|
499
|
+
type: "object",
|
|
500
|
+
additionalProperties: { type: "string" },
|
|
501
|
+
maxProperties: 64,
|
|
502
|
+
description: "Key-value evidence mapping required keys to proof strings",
|
|
503
|
+
},
|
|
504
|
+
evidenceRefs: {
|
|
505
|
+
type: "object",
|
|
506
|
+
description: "Peer evidence references keyed by required evidence identity",
|
|
507
|
+
patternProperties: {
|
|
508
|
+
"^(.*)$": {
|
|
509
|
+
type: "object",
|
|
510
|
+
properties: {
|
|
511
|
+
messageIds: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 16 },
|
|
512
|
+
},
|
|
513
|
+
required: ["messageIds"],
|
|
514
|
+
additionalProperties: false,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
additionalProperties: {
|
|
518
|
+
type: "object",
|
|
519
|
+
properties: {
|
|
520
|
+
messageIds: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 16 },
|
|
521
|
+
},
|
|
522
|
+
required: ["messageIds"],
|
|
523
|
+
additionalProperties: false,
|
|
524
|
+
},
|
|
525
|
+
maxProperties: 32,
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
required: ["runId", "stageId", "status", "summary"],
|
|
529
|
+
additionalProperties: false,
|
|
530
|
+
},
|
|
531
|
+
async execute(client, args) {
|
|
532
|
+
return await client.checkpointWorkflow(requiredString(args.runId, "runId"), {
|
|
533
|
+
stageId: requiredString(args.stageId, "stageId"),
|
|
534
|
+
status: requiredString(args.status, "status") as WorkflowCheckpointStatus,
|
|
535
|
+
summary: requiredString(args.summary, "summary"),
|
|
536
|
+
...(args.evidence && typeof args.evidence === "object" && !Array.isArray(args.evidence)
|
|
537
|
+
? { evidence: args.evidence as WorkflowEvidenceInput }
|
|
538
|
+
: {}),
|
|
539
|
+
...(optionalEvidenceRefs(args.evidenceRefs)
|
|
540
|
+
? { evidenceRefs: optionalEvidenceRefs(args.evidenceRefs)! }
|
|
541
|
+
: {}),
|
|
542
|
+
});
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "kxm_workflow_record",
|
|
547
|
+
group: "workflow",
|
|
548
|
+
verb: "record",
|
|
549
|
+
label: "Record workflow journal entry",
|
|
550
|
+
description: "Record a plan, decision, contradiction, error, or lesson for continuous improvement.",
|
|
551
|
+
parameters: {
|
|
552
|
+
type: "object",
|
|
553
|
+
properties: {
|
|
554
|
+
runId: { type: "string", description: "Active durable workflow run ID" },
|
|
555
|
+
category: {
|
|
556
|
+
type: "string",
|
|
557
|
+
enum: ["plan", "decision", "contradiction", "error", "lesson"],
|
|
558
|
+
description: "Category of journal entry",
|
|
559
|
+
},
|
|
560
|
+
area: {
|
|
561
|
+
type: "string",
|
|
562
|
+
enum: ["harness", "gates", "implementation", "workflow", "documentation", "security", "other"],
|
|
563
|
+
description: "System area",
|
|
564
|
+
},
|
|
565
|
+
severity: {
|
|
566
|
+
type: "string",
|
|
567
|
+
enum: ["info", "warning", "error"],
|
|
568
|
+
default: "info",
|
|
569
|
+
description: "Severity level",
|
|
570
|
+
},
|
|
571
|
+
summary: { type: "string", description: "Concise description of the observation or decision" },
|
|
572
|
+
details: { type: "string", description: "Extended details, context, and reasoning" },
|
|
573
|
+
evidence: {
|
|
574
|
+
type: "array",
|
|
575
|
+
items: { type: "string" },
|
|
576
|
+
maxItems: 32,
|
|
577
|
+
description: "Durable evidence strings or URIs",
|
|
578
|
+
},
|
|
579
|
+
relatedEntryIds: {
|
|
580
|
+
type: "array",
|
|
581
|
+
items: { type: "string" },
|
|
582
|
+
maxItems: 16,
|
|
583
|
+
description: "Related previous journal entry IDs",
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
required: ["runId", "category", "area", "summary"],
|
|
587
|
+
additionalProperties: false,
|
|
588
|
+
},
|
|
589
|
+
async execute(client, args) {
|
|
590
|
+
return await client.recordWorkflowEntry(requiredString(args.runId, "runId"), {
|
|
591
|
+
category: requiredString(args.category, "category") as JournalCategory,
|
|
592
|
+
area: requiredString(args.area, "area") as ImprovementArea,
|
|
593
|
+
...(optionalString(args.severity)
|
|
594
|
+
? { severity: optionalString(args.severity) as "info" | "warning" | "error" }
|
|
595
|
+
: {}),
|
|
596
|
+
summary: requiredString(args.summary, "summary"),
|
|
597
|
+
...(optionalString(args.details) ? { details: optionalString(args.details)! } : {}),
|
|
598
|
+
...(Array.isArray(args.evidence) ? { evidence: args.evidence as string[] } : {}),
|
|
599
|
+
...(Array.isArray(args.relatedEntryIds) ? { relatedEntryIds: args.relatedEntryIds as string[] } : {}),
|
|
600
|
+
});
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: "kxm_workflow_wait",
|
|
605
|
+
group: "workflow",
|
|
606
|
+
verb: "wait",
|
|
607
|
+
label: "Wait for workflow signal",
|
|
608
|
+
description:
|
|
609
|
+
"Pause the active stage until a signed external callback checkpoints it and resumes the coordinator. Cite peer-reply requirements through evidenceRefs so the hub can verify provenance and quorum; verified evidence is accumulated with callback evidence.",
|
|
610
|
+
parameters: {
|
|
611
|
+
type: "object",
|
|
612
|
+
properties: {
|
|
613
|
+
runId: { type: "string", description: "Active durable workflow run ID" },
|
|
614
|
+
stageId: { type: "string", description: "Active stage ID" },
|
|
615
|
+
signalKey: { type: "string", description: "Stable callback key, such as github-pr-42-checks" },
|
|
616
|
+
summary: { type: "string", description: "What is running externally and what result is expected" },
|
|
617
|
+
evidence: {
|
|
618
|
+
type: "object",
|
|
619
|
+
additionalProperties: { type: "string" },
|
|
620
|
+
maxProperties: 64,
|
|
621
|
+
description: "Evidence gathered before the wait",
|
|
622
|
+
},
|
|
623
|
+
evidenceRefs: {
|
|
624
|
+
type: "object",
|
|
625
|
+
description: "Peer evidence references verified before waiting",
|
|
626
|
+
patternProperties: {
|
|
627
|
+
"^(.*)$": {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
messageIds: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 16 },
|
|
631
|
+
},
|
|
632
|
+
required: ["messageIds"],
|
|
633
|
+
additionalProperties: false,
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
additionalProperties: {
|
|
637
|
+
type: "object",
|
|
638
|
+
properties: {
|
|
639
|
+
messageIds: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 16 },
|
|
640
|
+
},
|
|
641
|
+
required: ["messageIds"],
|
|
642
|
+
additionalProperties: false,
|
|
643
|
+
},
|
|
644
|
+
maxProperties: 32,
|
|
645
|
+
},
|
|
646
|
+
timeoutMs: {
|
|
647
|
+
type: "number",
|
|
648
|
+
minimum: 1_000,
|
|
649
|
+
maximum: 2_592_000_000,
|
|
650
|
+
description: "Maximum wait duration in milliseconds",
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
required: ["runId", "stageId", "signalKey", "summary"],
|
|
654
|
+
additionalProperties: false,
|
|
655
|
+
},
|
|
656
|
+
async execute(client, args) {
|
|
657
|
+
return await client.waitForWorkflowSignal(requiredString(args.runId, "runId"), {
|
|
658
|
+
stageId: requiredString(args.stageId, "stageId"),
|
|
659
|
+
signalKey: requiredString(args.signalKey, "signalKey"),
|
|
660
|
+
summary: requiredString(args.summary, "summary"),
|
|
661
|
+
...(args.evidence && typeof args.evidence === "object" && !Array.isArray(args.evidence)
|
|
662
|
+
? { evidence: args.evidence as WorkflowEvidenceInput }
|
|
663
|
+
: {}),
|
|
664
|
+
...(optionalEvidenceRefs(args.evidenceRefs)
|
|
665
|
+
? { evidenceRefs: optionalEvidenceRefs(args.evidenceRefs)! }
|
|
666
|
+
: {}),
|
|
667
|
+
...(typeof args.timeoutMs === "number" ? { timeoutMs: args.timeoutMs } : {}),
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "kxm_improvement_report",
|
|
673
|
+
group: "workflow",
|
|
674
|
+
verb: "improve-report",
|
|
675
|
+
label: "Summarize improvement report",
|
|
676
|
+
description: "Summarize workflow errors, contradictions, and lessons by improvement area.",
|
|
677
|
+
parameters: {
|
|
678
|
+
type: "object",
|
|
679
|
+
properties: {},
|
|
680
|
+
additionalProperties: false,
|
|
681
|
+
},
|
|
682
|
+
async execute(client) {
|
|
683
|
+
return await client.improvementReport();
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "kxm_context",
|
|
688
|
+
group: "context",
|
|
689
|
+
verb: "get",
|
|
690
|
+
label: "Get KXM context packet",
|
|
691
|
+
description:
|
|
692
|
+
"Normal entry point for KXM context. Assembles a token-budgeted role-aware context packet from durable journal evidence, temporal state, knowledge, episodes, and skills. Superseded and rejected records are excluded. Use KXM context tools instead of provider-specific memory APIs.",
|
|
693
|
+
parameters: {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
project: { type: "string", description: "Project scope (must be the client's project)" },
|
|
697
|
+
role: {
|
|
698
|
+
type: "string",
|
|
699
|
+
description: "Requesting role: repro, planner, critic, implementer, verifier, or custom",
|
|
700
|
+
},
|
|
701
|
+
task: { type: "string", description: "What the role is trying to accomplish" },
|
|
702
|
+
workflowRunId: { type: "string", description: "Workflow run scope" },
|
|
703
|
+
stageId: { type: "string", description: "Workflow stage scope" },
|
|
704
|
+
budgetTokens: { type: "integer", description: "Token budget; defaults to the role policy" },
|
|
705
|
+
includeKinds: {
|
|
706
|
+
type: "array",
|
|
707
|
+
items: { type: "string", enum: ["evidence", "state", "episode", "knowledge", "skill"] },
|
|
708
|
+
description: "Restrict packet to these item kinds",
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
required: ["role", "task"],
|
|
712
|
+
additionalProperties: false,
|
|
713
|
+
},
|
|
714
|
+
async execute(client, args) {
|
|
715
|
+
return await client.contextGet({
|
|
716
|
+
project: resolveProject(client, args.project),
|
|
717
|
+
role: requiredString(args.role, "role"),
|
|
718
|
+
task: requiredString(args.task, "task"),
|
|
719
|
+
...(optionalString(args.workflowRunId) ? { workflowRunId: optionalString(args.workflowRunId)! } : {}),
|
|
720
|
+
...(optionalString(args.stageId) ? { stageId: optionalString(args.stageId)! } : {}),
|
|
721
|
+
...(typeof args.budgetTokens === "number" ? { budgetTokens: args.budgetTokens } : {}),
|
|
722
|
+
...(Array.isArray(args.includeKinds) ? { includeKinds: args.includeKinds as any } : {}),
|
|
723
|
+
});
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
name: "kxm_recall",
|
|
728
|
+
group: "context",
|
|
729
|
+
verb: "recall",
|
|
730
|
+
label: "Recall context metadata",
|
|
731
|
+
description: "Search durable context records for a project by query; returns bounded metadata only.",
|
|
732
|
+
parameters: {
|
|
733
|
+
type: "object",
|
|
734
|
+
properties: {
|
|
735
|
+
project: { type: "string", description: "Project identifier" },
|
|
736
|
+
query: { type: "string", description: "Query string" },
|
|
737
|
+
kinds: { type: "array", items: { type: "string" }, description: "Kinds filter" },
|
|
738
|
+
limit: { type: "integer", minimum: 1, maximum: 100, description: "Maximum results" },
|
|
739
|
+
},
|
|
740
|
+
additionalProperties: false,
|
|
741
|
+
},
|
|
742
|
+
async execute(client, args) {
|
|
743
|
+
return await client.contextRecall({
|
|
744
|
+
project: resolveProject(client, args.project),
|
|
745
|
+
...(optionalString(args.query) ? { query: optionalString(args.query)! } : {}),
|
|
746
|
+
...(Array.isArray(args.kinds) ? { kinds: args.kinds as any } : {}),
|
|
747
|
+
...(typeof args.limit === "number" ? { limit: args.limit } : {}),
|
|
748
|
+
});
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "kxm_state",
|
|
753
|
+
group: "context",
|
|
754
|
+
verb: "state",
|
|
755
|
+
label: "Get temporal state",
|
|
756
|
+
description: "Current value for one temporal state key, optionally as of a historical timestamp.",
|
|
757
|
+
parameters: {
|
|
758
|
+
type: "object",
|
|
759
|
+
properties: {
|
|
760
|
+
project: { type: "string", description: "Project identifier" },
|
|
761
|
+
key: { type: "string", description: "State key" },
|
|
762
|
+
asOf: { type: "string", description: "ISO-8601 timestamp for historical queries" },
|
|
763
|
+
},
|
|
764
|
+
required: ["key"],
|
|
765
|
+
additionalProperties: false,
|
|
766
|
+
},
|
|
767
|
+
async execute(client, args) {
|
|
768
|
+
return await client.contextState({
|
|
769
|
+
project: resolveProject(client, args.project),
|
|
770
|
+
key: requiredString(args.key, "key"),
|
|
771
|
+
...(optionalString(args.asOf) ? { asOf: optionalString(args.asOf)! } : {}),
|
|
772
|
+
});
|
|
773
|
+
},
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
name: "kxm_episode",
|
|
777
|
+
group: "context",
|
|
778
|
+
verb: "episode",
|
|
779
|
+
label: "Get workflow episodes",
|
|
780
|
+
description: "Episodic learning from workflow journals: errors, lessons, observations, experiments for a project.",
|
|
781
|
+
parameters: {
|
|
782
|
+
type: "object",
|
|
783
|
+
properties: {
|
|
784
|
+
project: { type: "string", description: "Project identifier" },
|
|
785
|
+
workflowRunId: { type: "string", description: "Optional workflow run scope" },
|
|
786
|
+
},
|
|
787
|
+
additionalProperties: false,
|
|
788
|
+
},
|
|
789
|
+
async execute(client, args) {
|
|
790
|
+
return await client.contextEpisode({
|
|
791
|
+
project: resolveProject(client, args.project),
|
|
792
|
+
...(optionalString(args.workflowRunId) ? { workflowRunId: optionalString(args.workflowRunId)! } : {}),
|
|
793
|
+
});
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "kxm_promote",
|
|
798
|
+
group: "context",
|
|
799
|
+
verb: "promote",
|
|
800
|
+
label: "Propose state promotion",
|
|
801
|
+
description: "Propose a change to one authoritative state key. Promotion requires durable evidence.",
|
|
802
|
+
parameters: {
|
|
803
|
+
type: "object",
|
|
804
|
+
properties: {
|
|
805
|
+
project: { type: "string", description: "Project identifier" },
|
|
806
|
+
key: { type: "string", description: "State key to promote" },
|
|
807
|
+
summary: { type: "string", description: "Promotion summary" },
|
|
808
|
+
authority: {
|
|
809
|
+
type: "string",
|
|
810
|
+
enum: ["policy", "instruction", "evidence", "hypothesis"],
|
|
811
|
+
description: "Authority class",
|
|
812
|
+
},
|
|
813
|
+
confidence: {
|
|
814
|
+
type: "string",
|
|
815
|
+
enum: ["verified", "probable", "uncertain"],
|
|
816
|
+
description: "Confidence level",
|
|
817
|
+
},
|
|
818
|
+
evidenceRefs: {
|
|
819
|
+
type: "array",
|
|
820
|
+
items: { type: "string" },
|
|
821
|
+
minItems: 1,
|
|
822
|
+
maxItems: 32,
|
|
823
|
+
description: "Evidence item references backing the promotion",
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
required: ["key", "summary", "authority", "confidence", "evidenceRefs"],
|
|
827
|
+
additionalProperties: false,
|
|
828
|
+
},
|
|
829
|
+
async execute(client, args) {
|
|
830
|
+
return await client.contextStatePropose({
|
|
831
|
+
project: resolveProject(client, args.project),
|
|
832
|
+
key: requiredString(args.key, "key"),
|
|
833
|
+
summary: requiredString(args.summary, "summary"),
|
|
834
|
+
authority: requiredString(args.authority, "authority") as any,
|
|
835
|
+
confidence: requiredString(args.confidence, "confidence") as any,
|
|
836
|
+
evidenceRefs: Array.isArray(args.evidenceRefs) ? (args.evidenceRefs as string[]) : [],
|
|
837
|
+
});
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
];
|
|
841
|
+
|
|
842
|
+
export const AGENT_COMMANDS_MAP: ReadonlyMap<string, AgentCommand> = new Map(
|
|
843
|
+
AGENT_COMMANDS.map((cmd) => [cmd.name, cmd]),
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
export function getMcpTools(): Array<{
|
|
847
|
+
name: string;
|
|
848
|
+
description: string;
|
|
849
|
+
inputSchema: Record<string, unknown>;
|
|
850
|
+
}> {
|
|
851
|
+
return AGENT_COMMANDS.map((cmd) => ({
|
|
852
|
+
name: cmd.name,
|
|
853
|
+
description: cmd.description,
|
|
854
|
+
inputSchema: cmd.parameters as unknown as Record<string, unknown>,
|
|
855
|
+
}));
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export function getPiToolDefinitions(): Array<{
|
|
859
|
+
name: string;
|
|
860
|
+
label: string;
|
|
861
|
+
description: string;
|
|
862
|
+
parameters: Record<string, unknown>;
|
|
863
|
+
}> {
|
|
864
|
+
return AGENT_COMMANDS.map((cmd) => ({
|
|
865
|
+
name: cmd.name,
|
|
866
|
+
label: cmd.label,
|
|
867
|
+
description: cmd.description,
|
|
868
|
+
parameters: cmd.parameters as unknown as Record<string, unknown>,
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
export function getCliAgentCommands(): readonly AgentCommand[] {
|
|
873
|
+
return AGENT_COMMANDS;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function mintAttemptToken(input: {
|
|
877
|
+
runId: string;
|
|
878
|
+
stepId?: string;
|
|
879
|
+
stepAttempt?: number;
|
|
880
|
+
assignmentId?: string;
|
|
881
|
+
attemptId?: string;
|
|
882
|
+
agentId?: string;
|
|
883
|
+
toolPolicy?: ToolPolicy;
|
|
884
|
+
stageId?: string;
|
|
885
|
+
attempt?: number;
|
|
886
|
+
allowedTools?: string[];
|
|
887
|
+
deniedTools?: string[];
|
|
888
|
+
preset?: string;
|
|
889
|
+
}): string {
|
|
890
|
+
const toolPolicy: ToolPolicy | undefined = input.toolPolicy ?? (
|
|
891
|
+
input.allowedTools || input.deniedTools || input.preset
|
|
892
|
+
? {
|
|
893
|
+
...(input.preset ? { preset: input.preset } : {}),
|
|
894
|
+
...(input.allowedTools ? { allow: input.allowedTools, allowedTools: input.allowedTools } : {}),
|
|
895
|
+
...(input.deniedTools ? { deny: input.deniedTools, deniedTools: input.deniedTools } : {}),
|
|
896
|
+
}
|
|
897
|
+
: undefined
|
|
898
|
+
);
|
|
899
|
+
const payload: AttemptTokenPayload = {
|
|
900
|
+
schema: "kxm.attempt-token.v1",
|
|
901
|
+
runId: input.runId,
|
|
902
|
+
stepId: input.stepId ?? input.stageId ?? "step-1",
|
|
903
|
+
stepAttempt: input.stepAttempt ?? input.attempt ?? 1,
|
|
904
|
+
stageId: input.stageId ?? input.stepId ?? "step-1",
|
|
905
|
+
attempt: input.attempt ?? input.stepAttempt ?? 1,
|
|
906
|
+
assignmentId: input.assignmentId ?? `assign-${randomUUID()}`,
|
|
907
|
+
attemptId: input.attemptId ?? `attempt-${randomUUID()}`,
|
|
908
|
+
agentId: input.agentId ?? "agent-worker",
|
|
909
|
+
issuedAt: new Date().toISOString(),
|
|
910
|
+
...(toolPolicy ? { toolPolicy } : {}),
|
|
911
|
+
};
|
|
912
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function parseAttemptToken(token: string): AttemptTokenPayload | undefined {
|
|
916
|
+
try {
|
|
917
|
+
const raw = Buffer.from(token.trim(), "base64url").toString("utf8");
|
|
918
|
+
const parsed = JSON.parse(raw);
|
|
919
|
+
if (
|
|
920
|
+
parsed
|
|
921
|
+
&& typeof parsed === "object"
|
|
922
|
+
&& parsed.schema === "kxm.attempt-token.v1"
|
|
923
|
+
&& typeof parsed.runId === "string"
|
|
924
|
+
) {
|
|
925
|
+
return parsed as AttemptTokenPayload;
|
|
926
|
+
}
|
|
927
|
+
} catch {
|
|
928
|
+
return undefined;
|
|
929
|
+
}
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export function mintSessionToken(input?: {
|
|
934
|
+
sessionId?: string;
|
|
935
|
+
agentName?: string;
|
|
936
|
+
toolPolicy?: ToolPolicy;
|
|
937
|
+
preset?: string;
|
|
938
|
+
allowedTools?: string[];
|
|
939
|
+
deniedTools?: string[];
|
|
940
|
+
}): string {
|
|
941
|
+
const toolPolicy: ToolPolicy | undefined = input?.toolPolicy ?? (
|
|
942
|
+
input?.allowedTools || input?.deniedTools || input?.preset
|
|
943
|
+
? {
|
|
944
|
+
...(input?.preset ? { preset: input.preset } : {}),
|
|
945
|
+
...(input?.allowedTools ? { allow: input.allowedTools } : {}),
|
|
946
|
+
...(input?.deniedTools ? { deny: input.deniedTools } : {}),
|
|
947
|
+
}
|
|
948
|
+
: undefined
|
|
949
|
+
);
|
|
950
|
+
const payload: SessionTokenPayload = {
|
|
951
|
+
schema: "kxm.session-token.v1",
|
|
952
|
+
sessionId: input?.sessionId ?? `session-${randomUUID()}`,
|
|
953
|
+
issuedAt: new Date().toISOString(),
|
|
954
|
+
...(input?.agentName ? { agentName: input.agentName } : {}),
|
|
955
|
+
...(toolPolicy ? { toolPolicy } : {}),
|
|
956
|
+
};
|
|
957
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export function parseSessionToken(token: string): SessionTokenPayload | undefined {
|
|
961
|
+
try {
|
|
962
|
+
const raw = Buffer.from(token.trim(), "base64url").toString("utf8");
|
|
963
|
+
const parsed = JSON.parse(raw);
|
|
964
|
+
if (
|
|
965
|
+
parsed
|
|
966
|
+
&& typeof parsed === "object"
|
|
967
|
+
&& parsed.schema === "kxm.session-token.v1"
|
|
968
|
+
&& typeof parsed.sessionId === "string"
|
|
969
|
+
) {
|
|
970
|
+
return parsed as SessionTokenPayload;
|
|
971
|
+
}
|
|
972
|
+
} catch {
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
return undefined;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function matchToolPattern(pattern: string, toolName: string): boolean {
|
|
979
|
+
if (pattern === "*" || pattern === toolName) return true;
|
|
980
|
+
if (pattern.endsWith("*")) {
|
|
981
|
+
const prefix = pattern.slice(0, -1);
|
|
982
|
+
return toolName.startsWith(prefix);
|
|
983
|
+
}
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
export function isToolAllowed(commandName: string, policy?: ToolPolicy): boolean {
|
|
988
|
+
if (!policy) return true;
|
|
989
|
+
const canonical = commandName.startsWith("kxm_") ? commandName : `kxm_${commandName}`;
|
|
990
|
+
const bare = commandName.replace(/^kxm_/, "");
|
|
991
|
+
|
|
992
|
+
const denyList = policy.deny ?? policy.deniedTools;
|
|
993
|
+
if (Array.isArray(denyList)) {
|
|
994
|
+
for (const d of denyList) {
|
|
995
|
+
if (d === canonical || d === commandName || d === bare || matchToolPattern(d, canonical)) {
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const allowList = policy.allow ?? policy.allowedTools;
|
|
1002
|
+
if (Array.isArray(allowList) && allowList.length > 0) {
|
|
1003
|
+
const matched = allowList.some(
|
|
1004
|
+
(a) => a === canonical || a === commandName || a === bare || a === "*" || matchToolPattern(a, canonical),
|
|
1005
|
+
);
|
|
1006
|
+
if (!matched) return false;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (policy.preset === "read-only") {
|
|
1010
|
+
const mutating = [
|
|
1011
|
+
"kxm_send",
|
|
1012
|
+
"kxm_reply",
|
|
1013
|
+
"kxm_cancel",
|
|
1014
|
+
"kxm_fanout",
|
|
1015
|
+
"kxm_workflow_checkpoint",
|
|
1016
|
+
"kxm_workflow_record",
|
|
1017
|
+
"kxm_workflow_wait",
|
|
1018
|
+
"kxm_promote",
|
|
1019
|
+
];
|
|
1020
|
+
if (mutating.includes(canonical)) return false;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
export function enforceToolPolicy(
|
|
1027
|
+
commandName: string,
|
|
1028
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1029
|
+
): { allowed: boolean; error?: string; detail?: string } {
|
|
1030
|
+
const attemptTokenRaw = env.KXM_ATTEMPT_TOKEN?.trim();
|
|
1031
|
+
if (attemptTokenRaw) {
|
|
1032
|
+
const attempt = parseAttemptToken(attemptTokenRaw);
|
|
1033
|
+
if (!attempt) {
|
|
1034
|
+
return { allowed: false, error: "attempt_token_invalid", detail: "KXM_ATTEMPT_TOKEN is malformed" };
|
|
1035
|
+
}
|
|
1036
|
+
if (!isToolAllowed(commandName, attempt.toolPolicy)) {
|
|
1037
|
+
return {
|
|
1038
|
+
allowed: false,
|
|
1039
|
+
error: "tool_policy_denied",
|
|
1040
|
+
detail: `command ${commandName} is denied by attempt tool policy`,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
return { allowed: true };
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const sessionTokenRaw = env.KXM_SESSION_TOKEN?.trim();
|
|
1047
|
+
if (sessionTokenRaw) {
|
|
1048
|
+
const session = parseSessionToken(sessionTokenRaw);
|
|
1049
|
+
if (!session) {
|
|
1050
|
+
return { allowed: false, error: "session_token_invalid", detail: "KXM_SESSION_TOKEN is malformed" };
|
|
1051
|
+
}
|
|
1052
|
+
if (!isToolAllowed(commandName, session.toolPolicy)) {
|
|
1053
|
+
return {
|
|
1054
|
+
allowed: false,
|
|
1055
|
+
error: "tool_policy_denied",
|
|
1056
|
+
detail: `command ${commandName} is denied by session tool policy`,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return { allowed: true };
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return { allowed: true };
|
|
1063
|
+
}
|