@femtomc/mu-agent 26.2.41 → 26.2.43

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.
Files changed (41) hide show
  1. package/README.md +11 -0
  2. package/dist/command_context.d.ts.map +1 -1
  3. package/dist/command_context.js +2 -0
  4. package/dist/default_prompts.d.ts +13 -0
  5. package/dist/default_prompts.d.ts.map +1 -0
  6. package/dist/default_prompts.js +39 -0
  7. package/dist/extensions/activities.d.ts +4 -0
  8. package/dist/extensions/activities.d.ts.map +1 -0
  9. package/dist/extensions/activities.js +125 -0
  10. package/dist/extensions/heartbeats.d.ts +4 -0
  11. package/dist/extensions/heartbeats.d.ts.map +1 -0
  12. package/dist/extensions/heartbeats.js +149 -0
  13. package/dist/extensions/index.d.ts +13 -1
  14. package/dist/extensions/index.d.ts.map +1 -1
  15. package/dist/extensions/index.js +29 -2
  16. package/dist/extensions/orchestration-runs-readonly.d.ts +4 -0
  17. package/dist/extensions/orchestration-runs-readonly.d.ts.map +1 -0
  18. package/dist/extensions/orchestration-runs-readonly.js +56 -0
  19. package/dist/extensions/orchestration-runs.d.ts +4 -0
  20. package/dist/extensions/orchestration-runs.d.ts.map +1 -0
  21. package/dist/extensions/orchestration-runs.js +123 -0
  22. package/dist/extensions/server-tools-readonly.d.ts +4 -0
  23. package/dist/extensions/server-tools-readonly.d.ts.map +1 -0
  24. package/dist/extensions/server-tools-readonly.js +5 -0
  25. package/dist/extensions/server-tools.d.ts +7 -1
  26. package/dist/extensions/server-tools.d.ts.map +1 -1
  27. package/dist/extensions/server-tools.js +23 -2
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -0
  31. package/dist/mu_roles.d.ts +5 -7
  32. package/dist/mu_roles.d.ts.map +1 -1
  33. package/dist/mu_roles.js +5 -148
  34. package/dist/operator.d.ts +27 -2
  35. package/dist/operator.d.ts.map +1 -1
  36. package/dist/operator.js +356 -76
  37. package/package.json +4 -3
  38. package/prompts/operator.md +50 -0
  39. package/prompts/orchestrator.md +39 -0
  40. package/prompts/soul.md +10 -0
  41. package/prompts/worker.md +34 -0
package/README.md CHANGED
@@ -10,6 +10,17 @@ This package provides reusable runtime pieces for chat, orchestration, and serve
10
10
  - pi CLI/SDK orchestration backends and resource loader helpers
11
11
  - Prompt/template helpers used by orchestration roles
12
12
 
13
+ ## Bundled default prompts
14
+
15
+ Bundled defaults now live as markdown files under `packages/agent/prompts/`:
16
+
17
+ - `operator.md`
18
+ - `orchestrator.md`
19
+ - `worker.md`
20
+ - `soul.md` (shared tail appended to all role prompts)
21
+
22
+ These are loaded by runtime code and are the single source of truth for default system prompts.
23
+
13
24
  ## Install
14
25
 
15
26
  ```bash
@@ -1 +1 @@
1
- {"version":3,"file":"command_context.d.ts","sourceRoot":"","sources":["../src/command_context.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,cAAc,GAAG,MAAM,CAAC;AAE3E,MAAM,MAAM,8BAA8B,GACvC,iBAAiB,GACjB,mBAAmB,GACnB,sBAAsB,GACtB,uBAAuB,CAAC;AAE3B,MAAM,MAAM,yBAAyB,GAClC;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,uBAAuB,CAAC;CAC/B,GACD;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,8BAA8B,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL,MAAM,MAAM,0BAA0B,GAAG;IACxC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC,CAAC;AAsJF,qBAAa,sBAAsB;;gBAGf,IAAI,GAAE,0BAA+B;IAQjD,OAAO,CAAC,IAAI,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,GAAG,yBAAyB;CAqG7B"}
1
+ {"version":3,"file":"command_context.d.ts","sourceRoot":"","sources":["../src/command_context.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,cAAc,GAAG,MAAM,CAAC;AAE3E,MAAM,MAAM,8BAA8B,GACvC,iBAAiB,GACjB,mBAAmB,GACnB,sBAAsB,GACtB,uBAAuB,CAAC;AAE3B,MAAM,MAAM,yBAAyB,GAClC;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,uBAAuB,CAAC;CAC/B,GACD;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,8BAA8B,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL,MAAM,MAAM,0BAA0B,GAAG;IACxC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC,CAAC;AAwJF,qBAAa,sBAAsB;;gBAGf,IAAI,GAAE,0BAA+B;IAQjD,OAAO,CAAC,IAAI,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,GAAG,yBAAyB;CAqG7B"}
@@ -8,6 +8,8 @@ const ISSUE_TARGET_COMMANDS = new Set([
8
8
  "issue claim",
9
9
  "issue close",
10
10
  "run resume",
11
+ "run status",
12
+ "run interrupt",
11
13
  ]);
12
14
  const TOPIC_TARGET_COMMANDS = new Set(["forum read", "forum post"]);
13
15
  const GENERIC_TARGET_COMMANDS = new Set(["audit get", "dlq inspect", "dlq replay"]);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Load a bundled markdown prompt from packages/agent/prompts.
3
+ *
4
+ * This is intentionally strict: bundled prompt markdown is the single source
5
+ * of truth for default system prompts.
6
+ */
7
+ export declare function loadBundledPrompt(name: string): string;
8
+ export declare function appendSharedSoul(basePrompt: string, soulPrompt: string): string;
9
+ export declare const DEFAULT_SOUL_PROMPT: string;
10
+ export declare const DEFAULT_ORCHESTRATOR_PROMPT: string;
11
+ export declare const DEFAULT_WORKER_PROMPT: string;
12
+ export declare const DEFAULT_OPERATOR_SYSTEM_PROMPT: string;
13
+ //# sourceMappingURL=default_prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default_prompts.d.ts","sourceRoot":"","sources":["../src/default_prompts.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAStD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAO/E;AAED,eAAO,MAAM,mBAAmB,QAA+B,CAAC;AAMhE,eAAO,MAAM,2BAA2B,QAAkE,CAAC;AAC3G,eAAO,MAAM,qBAAqB,QAA4D,CAAC;AAC/F,eAAO,MAAM,8BAA8B,QAAqE,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { splitFrontmatter } from "./prompt.js";
5
+ function bundledPromptPath(name) {
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ return join(here, "..", "prompts", name);
8
+ }
9
+ /**
10
+ * Load a bundled markdown prompt from packages/agent/prompts.
11
+ *
12
+ * This is intentionally strict: bundled prompt markdown is the single source
13
+ * of truth for default system prompts.
14
+ */
15
+ export function loadBundledPrompt(name) {
16
+ const path = bundledPromptPath(name);
17
+ const raw = readFileSync(path, "utf8");
18
+ const { body } = splitFrontmatter(raw);
19
+ const prompt = body.trim();
20
+ if (prompt.length === 0) {
21
+ throw new Error(`bundled prompt is empty: ${name}`);
22
+ }
23
+ return prompt;
24
+ }
25
+ export function appendSharedSoul(basePrompt, soulPrompt) {
26
+ const base = basePrompt.trim();
27
+ const soul = soulPrompt.trim();
28
+ if (soul.length === 0) {
29
+ return base;
30
+ }
31
+ return `${base}\n\n${soul}`;
32
+ }
33
+ export const DEFAULT_SOUL_PROMPT = loadBundledPrompt("soul.md");
34
+ const BASE_ORCHESTRATOR_PROMPT = loadBundledPrompt("orchestrator.md");
35
+ const BASE_WORKER_PROMPT = loadBundledPrompt("worker.md");
36
+ const BASE_OPERATOR_SYSTEM_PROMPT = loadBundledPrompt("operator.md");
37
+ export const DEFAULT_ORCHESTRATOR_PROMPT = appendSharedSoul(BASE_ORCHESTRATOR_PROMPT, DEFAULT_SOUL_PROMPT);
38
+ export const DEFAULT_WORKER_PROMPT = appendSharedSoul(BASE_WORKER_PROMPT, DEFAULT_SOUL_PROMPT);
39
+ export const DEFAULT_OPERATOR_SYSTEM_PROMPT = appendSharedSoul(BASE_OPERATOR_SYSTEM_PROMPT, DEFAULT_SOUL_PROMPT);
@@ -0,0 +1,4 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ export declare function activitiesExtension(pi: ExtensionAPI): void;
3
+ export default activitiesExtension;
4
+ //# sourceMappingURL=activities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activities.d.ts","sourceRoot":"","sources":["../../src/extensions/activities.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,QAmHnD;AAED,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,125 @@
1
+ import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { clampInt, fetchMuJson, textResult, toJsonText } from "./shared.js";
4
+ function trimOrNull(value) {
5
+ if (value == null)
6
+ return null;
7
+ const trimmed = value.trim();
8
+ return trimmed.length > 0 ? trimmed : null;
9
+ }
10
+ export function activitiesExtension(pi) {
11
+ const ActivitiesParams = Type.Object({
12
+ action: StringEnum(["list", "get", "start", "progress", "heartbeat", "complete", "fail", "cancel", "events"]),
13
+ activity_id: Type.Optional(Type.String({ description: "Activity ID" })),
14
+ title: Type.Optional(Type.String({ description: "Title for start" })),
15
+ kind: Type.Optional(Type.String({ description: "Activity kind for start/list filtering" })),
16
+ heartbeat_every_ms: Type.Optional(Type.Number({ description: "Heartbeat interval in ms for start" })),
17
+ status: Type.Optional(Type.String({ description: "Status filter for list" })),
18
+ message: Type.Optional(Type.String({ description: "Progress/final message" })),
19
+ reason: Type.Optional(Type.String({ description: "Heartbeat reason" })),
20
+ limit: Type.Optional(Type.Number({ description: "Optional limit for list/events" })),
21
+ });
22
+ pi.registerTool({
23
+ name: "mu_activities",
24
+ label: "Activities",
25
+ description: "Manage generic long-running activities. Actions: list, get, start, progress, heartbeat, complete, fail, cancel, events.",
26
+ parameters: ActivitiesParams,
27
+ async execute(_toolCallId, params) {
28
+ switch (params.action) {
29
+ case "list": {
30
+ const query = new URLSearchParams();
31
+ const status = trimOrNull(params.status);
32
+ const kind = trimOrNull(params.kind);
33
+ const limit = clampInt(params.limit, 50, 1, 500);
34
+ if (status)
35
+ query.set("status", status);
36
+ if (kind)
37
+ query.set("kind", kind);
38
+ query.set("limit", String(limit));
39
+ const payload = await fetchMuJson(`/api/activities?${query.toString()}`);
40
+ return textResult(toJsonText(payload), { action: "list", status, kind, limit });
41
+ }
42
+ case "get": {
43
+ const activityId = trimOrNull(params.activity_id);
44
+ if (!activityId)
45
+ return textResult("get requires activity_id");
46
+ const payload = await fetchMuJson(`/api/activities/${encodeURIComponent(activityId)}`);
47
+ return textResult(toJsonText(payload), { action: "get", activityId });
48
+ }
49
+ case "events": {
50
+ const activityId = trimOrNull(params.activity_id);
51
+ if (!activityId)
52
+ return textResult("events requires activity_id");
53
+ const limit = clampInt(params.limit, 200, 1, 2_000);
54
+ const payload = await fetchMuJson(`/api/activities/${encodeURIComponent(activityId)}/events?limit=${limit}`);
55
+ return textResult(toJsonText(payload), { action: "events", activityId, limit });
56
+ }
57
+ case "start": {
58
+ const title = trimOrNull(params.title);
59
+ if (!title)
60
+ return textResult("start requires title");
61
+ const kind = trimOrNull(params.kind);
62
+ const heartbeatEveryMs = params.heartbeat_every_ms != null && Number.isFinite(params.heartbeat_every_ms)
63
+ ? Math.max(0, Math.trunc(params.heartbeat_every_ms))
64
+ : undefined;
65
+ const payload = await fetchMuJson("/api/activities/start", {
66
+ method: "POST",
67
+ body: {
68
+ title,
69
+ kind,
70
+ heartbeat_every_ms: heartbeatEveryMs,
71
+ },
72
+ });
73
+ return textResult(toJsonText(payload), { action: "start", title, kind, heartbeatEveryMs });
74
+ }
75
+ case "progress": {
76
+ const activityId = trimOrNull(params.activity_id);
77
+ if (!activityId)
78
+ return textResult("progress requires activity_id");
79
+ const message = trimOrNull(params.message) ?? "progress updated";
80
+ const payload = await fetchMuJson("/api/activities/progress", {
81
+ method: "POST",
82
+ body: {
83
+ activity_id: activityId,
84
+ message,
85
+ },
86
+ });
87
+ return textResult(toJsonText(payload), { action: "progress", activityId, message });
88
+ }
89
+ case "heartbeat": {
90
+ const activityId = trimOrNull(params.activity_id);
91
+ if (!activityId)
92
+ return textResult("heartbeat requires activity_id");
93
+ const reason = trimOrNull(params.reason) ?? "manual";
94
+ const payload = await fetchMuJson("/api/activities/heartbeat", {
95
+ method: "POST",
96
+ body: {
97
+ activity_id: activityId,
98
+ reason,
99
+ },
100
+ });
101
+ return textResult(toJsonText(payload), { action: "heartbeat", activityId, reason });
102
+ }
103
+ case "complete":
104
+ case "fail":
105
+ case "cancel": {
106
+ const activityId = trimOrNull(params.activity_id);
107
+ if (!activityId)
108
+ return textResult(`${params.action} requires activity_id`);
109
+ const message = trimOrNull(params.message);
110
+ const payload = await fetchMuJson(`/api/activities/${params.action}`, {
111
+ method: "POST",
112
+ body: {
113
+ activity_id: activityId,
114
+ message,
115
+ },
116
+ });
117
+ return textResult(toJsonText(payload), { action: params.action, activityId, message });
118
+ }
119
+ default:
120
+ return textResult(`unknown action: ${params.action}`);
121
+ }
122
+ },
123
+ });
124
+ }
125
+ export default activitiesExtension;
@@ -0,0 +1,4 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ export declare function heartbeatsExtension(pi: ExtensionAPI): void;
3
+ export default heartbeatsExtension;
4
+ //# sourceMappingURL=heartbeats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeats.d.ts","sourceRoot":"","sources":["../../src/extensions/heartbeats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,QAyInD;AAED,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,149 @@
1
+ import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { clampInt, fetchMuJson, textResult, toJsonText } from "./shared.js";
4
+ function trimOrNull(value) {
5
+ if (value == null)
6
+ return null;
7
+ const trimmed = value.trim();
8
+ return trimmed.length > 0 ? trimmed : null;
9
+ }
10
+ export function heartbeatsExtension(pi) {
11
+ const Params = Type.Object({
12
+ action: StringEnum(["list", "get", "create", "update", "delete", "trigger", "enable", "disable"]),
13
+ program_id: Type.Optional(Type.String({ description: "Heartbeat program ID" })),
14
+ title: Type.Optional(Type.String({ description: "Program title" })),
15
+ target_kind: Type.Optional(Type.String({ description: "Target kind (run|activity)" })),
16
+ run_job_id: Type.Optional(Type.String({ description: "Run job ID target" })),
17
+ run_root_issue_id: Type.Optional(Type.String({ description: "Run root issue ID target" })),
18
+ activity_id: Type.Optional(Type.String({ description: "Activity ID target" })),
19
+ every_ms: Type.Optional(Type.Number({ description: "Heartbeat interval in ms" })),
20
+ reason: Type.Optional(Type.String({ description: "Heartbeat reason" })),
21
+ enabled: Type.Optional(Type.Boolean({ description: "Enabled state" })),
22
+ limit: Type.Optional(Type.Number({ description: "Max returned items for list" })),
23
+ });
24
+ pi.registerTool({
25
+ name: "mu_heartbeats",
26
+ label: "Heartbeats",
27
+ description: "Program and manage persistent heartbeat schedules. Actions: list, get, create, update, delete, trigger, enable, disable.",
28
+ parameters: Params,
29
+ async execute(_toolCallId, params) {
30
+ switch (params.action) {
31
+ case "list": {
32
+ const query = new URLSearchParams();
33
+ const targetKind = trimOrNull(params.target_kind);
34
+ if (targetKind) {
35
+ query.set("target_kind", targetKind);
36
+ }
37
+ if (typeof params.enabled === "boolean") {
38
+ query.set("enabled", params.enabled ? "true" : "false");
39
+ }
40
+ query.set("limit", String(clampInt(params.limit, 50, 1, 500)));
41
+ const payload = await fetchMuJson(`/api/heartbeats?${query.toString()}`);
42
+ return textResult(toJsonText(payload), {
43
+ action: "list",
44
+ targetKind,
45
+ enabled: params.enabled,
46
+ });
47
+ }
48
+ case "get": {
49
+ const programId = trimOrNull(params.program_id);
50
+ if (!programId)
51
+ return textResult("get requires program_id");
52
+ const payload = await fetchMuJson(`/api/heartbeats/${encodeURIComponent(programId)}`);
53
+ return textResult(toJsonText(payload), { action: "get", programId });
54
+ }
55
+ case "create": {
56
+ const title = trimOrNull(params.title);
57
+ const targetKind = trimOrNull(params.target_kind);
58
+ if (!title)
59
+ return textResult("create requires title");
60
+ if (!targetKind)
61
+ return textResult("create requires target_kind (run|activity)");
62
+ const payload = await fetchMuJson("/api/heartbeats/create", {
63
+ method: "POST",
64
+ body: {
65
+ title,
66
+ target_kind: targetKind,
67
+ run_job_id: trimOrNull(params.run_job_id),
68
+ run_root_issue_id: trimOrNull(params.run_root_issue_id),
69
+ activity_id: trimOrNull(params.activity_id),
70
+ every_ms: params.every_ms != null && Number.isFinite(params.every_ms)
71
+ ? Math.max(0, Math.trunc(params.every_ms))
72
+ : undefined,
73
+ reason: trimOrNull(params.reason),
74
+ enabled: typeof params.enabled === "boolean" ? params.enabled : undefined,
75
+ },
76
+ });
77
+ return textResult(toJsonText(payload), { action: "create", title, targetKind });
78
+ }
79
+ case "update": {
80
+ const programId = trimOrNull(params.program_id);
81
+ if (!programId)
82
+ return textResult("update requires program_id");
83
+ const payload = await fetchMuJson("/api/heartbeats/update", {
84
+ method: "POST",
85
+ body: {
86
+ program_id: programId,
87
+ title: trimOrNull(params.title),
88
+ target_kind: trimOrNull(params.target_kind),
89
+ run_job_id: trimOrNull(params.run_job_id),
90
+ run_root_issue_id: trimOrNull(params.run_root_issue_id),
91
+ activity_id: trimOrNull(params.activity_id),
92
+ every_ms: params.every_ms != null && Number.isFinite(params.every_ms)
93
+ ? Math.max(0, Math.trunc(params.every_ms))
94
+ : undefined,
95
+ reason: trimOrNull(params.reason),
96
+ enabled: typeof params.enabled === "boolean" ? params.enabled : undefined,
97
+ },
98
+ });
99
+ return textResult(toJsonText(payload), { action: "update", programId });
100
+ }
101
+ case "delete": {
102
+ const programId = trimOrNull(params.program_id);
103
+ if (!programId)
104
+ return textResult("delete requires program_id");
105
+ const payload = await fetchMuJson("/api/heartbeats/delete", {
106
+ method: "POST",
107
+ body: {
108
+ program_id: programId,
109
+ },
110
+ });
111
+ return textResult(toJsonText(payload), { action: "delete", programId });
112
+ }
113
+ case "trigger": {
114
+ const programId = trimOrNull(params.program_id);
115
+ if (!programId)
116
+ return textResult("trigger requires program_id");
117
+ const payload = await fetchMuJson("/api/heartbeats/trigger", {
118
+ method: "POST",
119
+ body: {
120
+ program_id: programId,
121
+ reason: trimOrNull(params.reason),
122
+ },
123
+ });
124
+ return textResult(toJsonText(payload), { action: "trigger", programId });
125
+ }
126
+ case "enable":
127
+ case "disable": {
128
+ const programId = trimOrNull(params.program_id);
129
+ if (!programId)
130
+ return textResult(`${params.action} requires program_id`);
131
+ const payload = await fetchMuJson("/api/heartbeats/update", {
132
+ method: "POST",
133
+ body: {
134
+ program_id: programId,
135
+ enabled: params.action === "enable",
136
+ },
137
+ });
138
+ return textResult(toJsonText(payload), {
139
+ action: params.action,
140
+ programId,
141
+ });
142
+ }
143
+ default:
144
+ return textResult(`unknown action: ${params.action}`);
145
+ }
146
+ },
147
+ });
148
+ }
149
+ export default heartbeatsExtension;
@@ -1,7 +1,12 @@
1
+ export { activitiesExtension } from "./activities.js";
1
2
  export { brandingExtension } from "./branding.js";
2
3
  export { eventLogExtension } from "./event-log.js";
4
+ export { heartbeatsExtension } from "./heartbeats.js";
3
5
  export { messagingSetupExtension } from "./messaging-setup.js";
4
- export { serverToolsExtension } from "./server-tools.js";
6
+ export { orchestrationRunsExtension } from "./orchestration-runs.js";
7
+ export { orchestrationRunsReadOnlyExtension } from "./orchestration-runs-readonly.js";
8
+ export { serverToolsExtension, serverToolsReadOnlyExtension } from "./server-tools.js";
9
+ export { serverToolsReadonlyExtension } from "./server-tools-readonly.js";
5
10
  /**
6
11
  * Serve-mode extension module paths.
7
12
  *
@@ -10,4 +15,11 @@ export { serverToolsExtension } from "./server-tools.js";
10
15
  * anonymous inline factories.
11
16
  */
12
17
  export declare const serveExtensionPaths: string[];
18
+ /**
19
+ * Control-plane operator extension module paths.
20
+ *
21
+ * This set is intentionally read-only for tool-invoked actions so all
22
+ * mutations flow through approved `/mu ...` command proposals and policy.
23
+ */
24
+ export declare const operatorExtensionPaths: string[];
13
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AASzD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAE/B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,kCAAkC,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAyB1E;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAE/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,UAElC,CAAC"}
@@ -1,8 +1,28 @@
1
+ export { activitiesExtension } from "./activities.js";
1
2
  export { brandingExtension } from "./branding.js";
2
3
  export { eventLogExtension } from "./event-log.js";
4
+ export { heartbeatsExtension } from "./heartbeats.js";
3
5
  export { messagingSetupExtension } from "./messaging-setup.js";
4
- export { serverToolsExtension } from "./server-tools.js";
5
- const SERVE_EXTENSION_MODULE_BASENAMES = ["branding", "server-tools", "event-log", "messaging-setup"];
6
+ export { orchestrationRunsExtension } from "./orchestration-runs.js";
7
+ export { orchestrationRunsReadOnlyExtension } from "./orchestration-runs-readonly.js";
8
+ export { serverToolsExtension, serverToolsReadOnlyExtension } from "./server-tools.js";
9
+ export { serverToolsReadonlyExtension } from "./server-tools-readonly.js";
10
+ const SERVE_EXTENSION_MODULE_BASENAMES = [
11
+ "branding",
12
+ "server-tools",
13
+ "event-log",
14
+ "messaging-setup",
15
+ "orchestration-runs",
16
+ "activities",
17
+ "heartbeats",
18
+ ];
19
+ const OPERATOR_EXTENSION_MODULE_BASENAMES = [
20
+ "branding",
21
+ "server-tools-readonly",
22
+ "event-log",
23
+ "messaging-setup",
24
+ "orchestration-runs-readonly",
25
+ ];
6
26
  const RUNTIME_EXTENSION = import.meta.url.endsWith(".ts") ? "ts" : "js";
7
27
  function resolveBundledExtensionPath(moduleBasename) {
8
28
  return new URL(`./${moduleBasename}.${RUNTIME_EXTENSION}`, import.meta.url).pathname;
@@ -15,3 +35,10 @@ function resolveBundledExtensionPath(moduleBasename) {
15
35
  * anonymous inline factories.
16
36
  */
17
37
  export const serveExtensionPaths = SERVE_EXTENSION_MODULE_BASENAMES.map((moduleBasename) => resolveBundledExtensionPath(moduleBasename));
38
+ /**
39
+ * Control-plane operator extension module paths.
40
+ *
41
+ * This set is intentionally read-only for tool-invoked actions so all
42
+ * mutations flow through approved `/mu ...` command proposals and policy.
43
+ */
44
+ export const operatorExtensionPaths = OPERATOR_EXTENSION_MODULE_BASENAMES.map((moduleBasename) => resolveBundledExtensionPath(moduleBasename));
@@ -0,0 +1,4 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ export declare function orchestrationRunsReadOnlyExtension(pi: ExtensionAPI): void;
3
+ export default orchestrationRunsReadOnlyExtension;
4
+ //# sourceMappingURL=orchestration-runs-readonly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestration-runs-readonly.d.ts","sourceRoot":"","sources":["../../src/extensions/orchestration-runs-readonly.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE,wBAAgB,kCAAkC,CAAC,EAAE,EAAE,YAAY,QA8ClE;AAED,eAAe,kCAAkC,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { clampInt, fetchMuJson, textResult, toJsonText } from "./shared.js";
4
+ function trimOrNull(value) {
5
+ if (value == null)
6
+ return null;
7
+ const trimmed = value.trim();
8
+ return trimmed.length > 0 ? trimmed : null;
9
+ }
10
+ export function orchestrationRunsReadOnlyExtension(pi) {
11
+ const RunsParams = Type.Object({
12
+ action: StringEnum(["list", "status", "trace"]),
13
+ job_id: Type.Optional(Type.String({ description: "Run job ID" })),
14
+ root_issue_id: Type.Optional(Type.String({ description: "Run root issue ID (mu-...)" })),
15
+ limit: Type.Optional(Type.Number({ description: "Optional limit (list/trace)" })),
16
+ status: Type.Optional(Type.String({ description: "Optional status filter for list" })),
17
+ });
18
+ pi.registerTool({
19
+ name: "mu_runs",
20
+ label: "Runs",
21
+ description: "Read-only run inspection. Actions: list, status, trace. Mutating actions are disabled in operator mode.",
22
+ parameters: RunsParams,
23
+ async execute(_toolCallId, params) {
24
+ switch (params.action) {
25
+ case "list": {
26
+ const query = new URLSearchParams();
27
+ const status = trimOrNull(params.status);
28
+ if (status)
29
+ query.set("status", status);
30
+ const limit = clampInt(params.limit, 50, 1, 500);
31
+ query.set("limit", String(limit));
32
+ const payload = await fetchMuJson(`/api/runs?${query.toString()}`);
33
+ return textResult(toJsonText(payload), { action: "list", status, limit });
34
+ }
35
+ case "status": {
36
+ const id = trimOrNull(params.job_id) ?? trimOrNull(params.root_issue_id);
37
+ if (!id)
38
+ return textResult("status requires job_id or root_issue_id");
39
+ const payload = await fetchMuJson(`/api/runs/${encodeURIComponent(id)}`);
40
+ return textResult(toJsonText(payload), { action: "status", id });
41
+ }
42
+ case "trace": {
43
+ const id = trimOrNull(params.job_id) ?? trimOrNull(params.root_issue_id);
44
+ if (!id)
45
+ return textResult("trace requires job_id or root_issue_id");
46
+ const limit = clampInt(params.limit, 200, 1, 2_000);
47
+ const payload = await fetchMuJson(`/api/runs/${encodeURIComponent(id)}/trace?limit=${limit}`);
48
+ return textResult(toJsonText(payload), { action: "trace", id, limit });
49
+ }
50
+ default:
51
+ return textResult(`unknown action: ${params.action}`);
52
+ }
53
+ },
54
+ });
55
+ }
56
+ export default orchestrationRunsReadOnlyExtension;
@@ -0,0 +1,4 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ export declare function orchestrationRunsExtension(pi: ExtensionAPI): void;
3
+ export default orchestrationRunsExtension;
4
+ //# sourceMappingURL=orchestration-runs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestration-runs.d.ts","sourceRoot":"","sources":["../../src/extensions/orchestration-runs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,YAAY,QAiH1D;AAED,eAAe,0BAA0B,CAAC"}