@femtomc/mu-agent 26.2.41 → 26.2.42
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/dist/command_context.d.ts.map +1 -1
- package/dist/command_context.js +2 -0
- package/dist/extensions/activities.d.ts +4 -0
- package/dist/extensions/activities.d.ts.map +1 -0
- package/dist/extensions/activities.js +125 -0
- package/dist/extensions/heartbeats.d.ts +4 -0
- package/dist/extensions/heartbeats.d.ts.map +1 -0
- package/dist/extensions/heartbeats.js +149 -0
- package/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +12 -1
- package/dist/extensions/orchestration-runs.d.ts +4 -0
- package/dist/extensions/orchestration-runs.d.ts.map +1 -0
- package/dist/extensions/orchestration-runs.js +123 -0
- package/dist/extensions/server-tools.js +1 -1
- package/dist/operator.d.ts +24 -0
- package/dist/operator.d.ts.map +1 -1
- package/dist/operator.js +196 -54
- package/package.json +2 -2
|
@@ -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;
|
|
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"}
|
package/dist/command_context.js
CHANGED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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,6 +1,9 @@
|
|
|
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";
|
|
6
|
+
export { orchestrationRunsExtension } from "./orchestration-runs.js";
|
|
4
7
|
export { serverToolsExtension } from "./server-tools.js";
|
|
5
8
|
/**
|
|
6
9
|
* Serve-mode extension module paths.
|
|
@@ -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;
|
|
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,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAiBzD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAE/B,CAAC"}
|
package/dist/extensions/index.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
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";
|
|
6
|
+
export { orchestrationRunsExtension } from "./orchestration-runs.js";
|
|
4
7
|
export { serverToolsExtension } from "./server-tools.js";
|
|
5
|
-
const SERVE_EXTENSION_MODULE_BASENAMES = [
|
|
8
|
+
const SERVE_EXTENSION_MODULE_BASENAMES = [
|
|
9
|
+
"branding",
|
|
10
|
+
"server-tools",
|
|
11
|
+
"event-log",
|
|
12
|
+
"messaging-setup",
|
|
13
|
+
"orchestration-runs",
|
|
14
|
+
"activities",
|
|
15
|
+
"heartbeats",
|
|
16
|
+
];
|
|
6
17
|
const RUNTIME_EXTENSION = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
7
18
|
function resolveBundledExtensionPath(moduleBasename) {
|
|
8
19
|
return new URL(`./${moduleBasename}.${RUNTIME_EXTENSION}`, import.meta.url).pathname;
|
|
@@ -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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
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 orchestrationRunsExtension(pi) {
|
|
11
|
+
const RunsParams = Type.Object({
|
|
12
|
+
action: StringEnum(["list", "status", "start", "resume", "interrupt", "heartbeat", "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
|
+
prompt: Type.Optional(Type.String({ description: "Prompt for run start" })),
|
|
16
|
+
max_steps: Type.Optional(Type.Number({ description: "Optional max steps for start/resume" })),
|
|
17
|
+
limit: Type.Optional(Type.Number({ description: "Optional limit (list/trace)" })),
|
|
18
|
+
status: Type.Optional(Type.String({ description: "Optional status filter for list" })),
|
|
19
|
+
reason: Type.Optional(Type.String({ description: "Optional heartbeat reason (default: manual)" })),
|
|
20
|
+
});
|
|
21
|
+
pi.registerTool({
|
|
22
|
+
name: "mu_runs",
|
|
23
|
+
label: "Runs",
|
|
24
|
+
description: "Manage orchestration runs. Actions: list, status, start, resume, interrupt, heartbeat, trace (stdout/stderr + .mu/log hints).",
|
|
25
|
+
parameters: RunsParams,
|
|
26
|
+
async execute(_toolCallId, params) {
|
|
27
|
+
switch (params.action) {
|
|
28
|
+
case "list": {
|
|
29
|
+
const query = new URLSearchParams();
|
|
30
|
+
const status = trimOrNull(params.status);
|
|
31
|
+
if (status)
|
|
32
|
+
query.set("status", status);
|
|
33
|
+
const limit = clampInt(params.limit, 50, 1, 500);
|
|
34
|
+
query.set("limit", String(limit));
|
|
35
|
+
const payload = await fetchMuJson(`/api/runs?${query.toString()}`);
|
|
36
|
+
return textResult(toJsonText(payload), { action: "list", status, limit });
|
|
37
|
+
}
|
|
38
|
+
case "status": {
|
|
39
|
+
const id = trimOrNull(params.job_id) ?? trimOrNull(params.root_issue_id);
|
|
40
|
+
if (!id)
|
|
41
|
+
return textResult("status requires job_id or root_issue_id");
|
|
42
|
+
const payload = await fetchMuJson(`/api/runs/${encodeURIComponent(id)}`);
|
|
43
|
+
return textResult(toJsonText(payload), { action: "status", id });
|
|
44
|
+
}
|
|
45
|
+
case "start": {
|
|
46
|
+
const prompt = trimOrNull(params.prompt);
|
|
47
|
+
if (!prompt)
|
|
48
|
+
return textResult("start requires prompt");
|
|
49
|
+
const maxSteps = params.max_steps != null && Number.isFinite(params.max_steps)
|
|
50
|
+
? Math.max(1, Math.trunc(params.max_steps))
|
|
51
|
+
: undefined;
|
|
52
|
+
const payload = await fetchMuJson("/api/runs/start", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
body: {
|
|
55
|
+
prompt,
|
|
56
|
+
max_steps: maxSteps,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return textResult(toJsonText(payload), { action: "start", prompt, maxSteps });
|
|
60
|
+
}
|
|
61
|
+
case "resume": {
|
|
62
|
+
const rootIssueId = trimOrNull(params.root_issue_id);
|
|
63
|
+
if (!rootIssueId)
|
|
64
|
+
return textResult("resume requires root_issue_id");
|
|
65
|
+
const maxSteps = params.max_steps != null && Number.isFinite(params.max_steps)
|
|
66
|
+
? Math.max(1, Math.trunc(params.max_steps))
|
|
67
|
+
: undefined;
|
|
68
|
+
const payload = await fetchMuJson("/api/runs/resume", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
body: {
|
|
71
|
+
root_issue_id: rootIssueId,
|
|
72
|
+
max_steps: maxSteps,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
return textResult(toJsonText(payload), { action: "resume", rootIssueId, maxSteps });
|
|
76
|
+
}
|
|
77
|
+
case "interrupt": {
|
|
78
|
+
const jobId = trimOrNull(params.job_id);
|
|
79
|
+
const rootIssueId = trimOrNull(params.root_issue_id);
|
|
80
|
+
if (!jobId && !rootIssueId) {
|
|
81
|
+
return textResult("interrupt requires job_id or root_issue_id");
|
|
82
|
+
}
|
|
83
|
+
const payload = await fetchMuJson("/api/runs/interrupt", {
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: {
|
|
86
|
+
job_id: jobId,
|
|
87
|
+
root_issue_id: rootIssueId,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
return textResult(toJsonText(payload), { action: "interrupt", jobId, rootIssueId });
|
|
91
|
+
}
|
|
92
|
+
case "heartbeat": {
|
|
93
|
+
const jobId = trimOrNull(params.job_id);
|
|
94
|
+
const rootIssueId = trimOrNull(params.root_issue_id);
|
|
95
|
+
if (!jobId && !rootIssueId) {
|
|
96
|
+
return textResult("heartbeat requires job_id or root_issue_id");
|
|
97
|
+
}
|
|
98
|
+
const reason = trimOrNull(params.reason) ?? "manual";
|
|
99
|
+
const payload = await fetchMuJson("/api/runs/heartbeat", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: {
|
|
102
|
+
job_id: jobId,
|
|
103
|
+
root_issue_id: rootIssueId,
|
|
104
|
+
reason,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
return textResult(toJsonText(payload), { action: "heartbeat", jobId, rootIssueId, reason });
|
|
108
|
+
}
|
|
109
|
+
case "trace": {
|
|
110
|
+
const id = trimOrNull(params.job_id) ?? trimOrNull(params.root_issue_id);
|
|
111
|
+
if (!id)
|
|
112
|
+
return textResult("trace requires job_id or root_issue_id");
|
|
113
|
+
const limit = clampInt(params.limit, 200, 1, 2_000);
|
|
114
|
+
const payload = await fetchMuJson(`/api/runs/${encodeURIComponent(id)}/trace?limit=${limit}`);
|
|
115
|
+
return textResult(toJsonText(payload), { action: "trace", id, limit });
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
return textResult(`unknown action: ${params.action}`);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export default orchestrationRunsExtension;
|
|
@@ -53,7 +53,7 @@ export function serverToolsExtension(pi) {
|
|
|
53
53
|
const extra = [
|
|
54
54
|
"",
|
|
55
55
|
`[MU SERVER] Connected at ${url}.`,
|
|
56
|
-
"Tools: mu_status, mu_control_plane, mu_issues, mu_forum, mu_events.",
|
|
56
|
+
"Tools: mu_status, mu_control_plane, mu_issues, mu_forum, mu_events, mu_runs, mu_activities, mu_heartbeats.",
|
|
57
57
|
"Use these tools to inspect repository state and control-plane runtime before advising users.",
|
|
58
58
|
].join("\n");
|
|
59
59
|
return {
|
package/dist/operator.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CommandContextResolver } from "./command_context.js";
|
|
3
|
+
import { type CreateMuSessionOpts, type MuSession } from "./session_factory.js";
|
|
3
4
|
export type MessagingOperatorInboundEnvelope = {
|
|
4
5
|
channel: string;
|
|
5
6
|
channel_tenant_id: string;
|
|
@@ -30,10 +31,18 @@ export declare const OperatorApprovedCommandSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
30
31
|
kind: z.ZodLiteral<"forum_read">;
|
|
31
32
|
topic: z.ZodOptional<z.ZodString>;
|
|
32
33
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
35
|
+
kind: z.ZodLiteral<"run_list">;
|
|
36
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
37
|
+
kind: z.ZodLiteral<"run_status">;
|
|
38
|
+
root_issue_id: z.ZodOptional<z.ZodString>;
|
|
33
39
|
}, z.core.$strip>, z.ZodObject<{
|
|
34
40
|
kind: z.ZodLiteral<"run_resume">;
|
|
35
41
|
root_issue_id: z.ZodOptional<z.ZodString>;
|
|
36
42
|
max_steps: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
44
|
+
kind: z.ZodLiteral<"run_interrupt">;
|
|
45
|
+
root_issue_id: z.ZodOptional<z.ZodString>;
|
|
37
46
|
}, z.core.$strip>, z.ZodObject<{
|
|
38
47
|
kind: z.ZodLiteral<"run_start">;
|
|
39
48
|
prompt: z.ZodString;
|
|
@@ -58,10 +67,18 @@ export declare const OperatorBackendTurnResultSchema: z.ZodDiscriminatedUnion<[z
|
|
|
58
67
|
kind: z.ZodLiteral<"forum_read">;
|
|
59
68
|
topic: z.ZodOptional<z.ZodString>;
|
|
60
69
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
71
|
+
kind: z.ZodLiteral<"run_list">;
|
|
72
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
73
|
+
kind: z.ZodLiteral<"run_status">;
|
|
74
|
+
root_issue_id: z.ZodOptional<z.ZodString>;
|
|
61
75
|
}, z.core.$strip>, z.ZodObject<{
|
|
62
76
|
kind: z.ZodLiteral<"run_resume">;
|
|
63
77
|
root_issue_id: z.ZodOptional<z.ZodString>;
|
|
64
78
|
max_steps: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
80
|
+
kind: z.ZodLiteral<"run_interrupt">;
|
|
81
|
+
root_issue_id: z.ZodOptional<z.ZodString>;
|
|
65
82
|
}, z.core.$strip>, z.ZodObject<{
|
|
66
83
|
kind: z.ZodLiteral<"run_start">;
|
|
67
84
|
prompt: z.ZodString;
|
|
@@ -77,6 +94,7 @@ export type OperatorBackendTurnInput = {
|
|
|
77
94
|
};
|
|
78
95
|
export interface MessagingOperatorBackend {
|
|
79
96
|
runTurn(input: OperatorBackendTurnInput): Promise<OperatorBackendTurnResult>;
|
|
97
|
+
dispose?(): void | Promise<void>;
|
|
80
98
|
}
|
|
81
99
|
export type OperatorDecision = {
|
|
82
100
|
kind: "response";
|
|
@@ -129,6 +147,7 @@ export declare class MessagingOperatorRuntime {
|
|
|
129
147
|
inbound: InboundEnvelope;
|
|
130
148
|
binding: IdentityBinding;
|
|
131
149
|
}): Promise<OperatorDecision>;
|
|
150
|
+
stop(): Promise<void>;
|
|
132
151
|
}
|
|
133
152
|
export type PiMessagingOperatorBackendOpts = {
|
|
134
153
|
provider?: string;
|
|
@@ -137,12 +156,17 @@ export type PiMessagingOperatorBackendOpts = {
|
|
|
137
156
|
systemPrompt?: string;
|
|
138
157
|
timeoutMs?: number;
|
|
139
158
|
extensionPaths?: string[];
|
|
159
|
+
sessionFactory?: (opts: CreateMuSessionOpts) => Promise<MuSession>;
|
|
160
|
+
nowMs?: () => number;
|
|
161
|
+
sessionIdleTtlMs?: number;
|
|
162
|
+
maxSessions?: number;
|
|
140
163
|
};
|
|
141
164
|
export declare const DEFAULT_CHAT_SYSTEM_PROMPT: string;
|
|
142
165
|
export declare class PiMessagingOperatorBackend implements MessagingOperatorBackend {
|
|
143
166
|
#private;
|
|
144
167
|
constructor(opts?: PiMessagingOperatorBackendOpts);
|
|
145
168
|
runTurn(input: OperatorBackendTurnInput): Promise<OperatorBackendTurnResult>;
|
|
169
|
+
dispose(): void;
|
|
146
170
|
}
|
|
147
171
|
export {};
|
|
148
172
|
//# sourceMappingURL=operator.d.ts.map
|
package/dist/operator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"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"}
|
package/dist/operator.js
CHANGED
|
@@ -12,11 +12,20 @@ export const OperatorApprovedCommandSchema = z.discriminatedUnion("kind", [
|
|
|
12
12
|
topic: z.string().trim().min(1).optional(),
|
|
13
13
|
limit: z.number().int().min(1).max(500).optional(),
|
|
14
14
|
}),
|
|
15
|
+
z.object({ kind: z.literal("run_list") }),
|
|
16
|
+
z.object({
|
|
17
|
+
kind: z.literal("run_status"),
|
|
18
|
+
root_issue_id: z.string().trim().min(1).optional(),
|
|
19
|
+
}),
|
|
15
20
|
z.object({
|
|
16
21
|
kind: z.literal("run_resume"),
|
|
17
22
|
root_issue_id: z.string().trim().min(1).optional(),
|
|
18
23
|
max_steps: z.number().int().min(1).max(500).optional(),
|
|
19
24
|
}),
|
|
25
|
+
z.object({
|
|
26
|
+
kind: z.literal("run_interrupt"),
|
|
27
|
+
root_issue_id: z.string().trim().min(1).optional(),
|
|
28
|
+
}),
|
|
20
29
|
z.object({
|
|
21
30
|
kind: z.literal("run_start"),
|
|
22
31
|
prompt: z.string().trim().min(1),
|
|
@@ -74,6 +83,17 @@ export class ApprovedCommandBroker {
|
|
|
74
83
|
}
|
|
75
84
|
break;
|
|
76
85
|
}
|
|
86
|
+
case "run_list":
|
|
87
|
+
commandKey = "run list";
|
|
88
|
+
args = [];
|
|
89
|
+
break;
|
|
90
|
+
case "run_status":
|
|
91
|
+
commandKey = "run status";
|
|
92
|
+
args = [];
|
|
93
|
+
if (opts.proposal.root_issue_id) {
|
|
94
|
+
args.push(normalizeArg(opts.proposal.root_issue_id));
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
77
97
|
case "run_resume": {
|
|
78
98
|
if (!this.#runTriggersEnabled) {
|
|
79
99
|
return { kind: "reject", reason: "operator_action_disallowed", details: "run triggers disabled" };
|
|
@@ -88,6 +108,17 @@ export class ApprovedCommandBroker {
|
|
|
88
108
|
}
|
|
89
109
|
break;
|
|
90
110
|
}
|
|
111
|
+
case "run_interrupt": {
|
|
112
|
+
if (!this.#runTriggersEnabled) {
|
|
113
|
+
return { kind: "reject", reason: "operator_action_disallowed", details: "run triggers disabled" };
|
|
114
|
+
}
|
|
115
|
+
commandKey = "run interrupt";
|
|
116
|
+
args = [];
|
|
117
|
+
if (opts.proposal.root_issue_id) {
|
|
118
|
+
args.push(normalizeArg(opts.proposal.root_issue_id));
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
91
122
|
case "run_start": {
|
|
92
123
|
if (!this.#runTriggersEnabled) {
|
|
93
124
|
return { kind: "reject", reason: "operator_action_disallowed", details: "run triggers disabled" };
|
|
@@ -230,6 +261,10 @@ export class MessagingOperatorRuntime {
|
|
|
230
261
|
operatorTurnId: turnId,
|
|
231
262
|
};
|
|
232
263
|
}
|
|
264
|
+
async stop() {
|
|
265
|
+
this.#sessionByConversation.clear();
|
|
266
|
+
await this.#backend.dispose?.();
|
|
267
|
+
}
|
|
233
268
|
}
|
|
234
269
|
export const DEFAULT_CHAT_SYSTEM_PROMPT = [
|
|
235
270
|
"You are mu, an AI assistant for the mu orchestration platform.",
|
|
@@ -242,16 +277,53 @@ export const DEFAULT_CHAT_SYSTEM_PROMPT = [
|
|
|
242
277
|
"",
|
|
243
278
|
"Be concise, practical, and actionable.",
|
|
244
279
|
].join("\n");
|
|
280
|
+
const OPERATOR_COMMAND_PREFIX = "MU_COMMAND:";
|
|
245
281
|
const DEFAULT_OPERATOR_SYSTEM_PROMPT = [
|
|
246
282
|
"You are mu, an AI assistant for the mu orchestration platform.",
|
|
247
283
|
"You have tools to interact with the mu server: mu_status, mu_control_plane, mu_issues, mu_forum, mu_events.",
|
|
248
284
|
"Use these tools to answer questions about repository state, issues, events, and control-plane runtime state.",
|
|
249
285
|
"For adapter setup workflow, use mu_messaging_setup (check/preflight/plan/apply/verify/guide).",
|
|
250
286
|
"You can help users set up messaging integrations (Slack, Discord, Telegram, Gmail planning).",
|
|
287
|
+
"You may either respond normally or emit an approved control-plane command.",
|
|
288
|
+
`To emit a command, output exactly one line with prefix ${OPERATOR_COMMAND_PREFIX} followed by compact JSON.`,
|
|
289
|
+
"Example:",
|
|
290
|
+
`MU_COMMAND: {\"kind\":\"run_start\",\"prompt\":\"ship release\"}`,
|
|
291
|
+
"Available command kinds: status, ready, issue_list, issue_get, forum_read, run_list, run_status, run_start, run_resume, run_interrupt.",
|
|
251
292
|
"",
|
|
252
293
|
"Be concise, practical, and actionable.",
|
|
253
|
-
"
|
|
294
|
+
"For normal conversational answers, respond in plain text.",
|
|
254
295
|
].join("\n");
|
|
296
|
+
function parseOperatorCommandDirective(text) {
|
|
297
|
+
const whole = text.trim();
|
|
298
|
+
if (whole.startsWith("{") && whole.endsWith("}")) {
|
|
299
|
+
try {
|
|
300
|
+
return OperatorApprovedCommandSchema.parse(JSON.parse(whole));
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// fall through to explicit MU_COMMAND parsing
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const lines = text.split(/\r?\n/);
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
const trimmed = line.trim();
|
|
309
|
+
if (!trimmed.startsWith(OPERATOR_COMMAND_PREFIX)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const payloadText = trimmed.slice(OPERATOR_COMMAND_PREFIX.length).trim();
|
|
313
|
+
if (payloadText.length === 0) {
|
|
314
|
+
throw new Error("operator_command_directive_missing_payload");
|
|
315
|
+
}
|
|
316
|
+
let parsed;
|
|
317
|
+
try {
|
|
318
|
+
parsed = JSON.parse(payloadText);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
throw new Error(`operator_command_directive_invalid_json: ${err instanceof Error ? err.message : String(err)}`);
|
|
322
|
+
}
|
|
323
|
+
return OperatorApprovedCommandSchema.parse(parsed);
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
255
327
|
function buildOperatorPrompt(input) {
|
|
256
328
|
return [
|
|
257
329
|
`[Messaging context]`,
|
|
@@ -269,6 +341,11 @@ export class PiMessagingOperatorBackend {
|
|
|
269
341
|
#systemPrompt;
|
|
270
342
|
#timeoutMs;
|
|
271
343
|
#extensionPaths;
|
|
344
|
+
#sessionFactory;
|
|
345
|
+
#nowMs;
|
|
346
|
+
#sessionIdleTtlMs;
|
|
347
|
+
#maxSessions;
|
|
348
|
+
#sessions = new Map();
|
|
272
349
|
constructor(opts = {}) {
|
|
273
350
|
this.#provider = opts.provider;
|
|
274
351
|
this.#model = opts.model;
|
|
@@ -276,71 +353,136 @@ export class PiMessagingOperatorBackend {
|
|
|
276
353
|
this.#systemPrompt = opts.systemPrompt ?? DEFAULT_OPERATOR_SYSTEM_PROMPT;
|
|
277
354
|
this.#timeoutMs = Math.max(1_000, Math.trunc(opts.timeoutMs ?? 90_000));
|
|
278
355
|
this.#extensionPaths = opts.extensionPaths ?? [];
|
|
356
|
+
this.#sessionFactory = opts.sessionFactory ?? createMuSession;
|
|
357
|
+
this.#nowMs = opts.nowMs ?? Date.now;
|
|
358
|
+
this.#sessionIdleTtlMs = Math.max(60_000, Math.trunc(opts.sessionIdleTtlMs ?? 30 * 60 * 1_000));
|
|
359
|
+
this.#maxSessions = Math.max(1, Math.trunc(opts.maxSessions ?? 32));
|
|
279
360
|
}
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
361
|
+
#disposeSession(sessionId) {
|
|
362
|
+
const entry = this.#sessions.get(sessionId);
|
|
363
|
+
if (!entry) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.#sessions.delete(sessionId);
|
|
367
|
+
try {
|
|
368
|
+
entry.session.dispose();
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// Best effort cleanup.
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
#pruneSessions(nowMs) {
|
|
375
|
+
for (const [sessionId, entry] of this.#sessions.entries()) {
|
|
376
|
+
if (nowMs - entry.lastUsedAtMs > this.#sessionIdleTtlMs) {
|
|
377
|
+
this.#disposeSession(sessionId);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (this.#sessions.size <= this.#maxSessions) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const byOldestUse = [...this.#sessions.entries()].sort((a, b) => a[1].lastUsedAtMs - b[1].lastUsedAtMs);
|
|
384
|
+
while (this.#sessions.size > this.#maxSessions && byOldestUse.length > 0) {
|
|
385
|
+
const [sessionId] = byOldestUse.shift();
|
|
386
|
+
this.#disposeSession(sessionId);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async #createSession(repoRoot, nowMs) {
|
|
390
|
+
const session = await this.#sessionFactory({
|
|
391
|
+
cwd: repoRoot,
|
|
283
392
|
systemPrompt: this.#systemPrompt,
|
|
284
393
|
provider: this.#provider,
|
|
285
394
|
model: this.#model,
|
|
286
395
|
thinking: this.#thinking,
|
|
287
396
|
extensionPaths: this.#extensionPaths,
|
|
288
397
|
});
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
398
|
+
await session.bindExtensions({
|
|
399
|
+
commandContextActions: {
|
|
400
|
+
waitForIdle: () => session.agent.waitForIdle(),
|
|
401
|
+
newSession: async () => ({ cancelled: true }),
|
|
402
|
+
fork: async () => ({ cancelled: true }),
|
|
403
|
+
navigateTree: async () => ({ cancelled: true }),
|
|
404
|
+
switchSession: async () => ({ cancelled: true }),
|
|
405
|
+
reload: async () => { },
|
|
406
|
+
},
|
|
407
|
+
onError: () => { },
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
session,
|
|
411
|
+
repoRoot,
|
|
412
|
+
createdAtMs: nowMs,
|
|
413
|
+
lastUsedAtMs: nowMs,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
async #resolveSession(sessionId, repoRoot) {
|
|
417
|
+
const nowMs = Math.trunc(this.#nowMs());
|
|
418
|
+
this.#pruneSessions(nowMs);
|
|
419
|
+
const existing = this.#sessions.get(sessionId);
|
|
420
|
+
if (existing && existing.repoRoot === repoRoot) {
|
|
421
|
+
existing.lastUsedAtMs = nowMs;
|
|
422
|
+
return existing;
|
|
423
|
+
}
|
|
424
|
+
if (existing && existing.repoRoot !== repoRoot) {
|
|
425
|
+
this.#disposeSession(sessionId);
|
|
426
|
+
}
|
|
427
|
+
const created = await this.#createSession(repoRoot, nowMs);
|
|
428
|
+
this.#sessions.set(sessionId, created);
|
|
429
|
+
this.#pruneSessions(nowMs);
|
|
430
|
+
return created;
|
|
431
|
+
}
|
|
432
|
+
async runTurn(input) {
|
|
433
|
+
const sessionRecord = await this.#resolveSession(input.sessionId, input.inbound.repo_root);
|
|
434
|
+
const session = sessionRecord.session;
|
|
435
|
+
let assistantText = "";
|
|
436
|
+
const unsub = session.subscribe((event) => {
|
|
437
|
+
if (event?.type === "message_end" && event?.message?.role === "assistant") {
|
|
438
|
+
const msg = event.message;
|
|
439
|
+
if (typeof msg.text === "string") {
|
|
440
|
+
assistantText = msg.text;
|
|
441
|
+
}
|
|
442
|
+
else if (typeof msg.content === "string") {
|
|
443
|
+
assistantText = msg.content;
|
|
444
|
+
}
|
|
445
|
+
else if (Array.isArray(msg.content)) {
|
|
446
|
+
const parts = [];
|
|
447
|
+
for (const item of msg.content) {
|
|
448
|
+
const t = typeof item === "string" ? item : item?.text;
|
|
449
|
+
if (typeof t === "string" && t.trim().length > 0)
|
|
450
|
+
parts.push(t);
|
|
320
451
|
}
|
|
452
|
+
if (parts.length > 0)
|
|
453
|
+
assistantText = parts.join("\n");
|
|
321
454
|
}
|
|
322
|
-
});
|
|
323
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
324
|
-
setTimeout(() => reject(new Error("pi operator timeout")), this.#timeoutMs);
|
|
325
|
-
});
|
|
326
|
-
try {
|
|
327
|
-
await Promise.race([
|
|
328
|
-
session.prompt(buildOperatorPrompt(input), { expandPromptTemplates: false }),
|
|
329
|
-
timeoutPromise,
|
|
330
|
-
]);
|
|
331
|
-
}
|
|
332
|
-
finally {
|
|
333
|
-
unsub();
|
|
334
|
-
}
|
|
335
|
-
// The agent now responds naturally with tools — always return as respond
|
|
336
|
-
const message = assistantText.trim();
|
|
337
|
-
if (!message) {
|
|
338
|
-
throw new Error("operator_empty_response");
|
|
339
455
|
}
|
|
340
|
-
|
|
456
|
+
});
|
|
457
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
458
|
+
setTimeout(() => reject(new Error("pi operator timeout")), this.#timeoutMs);
|
|
459
|
+
});
|
|
460
|
+
try {
|
|
461
|
+
await Promise.race([
|
|
462
|
+
session.prompt(buildOperatorPrompt(input), { expandPromptTemplates: false }),
|
|
463
|
+
timeoutPromise,
|
|
464
|
+
]);
|
|
341
465
|
}
|
|
342
466
|
finally {
|
|
343
|
-
|
|
467
|
+
unsub();
|
|
468
|
+
sessionRecord.lastUsedAtMs = Math.trunc(this.#nowMs());
|
|
469
|
+
}
|
|
470
|
+
const message = assistantText.trim();
|
|
471
|
+
if (!message) {
|
|
472
|
+
throw new Error("operator_empty_response");
|
|
473
|
+
}
|
|
474
|
+
const command = parseOperatorCommandDirective(message);
|
|
475
|
+
if (command) {
|
|
476
|
+
return {
|
|
477
|
+
kind: "command",
|
|
478
|
+
command,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return { kind: "respond", message: message.slice(0, 2000) };
|
|
482
|
+
}
|
|
483
|
+
dispose() {
|
|
484
|
+
for (const sessionId of [...this.#sessions.keys()]) {
|
|
485
|
+
this.#disposeSession(sessionId);
|
|
344
486
|
}
|
|
345
487
|
}
|
|
346
488
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-agent",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.42",
|
|
4
4
|
"description": "Shared agent runtime for mu chat, orchestration roles, and serve extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist/**"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@femtomc/mu-core": "26.2.
|
|
25
|
+
"@femtomc/mu-core": "26.2.42",
|
|
26
26
|
"@mariozechner/pi-agent-core": "^0.52.12",
|
|
27
27
|
"@mariozechner/pi-ai": "^0.52.12",
|
|
28
28
|
"@mariozechner/pi-coding-agent": "^0.52.12",
|