@nettee/beacon 0.1.5 → 0.1.6
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 +20 -8
- package/dist/feishu/gateway.js +15 -0
- package/dist/feishu/intake.js +15 -2
- package/dist/manual-trigger.js +2 -1
- package/dist/outcome/content.js +3 -0
- package/dist/outcome/instructions.js +2 -2
- package/dist/run/orchestrator.js +3 -1
- package/dist/runtime/pi-outcome-extension.js +26 -2
- package/examples/profiles/example/prompt.md +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,12 +115,15 @@ does not read, initialize, or advance the Schedule's cron cursor. Unknown
|
|
|
115
115
|
Profiles or Schedules and failed Runs or Deliveries exit non-zero; failure
|
|
116
116
|
messages include the `run_id` whenever a Run record was created.
|
|
117
117
|
|
|
118
|
-
##
|
|
118
|
+
## Final Outcomes
|
|
119
119
|
|
|
120
|
-
An Agent explicitly calls one of
|
|
120
|
+
An Agent explicitly calls exactly one of three Final Outcome tools:
|
|
121
121
|
|
|
122
122
|
- `submit_final_outcome_text` sends a normal Feishu text message.
|
|
123
123
|
- `submit_final_outcome_card` sends an interactive card.
|
|
124
|
+
- `submit_final_outcome_no_reply` completes the Run without creating a
|
|
125
|
+
Delivery. Its required `reason` is stored for internal audit and is never
|
|
126
|
+
sent to the user.
|
|
124
127
|
|
|
125
128
|
The card tool accepts a title, Feishu-compatible Markdown body, and up to five
|
|
126
129
|
HTTP(S) link buttons. The first button is styled as primary. The tools accept
|
|
@@ -141,11 +144,16 @@ these argument shapes:
|
|
|
141
144
|
}
|
|
142
145
|
```
|
|
143
146
|
|
|
147
|
+
```json
|
|
148
|
+
{ "reason": "The group announcement is outside this Profile's role." }
|
|
149
|
+
```
|
|
150
|
+
|
|
144
151
|
The selected form is stored durably as part of the Final Outcome. Both quoted
|
|
145
152
|
message replies and scheduled chat messages preserve that choice: text remains
|
|
146
153
|
text, while cards are sent with Feishu's `interactive` message type and include
|
|
147
|
-
the card generation time.
|
|
148
|
-
|
|
154
|
+
the card generation time. A no-reply outcome records a successful Run without
|
|
155
|
+
a Delivery record. Manual local triggers render card outcomes as readable
|
|
156
|
+
Markdown on stdout and print no user-facing content for no-reply outcomes.
|
|
149
157
|
|
|
150
158
|
## State and failure behavior
|
|
151
159
|
|
|
@@ -200,6 +208,7 @@ The repository also contains Chinese operator runbooks based on the verified
|
|
|
200
208
|
|
|
201
209
|
- [Deploy or update Beacon on macmini](docs/macmini-deploy-and-update.md)
|
|
202
210
|
- [Add a Beacon Profile on macmini](docs/macmini-add-profile.md)
|
|
211
|
+
- [Write a role-bounded Profile Prompt](docs/profile-prompt-writing.md)
|
|
203
212
|
|
|
204
213
|
## Development checks
|
|
205
214
|
|
|
@@ -251,10 +260,13 @@ version change takes precedence over the automatic patch bump, and CI rejects
|
|
|
251
260
|
a changed version that is not greater than the base branch version.
|
|
252
261
|
|
|
253
262
|
The bump job accepts only branches in this repository, not pull requests from
|
|
254
|
-
forks.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
forks. Configure a repository secret named `AUTO_BUMP_TOKEN` with a fine-grained
|
|
264
|
+
GitHub token whose repository access is limited to `nettee/beacon` and whose
|
|
265
|
+
Contents permission is read/write. The checkout persists that credential for
|
|
266
|
+
the version commit push, so GitHub attributes the PR update to the token owner
|
|
267
|
+
and starts follow-up PR checks without an approval prompt. When the secret is
|
|
268
|
+
absent, CI falls back to the job-scoped `github.token`; the bump still succeeds,
|
|
269
|
+
but GitHub requires a maintainer to approve the resulting PR workflow run.
|
|
258
270
|
|
|
259
271
|
Submit the release candidate through the normal pull request checks. After the
|
|
260
272
|
initial package bootstrap, each push to `main` runs
|
package/dist/feishu/gateway.js
CHANGED
|
@@ -22,6 +22,9 @@ function assertSucceeded(operation, result) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
export function encodeFeishuFinalOutcome(outcome) {
|
|
25
|
+
if (outcome.kind === "no_reply") {
|
|
26
|
+
throw new Error("No-reply Outcomes must not be encoded for Delivery");
|
|
27
|
+
}
|
|
25
28
|
if (outcome.kind === "text") {
|
|
26
29
|
return {
|
|
27
30
|
msgType: "text",
|
|
@@ -84,6 +87,18 @@ export class FeishuGateway {
|
|
|
84
87
|
data: { reaction_type: { emoji_type: "OnIt" } },
|
|
85
88
|
});
|
|
86
89
|
assertSucceeded("add OnIt reaction", result);
|
|
90
|
+
const reactionId = result.data?.reaction_id;
|
|
91
|
+
if (!reactionId) {
|
|
92
|
+
throw new Error("add OnIt reaction returned no reaction_id");
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
clear: async () => {
|
|
96
|
+
const deleteResult = await this.client.im.v1.messageReaction.delete({
|
|
97
|
+
path: { message_id: messageId, reaction_id: reactionId },
|
|
98
|
+
});
|
|
99
|
+
assertSucceeded("remove OnIt reaction", deleteResult);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
87
102
|
}
|
|
88
103
|
async fetchMessage(messageId) {
|
|
89
104
|
const result = await this.client.im.v1.message.get({
|
package/dist/feishu/intake.js
CHANGED
|
@@ -31,12 +31,14 @@ export class FeishuIntake {
|
|
|
31
31
|
});
|
|
32
32
|
if (!claim.created)
|
|
33
33
|
return "duplicate";
|
|
34
|
-
|
|
34
|
+
const acknowledgement = this.options
|
|
35
35
|
.acknowledge(event.message.message_id)
|
|
36
36
|
.catch((error) => {
|
|
37
37
|
console.error(`[beacon] non-critical acknowledgement failure message_id=${event.message.message_id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
return undefined;
|
|
38
39
|
});
|
|
39
|
-
const task = this.options
|
|
40
|
+
const task = this.options
|
|
41
|
+
.process(claim.record.triggerKey, async () => {
|
|
40
42
|
const normalized = await buildFeishuTriggerInput({
|
|
41
43
|
messageId: event.message.message_id,
|
|
42
44
|
chatId: event.message.chat_id,
|
|
@@ -54,6 +56,17 @@ export class FeishuIntake {
|
|
|
54
56
|
quotedMessages: normalized.quotedMessages,
|
|
55
57
|
currentMessage: normalized.message,
|
|
56
58
|
};
|
|
59
|
+
})
|
|
60
|
+
.finally(async () => {
|
|
61
|
+
const activeAcknowledgement = await acknowledgement;
|
|
62
|
+
if (!activeAcknowledgement)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
await activeAcknowledgement.clear();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error(`[beacon] non-critical acknowledgement cleanup failure message_id=${event.message.message_id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
69
|
+
}
|
|
57
70
|
});
|
|
58
71
|
this.active.add(task);
|
|
59
72
|
void task
|
package/dist/manual-trigger.js
CHANGED
|
@@ -50,7 +50,8 @@ export async function runManualTrigger(configPath, profileId, input, output = wr
|
|
|
50
50
|
}));
|
|
51
51
|
const record = (await store.list()).find((candidate) => candidate.triggerKey === claim.record.triggerKey);
|
|
52
52
|
if (record?.run?.state !== "succeeded" ||
|
|
53
|
-
record.
|
|
53
|
+
(record.finalOutcome?.content.kind !== "no_reply" &&
|
|
54
|
+
record.delivery?.state !== "delivered")) {
|
|
54
55
|
throw new Error(`Manual Trigger failed: ${record?.run?.failure?.code ?? record?.delivery?.failure?.code ?? "unknown"}`);
|
|
55
56
|
}
|
|
56
57
|
}
|
package/dist/outcome/content.js
CHANGED
|
@@ -12,6 +12,7 @@ const buttonSchema = z
|
|
|
12
12
|
.strict();
|
|
13
13
|
export const finalOutcomeContentSchema = z.discriminatedUnion("kind", [
|
|
14
14
|
z.object({ kind: z.literal("text"), text: nonBlank }).strict(),
|
|
15
|
+
z.object({ kind: z.literal("no_reply"), reason: nonBlank }).strict(),
|
|
15
16
|
z
|
|
16
17
|
.object({
|
|
17
18
|
kind: z.literal("card"),
|
|
@@ -27,6 +28,8 @@ export function parseFinalOutcomeContent(value) {
|
|
|
27
28
|
export function renderFinalOutcomeAsText(outcome) {
|
|
28
29
|
if (outcome.kind === "text")
|
|
29
30
|
return outcome.text;
|
|
31
|
+
if (outcome.kind === "no_reply")
|
|
32
|
+
return "";
|
|
30
33
|
return [
|
|
31
34
|
`# ${outcome.title}`,
|
|
32
35
|
"",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const finalOutcomeSystemInstructions = [
|
|
2
|
-
"When your work is complete, call exactly one of submit_final_outcome_text
|
|
3
|
-
"Use submit_final_outcome_text for a normal response,
|
|
2
|
+
"When your work is complete, call exactly one of submit_final_outcome_text, submit_final_outcome_card, or submit_final_outcome_no_reply.",
|
|
3
|
+
"Use submit_final_outcome_text for a normal response, submit_final_outcome_card when a titled Markdown summary and link buttons materially improve the result, or submit_final_outcome_no_reply when the triggering message is outside the Profile's role and should receive no response.",
|
|
4
4
|
"Beacon ignores ordinary assistant final text for Delivery.",
|
|
5
5
|
];
|
package/dist/run/orchestrator.js
CHANGED
|
@@ -209,7 +209,8 @@ export class RunOrchestrator {
|
|
|
209
209
|
submittedAt: this.timestamp(),
|
|
210
210
|
},
|
|
211
211
|
}));
|
|
212
|
-
|
|
212
|
+
if (outcome.kind !== "no_reply")
|
|
213
|
+
await this.deliver(triggerKey);
|
|
213
214
|
}
|
|
214
215
|
catch (error) {
|
|
215
216
|
submission.cancel();
|
|
@@ -297,6 +298,7 @@ export class RunOrchestrator {
|
|
|
297
298
|
continue;
|
|
298
299
|
}
|
|
299
300
|
if (record.finalOutcome &&
|
|
301
|
+
record.finalOutcome.content.kind !== "no_reply" &&
|
|
300
302
|
!record.delivery &&
|
|
301
303
|
(record.run.state === "succeeded" || record.run.state === "failed")) {
|
|
302
304
|
await this.deliver(record.triggerKey);
|
|
@@ -11,6 +11,9 @@ export function cardOutcomeFromToolParams(params) {
|
|
|
11
11
|
buttons: params.buttons ?? [],
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
+
export function noReplyOutcomeFromToolParams(params) {
|
|
15
|
+
return parseFinalOutcomeContent({ kind: "no_reply", reason: params.reason });
|
|
16
|
+
}
|
|
14
17
|
async function invokeBeaconCli(outcome, signal) {
|
|
15
18
|
const cliPath = process.env.BEACON_CLI_PATH;
|
|
16
19
|
if (!cliPath)
|
|
@@ -47,7 +50,7 @@ export default function registerOutcomeTools(pi) {
|
|
|
47
50
|
pi.registerTool({
|
|
48
51
|
name: "submit_final_outcome_text",
|
|
49
52
|
label: "Submit Final Outcome Text",
|
|
50
|
-
description: "Submit the final response as a normal text message. Call
|
|
53
|
+
description: "Submit the final response as a normal text message. Call exactly one Final Outcome tool after completing the task.",
|
|
51
54
|
parameters: {
|
|
52
55
|
type: "object",
|
|
53
56
|
properties: {
|
|
@@ -68,7 +71,7 @@ export default function registerOutcomeTools(pi) {
|
|
|
68
71
|
pi.registerTool({
|
|
69
72
|
name: "submit_final_outcome_card",
|
|
70
73
|
label: "Submit Final Outcome Card",
|
|
71
|
-
description: "Submit the final response as a structured Feishu card. Call
|
|
74
|
+
description: "Submit the final response as a structured Feishu card. Call exactly one Final Outcome tool after completing the task.",
|
|
72
75
|
parameters: {
|
|
73
76
|
type: "object",
|
|
74
77
|
properties: {
|
|
@@ -104,4 +107,25 @@ export default function registerOutcomeTools(pi) {
|
|
|
104
107
|
return acceptedResult();
|
|
105
108
|
},
|
|
106
109
|
});
|
|
110
|
+
pi.registerTool({
|
|
111
|
+
name: "submit_final_outcome_no_reply",
|
|
112
|
+
label: "Submit No Reply Outcome",
|
|
113
|
+
description: "Complete the run without sending a reply. Use this exactly once when the triggering message is outside the Profile's role.",
|
|
114
|
+
parameters: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
reason: {
|
|
118
|
+
type: "string",
|
|
119
|
+
minLength: 1,
|
|
120
|
+
description: "A concise internal reason why the message should not receive a reply.",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
required: ["reason"],
|
|
124
|
+
additionalProperties: false,
|
|
125
|
+
},
|
|
126
|
+
async execute(_toolCallId, params, signal) {
|
|
127
|
+
await invokeBeaconCli(noReplyOutcomeFromToolParams(params), signal);
|
|
128
|
+
return acceptedResult();
|
|
129
|
+
},
|
|
130
|
+
});
|
|
107
131
|
}
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
You are
|
|
1
|
+
You are a workspace-status assistant. Your only role is to answer requests about the configured workspace's files, version-control state, and verification results.
|
|
2
|
+
|
|
3
|
+
Before reading files or running commands, decide whether the input is within that role. A group `@ All` does not by itself address this bot, and a direct message is not automatically relevant. Use the quoted-message chain when the current message is a follow-up.
|
|
4
|
+
|
|
5
|
+
If the input is outside this role, immediately call `submit_final_outcome_no_reply` with a concise internal reason. Do not inspect the workspace or submit a text or card response.
|
|
6
|
+
|
|
7
|
+
For relevant input, work only within the configured workspace and return a concise, user-facing result through the appropriate Final Outcome tool. Required operations must fail visibly; do not fabricate fallback data or report success after incomplete work.
|