@nettee/beacon 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/daemon/config/profile.d.ts +9 -0
- package/dist/daemon/config/profile.js +61 -0
- package/dist/daemon/dashboard/profiles.js +10 -0
- package/dist/daemon/dashboard/records.d.ts +2 -1
- package/dist/daemon/dashboard/records.js +12 -1
- package/dist/daemon/domain/types.d.ts +16 -0
- package/dist/daemon/domain/types.js +6 -0
- package/dist/daemon/outcome/content.d.ts +31 -1
- package/dist/daemon/outcome/content.js +29 -2
- package/dist/daemon/outcome/instructions.d.ts +2 -1
- package/dist/daemon/outcome/instructions.js +6 -0
- package/dist/daemon/outcome/server.d.ts +2 -1
- package/dist/daemon/outcome/server.js +14 -0
- package/dist/daemon/run/orchestrator.js +13 -2
- package/dist/daemon/run/profile-runner.js +1 -0
- package/dist/daemon/runtime/pi-outcome-extension.d.ts +5 -1
- package/dist/daemon/runtime/pi-outcome-extension.js +58 -1
- package/dist/daemon/runtime/pi-rpc.d.ts +5 -0
- package/dist/daemon/runtime/pi-rpc.js +8 -1
- package/dist/daemon/runtime/system-prompt.js +3 -1
- package/dist/daemon/state/trigger-store.js +2 -1
- package/dist/web/assets/{index-BP5xd2Iv.js → index-BjhnvHaZ.js} +1 -1
- package/dist/web/assets/index-v4gFn6EO.css +2 -0
- package/dist/web/index.html +2 -2
- package/examples/profiles/example/profile.yaml +5 -0
- package/package.json +1 -1
- package/dist/web/assets/index-D3C6sw_J.css +0 -2
package/README.md
CHANGED
|
@@ -68,7 +68,8 @@ and lists the missing paths. Both pairs may exist; the workspace pair wins.
|
|
|
68
68
|
|
|
69
69
|
Beacon prepends an English platform template for this Run: workspace limit,
|
|
70
70
|
inbound vs Schedule vs event vs manual capabilities, the `reply_text` / `reply_card` /
|
|
71
|
-
`no_reply` / `notify_card` contract,
|
|
71
|
+
`no_reply` / `notify_card` contract, optional `submit_feedback` (instruction gaps),
|
|
72
|
+
and optional `submit_observability_feedback` (RCA observability gaps).
|
|
72
73
|
|
|
73
74
|
Protect the home and secret file before running Beacon:
|
|
74
75
|
|
|
@@ -8,6 +8,12 @@ export type DeliveryDestination = {
|
|
|
8
8
|
name?: string | undefined;
|
|
9
9
|
description?: string | undefined;
|
|
10
10
|
};
|
|
11
|
+
/** Resolved Profile skills. Omitted on Profile means Pi auto-discovery (today's behavior). */
|
|
12
|
+
export type ProfileSkills = {
|
|
13
|
+
mode: "explicit";
|
|
14
|
+
/** Absolute paths after ~/$HOME/workspace resolution. */
|
|
15
|
+
paths: string[];
|
|
16
|
+
};
|
|
11
17
|
export type Profile = {
|
|
12
18
|
id: string;
|
|
13
19
|
directory: string;
|
|
@@ -28,11 +34,14 @@ export type Profile = {
|
|
|
28
34
|
input: string;
|
|
29
35
|
notify?: DeliveryDestination | undefined;
|
|
30
36
|
}>;
|
|
37
|
+
skills?: ProfileSkills | undefined;
|
|
31
38
|
};
|
|
32
39
|
export type LoadProfileOptions = {
|
|
33
40
|
channels?: ChannelRegistry;
|
|
34
41
|
channelsPath?: string;
|
|
35
42
|
};
|
|
36
43
|
export declare function workspaceProfileDirectory(workspace: string): string;
|
|
44
|
+
/** Expand `~` / `$HOME` and resolve relative paths against the Profile workspace. */
|
|
45
|
+
export declare function resolveSkillPath(rawPath: string, workspace: string): string;
|
|
37
46
|
export declare function loadProfile(profileId: string, profilesDirectory?: string, options?: LoadProfileOptions): Promise<Profile>;
|
|
38
47
|
export declare function profileAdminChatId(profile: Profile): string;
|
|
@@ -55,6 +55,12 @@ const scheduleSchema = z
|
|
|
55
55
|
notify: notifyDestinationSchema.optional(),
|
|
56
56
|
})
|
|
57
57
|
.strict();
|
|
58
|
+
const skillsSchema = z
|
|
59
|
+
.object({
|
|
60
|
+
mode: z.literal("explicit"),
|
|
61
|
+
paths: z.array(z.string().trim().min(1)).default([]),
|
|
62
|
+
})
|
|
63
|
+
.strict();
|
|
58
64
|
const profileDocumentSchema = z
|
|
59
65
|
.object({
|
|
60
66
|
workspace: z.string().min(1),
|
|
@@ -75,6 +81,8 @@ const profileDocumentSchema = z
|
|
|
75
81
|
})
|
|
76
82
|
.strict()
|
|
77
83
|
.optional(),
|
|
84
|
+
/** Omit = Pi default skill discovery. `explicit` → `--no-skills` + `--skill` paths. */
|
|
85
|
+
skills: skillsSchema.optional(),
|
|
78
86
|
})
|
|
79
87
|
.strict();
|
|
80
88
|
function assertProfileId(profileId) {
|
|
@@ -179,6 +187,55 @@ function resolveNotifyDestination(destination, channels, channelsPath) {
|
|
|
179
187
|
chatId: channel.chatId,
|
|
180
188
|
};
|
|
181
189
|
}
|
|
190
|
+
/** Expand `~` / `$HOME` and resolve relative paths against the Profile workspace. */
|
|
191
|
+
export function resolveSkillPath(rawPath, workspace) {
|
|
192
|
+
const trimmed = rawPath.trim();
|
|
193
|
+
const home = homedir();
|
|
194
|
+
if (trimmed === "~")
|
|
195
|
+
return home;
|
|
196
|
+
if (trimmed.startsWith("~/"))
|
|
197
|
+
return resolve(home, trimmed.slice(2));
|
|
198
|
+
if (trimmed === "$HOME")
|
|
199
|
+
return home;
|
|
200
|
+
if (trimmed.startsWith("$HOME/") || trimmed.startsWith("$HOME\\")) {
|
|
201
|
+
return resolve(home, trimmed.slice("$HOME/".length));
|
|
202
|
+
}
|
|
203
|
+
if (isAbsolute(trimmed))
|
|
204
|
+
return trimmed;
|
|
205
|
+
return resolve(workspace, trimmed);
|
|
206
|
+
}
|
|
207
|
+
async function assertValidSkillPath(profileId, resolvedPath) {
|
|
208
|
+
let pathStat;
|
|
209
|
+
try {
|
|
210
|
+
pathStat = await stat(resolvedPath);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
throw new Error(`Profile ${profileId} skill path does not exist: ${resolvedPath}`, { cause: error });
|
|
214
|
+
}
|
|
215
|
+
if (pathStat.isDirectory()) {
|
|
216
|
+
const skillMd = join(resolvedPath, "SKILL.md");
|
|
217
|
+
if (!(await isExistingFile(skillMd))) {
|
|
218
|
+
throw new Error(`Profile ${profileId} skill directory must contain SKILL.md: ${resolvedPath}`);
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (pathStat.isFile()) {
|
|
223
|
+
if (!resolvedPath.endsWith(".md")) {
|
|
224
|
+
throw new Error(`Profile ${profileId} skill file must end with .md: ${resolvedPath}`);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`Profile ${profileId} skill path must be a directory or .md file: ${resolvedPath}`);
|
|
229
|
+
}
|
|
230
|
+
async function resolveProfileSkills(profileId, workspace, skills) {
|
|
231
|
+
const paths = [];
|
|
232
|
+
for (const rawPath of skills.paths) {
|
|
233
|
+
const resolved = resolveSkillPath(rawPath, workspace);
|
|
234
|
+
await assertValidSkillPath(profileId, resolved);
|
|
235
|
+
paths.push(resolved);
|
|
236
|
+
}
|
|
237
|
+
return { mode: "explicit", paths };
|
|
238
|
+
}
|
|
182
239
|
export async function loadProfile(profileId, profilesDirectory = join(homedir(), ".beacon", "profiles"), options = {}) {
|
|
183
240
|
assertProfileId(profileId);
|
|
184
241
|
const profileDirectory = join(profilesDirectory, profileId);
|
|
@@ -225,6 +282,9 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
|
|
|
225
282
|
throw new Error(`Profile workspace is not a directory: ${workspace}`);
|
|
226
283
|
}
|
|
227
284
|
const { persona, task } = await loadPersonaAndTask(profileId, workspace, canonicalProfileDirectory);
|
|
285
|
+
const skills = config.skills
|
|
286
|
+
? await resolveProfileSkills(profileId, workspace, config.skills)
|
|
287
|
+
: undefined;
|
|
228
288
|
return {
|
|
229
289
|
id: profileId,
|
|
230
290
|
directory: canonicalProfileDirectory,
|
|
@@ -258,6 +318,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
|
|
|
258
318
|
}
|
|
259
319
|
: {}),
|
|
260
320
|
})),
|
|
321
|
+
...(skills ? { skills } : {}),
|
|
261
322
|
};
|
|
262
323
|
}
|
|
263
324
|
export function profileAdminChatId(profile) {
|
|
@@ -31,6 +31,12 @@ const scheduleSchema = z
|
|
|
31
31
|
notify: notifyDestinationSchema.optional(),
|
|
32
32
|
})
|
|
33
33
|
.strict();
|
|
34
|
+
const skillsSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
mode: z.literal("explicit"),
|
|
37
|
+
paths: z.array(z.string().trim().min(1)).default([]),
|
|
38
|
+
})
|
|
39
|
+
.strict();
|
|
34
40
|
const profileDocumentSchema = z
|
|
35
41
|
.object({
|
|
36
42
|
workspace: z.string().min(1),
|
|
@@ -51,6 +57,7 @@ const profileDocumentSchema = z
|
|
|
51
57
|
})
|
|
52
58
|
.strict()
|
|
53
59
|
.optional(),
|
|
60
|
+
skills: skillsSchema.optional(),
|
|
54
61
|
})
|
|
55
62
|
.strict();
|
|
56
63
|
function toNotifyView(notify) {
|
|
@@ -86,6 +93,9 @@ function toDetailView(profileId, document) {
|
|
|
86
93
|
notify: toNotifyView(document.listener.notify),
|
|
87
94
|
}
|
|
88
95
|
: null,
|
|
96
|
+
skills: document.skills
|
|
97
|
+
? { mode: document.skills.mode, paths: document.skills.paths }
|
|
98
|
+
: null,
|
|
89
99
|
secrets: {
|
|
90
100
|
present: false,
|
|
91
101
|
note: "Feishu app credentials are stored in secrets.json and are not exposed by the dashboard.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RunRow } from "@nettee/beacon-shared";
|
|
2
|
-
import type { FeedbackRecord } from "../domain/types.js";
|
|
2
|
+
import type { FeedbackRecord, ObservabilityFeedbackRecord } from "../domain/types.js";
|
|
3
3
|
export declare const runIdPattern: RegExp;
|
|
4
4
|
export type RunSummary = {
|
|
5
5
|
profileId: string;
|
|
@@ -18,6 +18,7 @@ export type RunSummary = {
|
|
|
18
18
|
hasSessionFile: boolean;
|
|
19
19
|
systemPrompt: string | undefined;
|
|
20
20
|
feedback: FeedbackRecord | undefined;
|
|
21
|
+
observabilityFeedback: ObservabilityFeedbackRecord | undefined;
|
|
21
22
|
};
|
|
22
23
|
export declare function listRunSummaries(profilesDirectory: string): Promise<RunSummary[]>;
|
|
23
24
|
export declare function kindLabel(row: Pick<RunSummary, "kind" | "scheduleId" | "eventSource" | "eventType">): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { feedbackRecordSchema } from "../outcome/content.js";
|
|
3
|
+
import { feedbackRecordSchema, observabilityFeedbackRecordSchema, } from "../outcome/content.js";
|
|
4
4
|
export const runIdPattern = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
5
5
|
function asRecord(value) {
|
|
6
6
|
return typeof value === "object" && value !== null
|
|
@@ -14,6 +14,10 @@ function asFeedback(value) {
|
|
|
14
14
|
const parsed = feedbackRecordSchema.safeParse(value);
|
|
15
15
|
return parsed.success ? parsed.data : undefined;
|
|
16
16
|
}
|
|
17
|
+
function asObservabilityFeedback(value) {
|
|
18
|
+
const parsed = observabilityFeedbackRecordSchema.safeParse(value);
|
|
19
|
+
return parsed.success ? parsed.data : undefined;
|
|
20
|
+
}
|
|
17
21
|
async function readDirents(path) {
|
|
18
22
|
try {
|
|
19
23
|
return await readdir(path, { withFileTypes: true });
|
|
@@ -61,6 +65,7 @@ function summarize(value, hasSessionFile) {
|
|
|
61
65
|
hasSessionFile,
|
|
62
66
|
systemPrompt: asString(run?.systemPrompt),
|
|
63
67
|
feedback: asFeedback(record.feedback),
|
|
68
|
+
observabilityFeedback: asObservabilityFeedback(record.observabilityFeedback),
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
async function loadSummary(path) {
|
|
@@ -126,6 +131,12 @@ export function toRunListItem(row) {
|
|
|
126
131
|
summary: item.summary,
|
|
127
132
|
}))
|
|
128
133
|
: null,
|
|
134
|
+
observabilityFeedback: row.observabilityFeedback && row.observabilityFeedback.items.length > 0
|
|
135
|
+
? row.observabilityFeedback.items.map((item) => ({
|
|
136
|
+
priority: item.priority,
|
|
137
|
+
summary: item.summary,
|
|
138
|
+
}))
|
|
139
|
+
: null,
|
|
129
140
|
};
|
|
130
141
|
}
|
|
131
142
|
export async function findRunSummary(profilesDirectory, runId) {
|
|
@@ -84,6 +84,20 @@ export type FeedbackRecord = {
|
|
|
84
84
|
items: FeedbackItem[];
|
|
85
85
|
submittedAt: string;
|
|
86
86
|
};
|
|
87
|
+
/** Observability-gap feedback may include low; instruction feedback may not. */
|
|
88
|
+
export declare const observabilityFeedbackPriorities: readonly ["high", "medium", "low"];
|
|
89
|
+
export type ObservabilityFeedbackPriority = (typeof observabilityFeedbackPriorities)[number];
|
|
90
|
+
export type ObservabilityFeedbackItem = {
|
|
91
|
+
priority: ObservabilityFeedbackPriority;
|
|
92
|
+
summary: string;
|
|
93
|
+
};
|
|
94
|
+
export type ObservabilityFeedbackContent = {
|
|
95
|
+
items: ObservabilityFeedbackItem[];
|
|
96
|
+
};
|
|
97
|
+
export type ObservabilityFeedbackRecord = {
|
|
98
|
+
items: ObservabilityFeedbackItem[];
|
|
99
|
+
submittedAt: string;
|
|
100
|
+
};
|
|
87
101
|
export type DeliveryContent = TextReplyContent | CardContent;
|
|
88
102
|
export type RunRecord = {
|
|
89
103
|
runId: string;
|
|
@@ -132,6 +146,8 @@ export type TriggerRecord = {
|
|
|
132
146
|
finalOutcome?: FinalOutcomeRecord | undefined;
|
|
133
147
|
/** Present only when the Agent called `submit_feedback`. */
|
|
134
148
|
feedback?: FeedbackRecord | undefined;
|
|
149
|
+
/** Present only when the Agent called `submit_observability_feedback`. */
|
|
150
|
+
observabilityFeedback?: ObservabilityFeedbackRecord | undefined;
|
|
135
151
|
delivery?: DeliveryRecord | undefined;
|
|
136
152
|
notifyDelivery?: DeliveryRecord | undefined;
|
|
137
153
|
};
|
|
@@ -17,3 +17,9 @@ export const failureCodes = [
|
|
|
17
17
|
"delivery_interrupted",
|
|
18
18
|
];
|
|
19
19
|
export const feedbackPriorities = ["high", "medium"];
|
|
20
|
+
/** Observability-gap feedback may include low; instruction feedback may not. */
|
|
21
|
+
export const observabilityFeedbackPriorities = [
|
|
22
|
+
"high",
|
|
23
|
+
"medium",
|
|
24
|
+
"low",
|
|
25
|
+
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { CardContent, DeliveryContent, FeedbackContent, FinalOutcomeContent, ReplyContent } from "../domain/types.js";
|
|
2
|
+
import type { CardContent, DeliveryContent, FeedbackContent, FinalOutcomeContent, ObservabilityFeedbackContent, ReplyContent } from "../domain/types.js";
|
|
3
3
|
export declare const feedbackItemSchema: z.ZodObject<{
|
|
4
4
|
priority: z.ZodEnum<{
|
|
5
5
|
high: "high";
|
|
@@ -26,6 +26,35 @@ export declare const feedbackRecordSchema: z.ZodObject<{
|
|
|
26
26
|
}, z.core.$strict>>;
|
|
27
27
|
submittedAt: z.ZodString;
|
|
28
28
|
}, z.core.$strict>;
|
|
29
|
+
export declare const observabilityFeedbackItemSchema: z.ZodObject<{
|
|
30
|
+
priority: z.ZodEnum<{
|
|
31
|
+
high: "high";
|
|
32
|
+
medium: "medium";
|
|
33
|
+
low: "low";
|
|
34
|
+
}>;
|
|
35
|
+
summary: z.ZodString;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
export declare const observabilityFeedbackContentSchema: z.ZodObject<{
|
|
38
|
+
items: z.ZodArray<z.ZodObject<{
|
|
39
|
+
priority: z.ZodEnum<{
|
|
40
|
+
high: "high";
|
|
41
|
+
medium: "medium";
|
|
42
|
+
low: "low";
|
|
43
|
+
}>;
|
|
44
|
+
summary: z.ZodString;
|
|
45
|
+
}, z.core.$strict>>;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
export declare const observabilityFeedbackRecordSchema: z.ZodObject<{
|
|
48
|
+
items: z.ZodArray<z.ZodObject<{
|
|
49
|
+
priority: z.ZodEnum<{
|
|
50
|
+
high: "high";
|
|
51
|
+
medium: "medium";
|
|
52
|
+
low: "low";
|
|
53
|
+
}>;
|
|
54
|
+
summary: z.ZodString;
|
|
55
|
+
}, z.core.$strict>>;
|
|
56
|
+
submittedAt: z.ZodString;
|
|
57
|
+
}, z.core.$strict>;
|
|
29
58
|
export declare const finalOutcomeContentSchema: z.ZodObject<{
|
|
30
59
|
reply: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
31
60
|
kind: z.ZodLiteral<"text">;
|
|
@@ -56,6 +85,7 @@ export type OutcomePatch = {
|
|
|
56
85
|
reply?: ReplyContent | undefined;
|
|
57
86
|
notify?: CardContent | undefined;
|
|
58
87
|
feedback?: FeedbackContent | undefined;
|
|
88
|
+
observabilityFeedback?: ObservabilityFeedbackContent | undefined;
|
|
59
89
|
};
|
|
60
90
|
export declare function parseFinalOutcomeContent(value: unknown): FinalOutcomeContent;
|
|
61
91
|
export declare function parseOutcomePatch(value: unknown): OutcomePatch;
|
|
@@ -46,6 +46,23 @@ export const feedbackRecordSchema = z
|
|
|
46
46
|
submittedAt: z.string().datetime({ offset: true }),
|
|
47
47
|
})
|
|
48
48
|
.strict();
|
|
49
|
+
export const observabilityFeedbackItemSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
priority: z.enum(["high", "medium", "low"]),
|
|
52
|
+
summary: nonBlank.max(1024),
|
|
53
|
+
})
|
|
54
|
+
.strict();
|
|
55
|
+
export const observabilityFeedbackContentSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
items: z.array(observabilityFeedbackItemSchema).min(1).max(3),
|
|
58
|
+
})
|
|
59
|
+
.strict();
|
|
60
|
+
export const observabilityFeedbackRecordSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
items: z.array(observabilityFeedbackItemSchema).min(1).max(3),
|
|
63
|
+
submittedAt: z.string().datetime({ offset: true }),
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
49
66
|
export const finalOutcomeContentSchema = z
|
|
50
67
|
.object({
|
|
51
68
|
reply: replySchema,
|
|
@@ -57,12 +74,14 @@ const outcomePatchSchema = z
|
|
|
57
74
|
reply: replySchema.optional(),
|
|
58
75
|
notify: cardSchema.optional(),
|
|
59
76
|
feedback: feedbackContentSchema.optional(),
|
|
77
|
+
observabilityFeedback: observabilityFeedbackContentSchema.optional(),
|
|
60
78
|
})
|
|
61
79
|
.strict()
|
|
62
80
|
.refine((value) => value.reply !== undefined ||
|
|
63
81
|
value.notify !== undefined ||
|
|
64
|
-
value.feedback !== undefined
|
|
65
|
-
|
|
82
|
+
value.feedback !== undefined ||
|
|
83
|
+
value.observabilityFeedback !== undefined, {
|
|
84
|
+
message: "Outcome submission must include reply, notify, feedback, or observabilityFeedback",
|
|
66
85
|
});
|
|
67
86
|
const legacyOutcomeSchema = z.discriminatedUnion("kind", [
|
|
68
87
|
textReplySchema,
|
|
@@ -106,6 +125,9 @@ export function mergeOutcome(current, patch) {
|
|
|
106
125
|
if (patch.feedback && current?.feedback) {
|
|
107
126
|
throw new Error("Feedback already submitted");
|
|
108
127
|
}
|
|
128
|
+
if (patch.observabilityFeedback && current?.observabilityFeedback) {
|
|
129
|
+
throw new Error("Observability feedback already submitted");
|
|
130
|
+
}
|
|
109
131
|
return {
|
|
110
132
|
...(current?.reply || patch.reply
|
|
111
133
|
? { reply: patch.reply ?? current?.reply }
|
|
@@ -116,6 +138,11 @@ export function mergeOutcome(current, patch) {
|
|
|
116
138
|
...(current?.feedback || patch.feedback
|
|
117
139
|
? { feedback: patch.feedback ?? current?.feedback }
|
|
118
140
|
: {}),
|
|
141
|
+
...(current?.observabilityFeedback || patch.observabilityFeedback
|
|
142
|
+
? {
|
|
143
|
+
observabilityFeedback: patch.observabilityFeedback ?? current?.observabilityFeedback,
|
|
144
|
+
}
|
|
145
|
+
: {}),
|
|
119
146
|
};
|
|
120
147
|
}
|
|
121
148
|
/** Stable English summary when the Agent settles without closing reply/no_reply. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const inboundOutOfRoleReply = "\u8FD9\u6761\u6D88\u606F\u4E0D\u5728\u6211\u7684\u804C\u8D23\u8303\u56F4\u5185\u3002";
|
|
2
2
|
export declare const finalOutcomeContractInstructions: readonly ["`reply_text` sends plain text. For an inbound Feishu message, quote-reply that message. For a Schedule, message the Profile admin. For a manual Run, print to the operator. Do not fill `chat_id`.", "`reply_card` sends a titled Markdown card on the conversational channel (inbound quote-reply, or Schedule admin chat). It closes that channel by itself. Do not also call `reply_text` or `no_reply` on the same Run. Do not fill `chat_id`.", "`no_reply` finishes a Run without messaging the conversational channel. Its reason is stored for audit and is never sent. Do not use it on inbound Feishu messages.", "`notify_card` posts a titled Markdown card to this Run's configured notify group. Call it only when this Run has a notify target. Do not fill `chat_id`.", "Which tool to use for a given business result is decided by the Profile persona/task (or the situation), not by these platform lines alone.", "Beacon ignores ordinary assistant final text for Delivery."];
|
|
3
|
-
export declare const feedbackContractInstructions: readonly ["If this Run had a real high or medium problem with instructions, Skills, dependencies, or tools, you may call `submit_feedback` once with one to three items.", "If there are none, do not call it. Do not invent items. Do not report low-priority issues, taste, or problems you already worked around without needing a platform change."];
|
|
3
|
+
export declare const feedbackContractInstructions: readonly ["If this Run had a real high or medium problem with instructions, Skills, dependencies, or tools, you may call `submit_feedback` once with one to three items.", "If there are none, do not call it. Do not invent items. Do not report low-priority issues, taste, or problems you already worked around without needing a platform change.", "Do not put observability gaps in `submit_feedback`. Use `submit_observability_feedback` for those."];
|
|
4
|
+
export declare const observabilityFeedbackContractInstructions: readonly ["If root-cause analysis on this Run was held back by missing observability (logs, metrics, traces, event correlation, or similar signals), you may call `submit_observability_feedback` once with one to three items at high, medium, or low priority.", "Say what signal is missing and how adding it would make RCA more useful or more certain. If there are none, do not call it. Do not invent items.", "Do not put instruction, Skill, dependency, or tool problems in `submit_observability_feedback`. Use `submit_feedback` for those."];
|
|
@@ -10,4 +10,10 @@ export const finalOutcomeContractInstructions = [
|
|
|
10
10
|
export const feedbackContractInstructions = [
|
|
11
11
|
"If this Run had a real high or medium problem with instructions, Skills, dependencies, or tools, you may call `submit_feedback` once with one to three items.",
|
|
12
12
|
"If there are none, do not call it. Do not invent items. Do not report low-priority issues, taste, or problems you already worked around without needing a platform change.",
|
|
13
|
+
"Do not put observability gaps in `submit_feedback`. Use `submit_observability_feedback` for those.",
|
|
14
|
+
];
|
|
15
|
+
export const observabilityFeedbackContractInstructions = [
|
|
16
|
+
"If root-cause analysis on this Run was held back by missing observability (logs, metrics, traces, event correlation, or similar signals), you may call `submit_observability_feedback` once with one to three items at high, medium, or low priority.",
|
|
17
|
+
"Say what signal is missing and how adding it would make RCA more useful or more certain. If there are none, do not call it. Do not invent items.",
|
|
18
|
+
"Do not put instruction, Skill, dependency, or tool problems in `submit_observability_feedback`. Use `submit_feedback` for those.",
|
|
13
19
|
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FeedbackRecord, FinalOutcomeContent } from "../domain/types.js";
|
|
1
|
+
import type { FeedbackRecord, FinalOutcomeContent, ObservabilityFeedbackRecord } from "../domain/types.js";
|
|
2
2
|
export type OutcomeBinding = {
|
|
3
3
|
socketPath: string;
|
|
4
4
|
runToken: string;
|
|
@@ -7,6 +7,7 @@ export type OutcomeSubmission = {
|
|
|
7
7
|
binding: OutcomeBinding;
|
|
8
8
|
take(): FinalOutcomeContent;
|
|
9
9
|
takeFeedback(): FeedbackRecord | undefined;
|
|
10
|
+
takeObservabilityFeedback(): ObservabilityFeedbackRecord | undefined;
|
|
10
11
|
cancel(): void;
|
|
11
12
|
};
|
|
12
13
|
export type OutcomeSink = {
|
|
@@ -76,6 +76,10 @@ export async function startOutcomeServer(options = {}) {
|
|
|
76
76
|
if (patch.feedback && !previous?.feedback) {
|
|
77
77
|
submission.feedbackSubmittedAt = new Date().toISOString();
|
|
78
78
|
}
|
|
79
|
+
if (patch.observabilityFeedback && !previous?.observabilityFeedback) {
|
|
80
|
+
submission.observabilityFeedbackSubmittedAt =
|
|
81
|
+
new Date().toISOString();
|
|
82
|
+
}
|
|
79
83
|
}
|
|
80
84
|
catch (error) {
|
|
81
85
|
respond(socket, {
|
|
@@ -113,6 +117,16 @@ export async function startOutcomeServer(options = {}) {
|
|
|
113
117
|
}
|
|
114
118
|
return { items, submittedAt: record.feedbackSubmittedAt };
|
|
115
119
|
},
|
|
120
|
+
takeObservabilityFeedback() {
|
|
121
|
+
const items = record.outcome?.observabilityFeedback?.items;
|
|
122
|
+
if (!items || record.observabilityFeedbackSubmittedAt === undefined) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
items,
|
|
127
|
+
submittedAt: record.observabilityFeedbackSubmittedAt,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
116
130
|
cancel() {
|
|
117
131
|
submissions.delete(runToken);
|
|
118
132
|
},
|
|
@@ -253,7 +253,7 @@ export class RunOrchestrator {
|
|
|
253
253
|
await this.deliverField(triggerKey, "notifyDelivery", current.notifyTarget, outcome.notify, create && current.notifyDelivery === undefined);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
-
async fail(triggerKey, runId, code, error, submitted, settle) {
|
|
256
|
+
async fail(triggerKey, runId, code, error, submitted, submittedObservability, settle) {
|
|
257
257
|
let detail = isMissingReplyOutcomeError(error)
|
|
258
258
|
? MISSING_REPLY_OUTCOME_SUMMARY
|
|
259
259
|
: summary(error);
|
|
@@ -278,6 +278,9 @@ export class RunOrchestrator {
|
|
|
278
278
|
submittedAt: this.timestamp(),
|
|
279
279
|
},
|
|
280
280
|
...(submitted ? { feedback: submitted } : {}),
|
|
281
|
+
...(submittedObservability
|
|
282
|
+
? { observabilityFeedback: submittedObservability }
|
|
283
|
+
: {}),
|
|
281
284
|
}));
|
|
282
285
|
await this.deliverOutcome(triggerKey);
|
|
283
286
|
}
|
|
@@ -326,6 +329,9 @@ export class RunOrchestrator {
|
|
|
326
329
|
path: record.run.sessionPath,
|
|
327
330
|
name: `Beacon ${this.options.profile.id} ${runId}`,
|
|
328
331
|
},
|
|
332
|
+
...(this.options.profile.skills
|
|
333
|
+
? { skills: this.options.profile.skills }
|
|
334
|
+
: {}),
|
|
329
335
|
});
|
|
330
336
|
settle = {
|
|
331
337
|
stopReason: completion.stopReason,
|
|
@@ -333,6 +339,7 @@ export class RunOrchestrator {
|
|
|
333
339
|
text: completion.text,
|
|
334
340
|
};
|
|
335
341
|
const submitted = submission.takeFeedback();
|
|
342
|
+
const submittedObservability = submission.takeObservabilityFeedback();
|
|
336
343
|
const outcome = normalizeAgentOutcome(input, submission.take(), record.notifyTarget);
|
|
337
344
|
await this.options.store.update(triggerKey, (current) => ({
|
|
338
345
|
...current,
|
|
@@ -349,18 +356,22 @@ export class RunOrchestrator {
|
|
|
349
356
|
submittedAt: this.timestamp(),
|
|
350
357
|
},
|
|
351
358
|
...(submitted ? { feedback: submitted } : {}),
|
|
359
|
+
...(submittedObservability
|
|
360
|
+
? { observabilityFeedback: submittedObservability }
|
|
361
|
+
: {}),
|
|
352
362
|
}));
|
|
353
363
|
await this.deliverOutcome(triggerKey);
|
|
354
364
|
}
|
|
355
365
|
catch (error) {
|
|
356
366
|
const submitted = submission.takeFeedback();
|
|
367
|
+
const submittedObservability = submission.takeObservabilityFeedback();
|
|
357
368
|
submission.cancel();
|
|
358
369
|
const code = error instanceof PiRuntimeError
|
|
359
370
|
? error.code
|
|
360
371
|
: isMissingReplyOutcomeError(error)
|
|
361
372
|
? "outcome_missing"
|
|
362
373
|
: "runtime_exit_failed";
|
|
363
|
-
await this.fail(triggerKey, runId, code, error, submitted, settle);
|
|
374
|
+
await this.fail(triggerKey, runId, code, error, submitted, submittedObservability, settle);
|
|
364
375
|
}
|
|
365
376
|
}
|
|
366
377
|
async process(triggerKey, normalize) {
|
|
@@ -32,6 +32,7 @@ export function createProfileRunner(profile, outcomes, beaconCliPath, runAgent =
|
|
|
32
32
|
notify: false,
|
|
33
33
|
}),
|
|
34
34
|
outcome: { ...submission.binding, cliPath: beaconCliPath },
|
|
35
|
+
...(profile.skills ? { skills: profile.skills } : {}),
|
|
35
36
|
});
|
|
36
37
|
return submission.take();
|
|
37
38
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CardContent, FeedbackContent, FinalOutcomeContent } from "../domain/types.js";
|
|
1
|
+
import type { CardContent, FeedbackContent, FinalOutcomeContent, ObservabilityFeedbackContent } from "../domain/types.js";
|
|
2
2
|
type ToolResult = {
|
|
3
3
|
content: Array<{
|
|
4
4
|
type: "text";
|
|
@@ -25,6 +25,9 @@ export type CardOutcomeToolParams = {
|
|
|
25
25
|
export type FeedbackToolParams = {
|
|
26
26
|
items: FeedbackContent["items"];
|
|
27
27
|
};
|
|
28
|
+
export type ObservabilityFeedbackToolParams = {
|
|
29
|
+
items: ObservabilityFeedbackContent["items"];
|
|
30
|
+
};
|
|
28
31
|
type ExtensionApi = {
|
|
29
32
|
registerTool<TParams>(tool: {
|
|
30
33
|
name: string;
|
|
@@ -39,5 +42,6 @@ export declare function replyCardFromToolParams(params: CardOutcomeToolParams):
|
|
|
39
42
|
export declare function noReplyFromToolParams(params: NoReplyOutcomeToolParams): FinalOutcomeContent;
|
|
40
43
|
export declare function notifyCardFromToolParams(params: CardOutcomeToolParams): CardContent;
|
|
41
44
|
export declare function feedbackFromToolParams(params: FeedbackToolParams): FeedbackContent;
|
|
45
|
+
export declare function observabilityFeedbackFromToolParams(params: ObservabilityFeedbackToolParams): ObservabilityFeedbackContent;
|
|
42
46
|
export default function registerOutcomeTools(pi: ExtensionApi): void;
|
|
43
47
|
export {};
|
|
@@ -39,6 +39,15 @@ export function feedbackFromToolParams(params) {
|
|
|
39
39
|
throw new Error("submit_feedback produced no items");
|
|
40
40
|
return patch.feedback;
|
|
41
41
|
}
|
|
42
|
+
export function observabilityFeedbackFromToolParams(params) {
|
|
43
|
+
const patch = parseOutcomePatch({
|
|
44
|
+
observabilityFeedback: { items: params.items },
|
|
45
|
+
});
|
|
46
|
+
if (!patch.observabilityFeedback) {
|
|
47
|
+
throw new Error("submit_observability_feedback produced no items");
|
|
48
|
+
}
|
|
49
|
+
return patch.observabilityFeedback;
|
|
50
|
+
}
|
|
42
51
|
async function invokeBeaconCli(outcome, signal) {
|
|
43
52
|
const cliPath = process.env.BEACON_CLI_PATH;
|
|
44
53
|
if (!cliPath)
|
|
@@ -209,7 +218,7 @@ export default function registerOutcomeTools(pi) {
|
|
|
209
218
|
pi.registerTool({
|
|
210
219
|
name: "submit_feedback",
|
|
211
220
|
label: "Submit Feedback",
|
|
212
|
-
description: "Optionally report one to three high or medium problems with instructions, Skills, dependencies, or tools on this Run. Call at most once, and only when such a problem actually existed. Do not call this tool when there is nothing to report.",
|
|
221
|
+
description: "Optionally report one to three high or medium problems with instructions, Skills, dependencies, or tools on this Run. Call at most once, and only when such a problem actually existed. Do not use this for missing observability — use submit_observability_feedback instead. Do not call this tool when there is nothing to report.",
|
|
213
222
|
parameters: {
|
|
214
223
|
type: "object",
|
|
215
224
|
properties: {
|
|
@@ -249,4 +258,52 @@ export default function registerOutcomeTools(pi) {
|
|
|
249
258
|
};
|
|
250
259
|
},
|
|
251
260
|
});
|
|
261
|
+
pi.registerTool({
|
|
262
|
+
name: "submit_observability_feedback",
|
|
263
|
+
label: "Submit Observability Feedback",
|
|
264
|
+
description: "Optionally report one to three observability gaps (missing logs, metrics, traces, or correlation signals) that held back root-cause analysis on this Run. Priority may be high, medium, or low. Call at most once, and only when such a gap actually existed. Do not use this for instruction/Skill/dependency/tool problems — use submit_feedback instead. Do not call this tool when there is nothing to report.",
|
|
265
|
+
parameters: {
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: {
|
|
268
|
+
items: {
|
|
269
|
+
type: "array",
|
|
270
|
+
minItems: 1,
|
|
271
|
+
maxItems: 3,
|
|
272
|
+
description: "One to three observability findings. Omit the tool call entirely when there are none.",
|
|
273
|
+
items: {
|
|
274
|
+
type: "object",
|
|
275
|
+
properties: {
|
|
276
|
+
priority: {
|
|
277
|
+
type: "string",
|
|
278
|
+
enum: ["high", "medium", "low"],
|
|
279
|
+
description: "high, medium, or low — how much the missing signal limited RCA confidence.",
|
|
280
|
+
},
|
|
281
|
+
summary: {
|
|
282
|
+
type: "string",
|
|
283
|
+
minLength: 1,
|
|
284
|
+
maxLength: 1024,
|
|
285
|
+
description: "One or two sentences naming the missing signal and how adding it would help RCA.",
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
required: ["priority", "summary"],
|
|
289
|
+
additionalProperties: false,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
required: ["items"],
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
},
|
|
296
|
+
async execute(_toolCallId, params, signal) {
|
|
297
|
+
await invokeBeaconCli({ observabilityFeedback: { items: params.items } }, signal);
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: "Observability feedback accepted by Beacon.",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
details: { submitted: true },
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
});
|
|
252
309
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProfileSkills } from "../config/profile.js";
|
|
1
2
|
import type { OutcomeBinding } from "../outcome/server.js";
|
|
2
3
|
type PiOutcomeBinding = OutcomeBinding & {
|
|
3
4
|
cliPath: string;
|
|
@@ -14,6 +15,8 @@ export type PiRunRequest = {
|
|
|
14
15
|
path: string;
|
|
15
16
|
name?: string | undefined;
|
|
16
17
|
} | undefined;
|
|
18
|
+
/** When set with mode explicit: `--no-skills` plus each `--skill` path. Omit = Pi discovery. */
|
|
19
|
+
skills?: ProfileSkills | undefined;
|
|
17
20
|
};
|
|
18
21
|
export type PiRunResult = {
|
|
19
22
|
text: string;
|
|
@@ -38,6 +41,8 @@ export declare class PiRuntimeError extends Error {
|
|
|
38
41
|
}
|
|
39
42
|
/** Collect thinking / reasoning blocks from an assistant message content array. */
|
|
40
43
|
export declare function collectThinking(content: unknown[]): string;
|
|
44
|
+
/** Exported for unit tests that assert argv without spawning a real Pi. */
|
|
45
|
+
export declare function buildArguments(request: PiRunRequest): string[];
|
|
41
46
|
/**
|
|
42
47
|
* Read NDJSON frames from Pi stdout with a hard per-frame byte cap.
|
|
43
48
|
*
|
|
@@ -65,7 +65,8 @@ export function collectThinking(content) {
|
|
|
65
65
|
.join("\n")
|
|
66
66
|
.trim();
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
/** Exported for unit tests that assert argv without spawning a real Pi. */
|
|
69
|
+
export function buildArguments(request) {
|
|
69
70
|
const args = ["--mode", "rpc", "--no-approve"];
|
|
70
71
|
if (request.session) {
|
|
71
72
|
args.push("--session-dir", request.session.path, "--session-id", request.session.id);
|
|
@@ -81,6 +82,12 @@ function buildArguments(request) {
|
|
|
81
82
|
args.push("--model", request.model);
|
|
82
83
|
if (request.systemPrompt)
|
|
83
84
|
args.push("--system-prompt", request.systemPrompt);
|
|
85
|
+
if (request.skills?.mode === "explicit") {
|
|
86
|
+
args.push("--no-skills");
|
|
87
|
+
for (const skillPath of request.skills.paths) {
|
|
88
|
+
args.push("--skill", skillPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
84
91
|
if (request.outcome) {
|
|
85
92
|
args.push("--extension", fileURLToPath(new URL(import.meta.url.endsWith(".ts")
|
|
86
93
|
? "../../dist/runtime/pi-outcome-extension.js"
|