@nettee/beacon 0.2.7 → 0.2.9
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 +29 -20
- package/dist/outcome/content.js +8 -5
- package/dist/outcome/instructions.js +5 -3
- package/dist/run/orchestrator.js +62 -9
- package/dist/runtime/pi-outcome-extension.js +61 -5
- package/dist/runtime/pi-rpc.js +24 -0
- package/dist/runtime/system-prompt.js +8 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Beacon
|
|
2
2
|
|
|
3
|
-
Beacon is a single-host service that turns Feishu messages and configured schedules into isolated Pi agent runs. Each accepted Trigger is durably claimed, produces an explicit Final Outcome, and may `
|
|
3
|
+
Beacon is a single-host service that turns Feishu messages and configured schedules into isolated Pi agent runs. Each accepted Trigger is durably claimed, produces an explicit Final Outcome, and may `reply_text` / `reply_card` to a person, `notify_card` a group, or stay silent on the admin channel.
|
|
4
4
|
|
|
5
5
|
This repository currently implements the Feishu + Pi MVP described by the
|
|
6
6
|
[Zest Dev Spec](https://github.com/nettee/beacon/blob/main/specs/change/20260913-beacon-feishu-pi-mvp/spec.md).
|
|
@@ -67,8 +67,8 @@ back to `prompt.md`. If neither place has a complete pair, Profile load fails
|
|
|
67
67
|
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
|
-
inbound vs Schedule vs manual capabilities, the `
|
|
71
|
-
`notify_card` contract, and an optional `submit_feedback` tool.
|
|
70
|
+
inbound vs Schedule vs manual capabilities, the `reply_text` / `reply_card` /
|
|
71
|
+
`no_reply` / `notify_card` contract, and an optional `submit_feedback` tool.
|
|
72
72
|
|
|
73
73
|
Protect the home and secret file before running Beacon:
|
|
74
74
|
|
|
@@ -112,10 +112,11 @@ per-Run directories with mode `0700` when Pi starts.
|
|
|
112
112
|
Configuration is strict: unknown YAML/JSON fields, YAML aliases or warnings, missing paths, duplicate Schedule IDs, invalid timezones/cron expressions, a missing complete `persona.md` + `task.md` pair, escaped Profile markdown, permissive secret or runtime-environment permissions, and missing Profile credentials all fail startup. Beacon validates all Profiles before opening a Feishu connection.
|
|
113
113
|
|
|
114
114
|
Each Schedule uses a five-field cron expression and an IANA timezone. Profile
|
|
115
|
-
`admin.chat_id` is the private chat used for schedule `
|
|
116
|
-
for failure notices. Optional `notify.chat_id` on a
|
|
117
|
-
receives `notify_card`. Beacon deliberately does not
|
|
118
|
-
another destination. A newly discovered Schedule starts at
|
|
115
|
+
`admin.chat_id` is the private chat used for schedule `reply_text` /
|
|
116
|
+
`reply_card` / `no_reply` and for failure notices. Optional `notify.chat_id` on a
|
|
117
|
+
Schedule is the group that receives `notify_card`. Beacon deliberately does not
|
|
118
|
+
infer or fall back to another destination. A newly discovered Schedule starts at
|
|
119
|
+
the current time.
|
|
119
120
|
After sleep or restart, overdue occurrences are reconciled and coalesced to the
|
|
120
121
|
most recent one.
|
|
121
122
|
|
|
@@ -157,18 +158,26 @@ messages include the `run_id` whenever a Run record was created.
|
|
|
157
158
|
|
|
158
159
|
## Final Outcomes
|
|
159
160
|
|
|
160
|
-
An Agent closes the conversational channel with `
|
|
161
|
-
also call `notify_card`
|
|
162
|
-
|
|
163
|
-
- `
|
|
164
|
-
Schedules message the Profile admin.
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
161
|
+
An Agent closes the conversational channel with `reply_text`, `reply_card`, or
|
|
162
|
+
`no_reply`, and may also call `notify_card` when the Run has a notify target:
|
|
163
|
+
|
|
164
|
+
- `reply_text` sends plain text. Inbound Feishu messages quote-reply the user.
|
|
165
|
+
Schedules message the Profile admin.
|
|
166
|
+
- `reply_card` sends an interactive card on the same conversational channel
|
|
167
|
+
(inbound quote-reply, or Schedule admin chat). It closes that channel by
|
|
168
|
+
itself.
|
|
169
|
+
- `no_reply` finishes without messaging the conversational channel. Its
|
|
170
|
+
`reason` is stored for audit and is never sent. Do not use it on inbound
|
|
171
|
+
messages.
|
|
168
172
|
- `notify_card` posts an interactive card to the Schedule's configured group.
|
|
169
|
-
|
|
173
|
+
Only valid when the Run has a notify target.
|
|
174
|
+
|
|
175
|
+
Which of `reply_text` / `reply_card` / `notify_card` / `no_reply` to use for a
|
|
176
|
+
business result is decided by the Profile persona/task (or the situation), not
|
|
177
|
+
hard-coded by Beacon beyond channel availability (`notify_card` needs a notify
|
|
178
|
+
target; inbound cannot `no_reply`).
|
|
170
179
|
|
|
171
|
-
The card
|
|
180
|
+
The card tools accept a title, Feishu-compatible Markdown body, and up to five
|
|
172
181
|
HTTP(S) link buttons. The first button is styled as primary. The tools accept
|
|
173
182
|
these argument shapes:
|
|
174
183
|
|
|
@@ -194,9 +203,9 @@ these argument shapes:
|
|
|
194
203
|
Beacon binds destinations; the Agent never supplies a `chat_id`. Text remains
|
|
195
204
|
text. Cards are sent with Feishu's `interactive` message type and include the
|
|
196
205
|
card generation time. A schedule `no_reply` without `notify_card` records a
|
|
197
|
-
successful Run with no Delivery. Failures `
|
|
198
|
-
notify a group. Manual local triggers print reply text and any notify card
|
|
199
|
-
Markdown on stdout.
|
|
206
|
+
successful Run with no Delivery. Failures `reply_text` a short error and never
|
|
207
|
+
notify a group. Manual local triggers print reply text/card and any notify card
|
|
208
|
+
as Markdown on stdout.
|
|
200
209
|
|
|
201
210
|
## State and failure behavior
|
|
202
211
|
|
package/dist/outcome/content.js
CHANGED
|
@@ -16,10 +16,6 @@ const textReplySchema = z
|
|
|
16
16
|
const noReplySchema = z
|
|
17
17
|
.object({ kind: z.literal("no_reply"), reason: nonBlank })
|
|
18
18
|
.strict();
|
|
19
|
-
const replySchema = z.discriminatedUnion("kind", [
|
|
20
|
-
textReplySchema,
|
|
21
|
-
noReplySchema,
|
|
22
|
-
]);
|
|
23
19
|
const cardSchema = z
|
|
24
20
|
.object({
|
|
25
21
|
kind: z.literal("card"),
|
|
@@ -28,6 +24,11 @@ const cardSchema = z
|
|
|
28
24
|
buttons: z.array(buttonSchema).max(5).default([]),
|
|
29
25
|
})
|
|
30
26
|
.strict();
|
|
27
|
+
const replySchema = z.discriminatedUnion("kind", [
|
|
28
|
+
textReplySchema,
|
|
29
|
+
noReplySchema,
|
|
30
|
+
cardSchema,
|
|
31
|
+
]);
|
|
31
32
|
export const feedbackItemSchema = z
|
|
32
33
|
.object({
|
|
33
34
|
priority: z.enum(["high", "medium"]),
|
|
@@ -118,7 +119,7 @@ export function mergeOutcome(current, patch) {
|
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
/** Stable English summary when the Agent settles without closing reply/no_reply. */
|
|
121
|
-
export const MISSING_REPLY_OUTCOME_SUMMARY = "Expected exactly one of `
|
|
122
|
+
export const MISSING_REPLY_OUTCOME_SUMMARY = "Expected exactly one of `reply_text`, `reply_card`, or `no_reply`, but the Agent Runtime settled without submitting any";
|
|
122
123
|
export function isMissingReplyOutcomeError(error) {
|
|
123
124
|
if (!(error instanceof Error))
|
|
124
125
|
return false;
|
|
@@ -141,6 +142,8 @@ export function renderFinalOutcomeAsText(outcome) {
|
|
|
141
142
|
const parts = [];
|
|
142
143
|
if (outcome.reply.kind === "text")
|
|
143
144
|
parts.push(outcome.reply.text);
|
|
145
|
+
if (outcome.reply.kind === "card")
|
|
146
|
+
parts.push(renderCard(outcome.reply));
|
|
144
147
|
if (outcome.notify)
|
|
145
148
|
parts.push(renderCard(outcome.notify));
|
|
146
149
|
return parts.join("\n\n");
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export const inboundOutOfRoleReply = "这条消息不在我的职责范围内。";
|
|
2
2
|
export const finalOutcomeContractInstructions = [
|
|
3
|
-
"`
|
|
4
|
-
"`
|
|
5
|
-
"`
|
|
3
|
+
"`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`.",
|
|
4
|
+
"`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`.",
|
|
5
|
+
"`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.",
|
|
6
|
+
"`notify_card` posts a titled Markdown card to the Schedule's configured notify group. Call it only when this Run has a notify target. Do not fill `chat_id`.",
|
|
7
|
+
"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.",
|
|
6
8
|
"Beacon ignores ordinary assistant final text for Delivery.",
|
|
7
9
|
];
|
|
8
10
|
export const feedbackContractInstructions = [
|
package/dist/run/orchestrator.js
CHANGED
|
@@ -40,13 +40,54 @@ function summary(error) {
|
|
|
40
40
|
const value = error instanceof Error ? error.message : String(error);
|
|
41
41
|
return value.slice(0, 4096) || "unknown failure";
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
const FAILURE_DETAIL_LIMIT = 400;
|
|
44
|
+
const THINKING_SNIPPET_LIMIT = 120;
|
|
45
|
+
function clipForFeishu(value, max) {
|
|
46
|
+
const trimmed = value.replace(/\s+/g, " ").trim();
|
|
47
|
+
if (trimmed.length <= max)
|
|
48
|
+
return trimmed;
|
|
49
|
+
return `${trimmed.slice(0, Math.max(0, max - 1))}…`;
|
|
50
|
+
}
|
|
51
|
+
/** Compact settle-side hint for outcome_missing (Feishu / admin). */
|
|
52
|
+
export function formatSettleFailureHint(parts) {
|
|
53
|
+
const bits = [];
|
|
54
|
+
if (parts.stopReason?.trim()) {
|
|
55
|
+
bits.push(`末轮 stopReason=${parts.stopReason.trim()}`);
|
|
56
|
+
}
|
|
57
|
+
const thought = parts.thinking?.trim() || parts.text?.trim();
|
|
58
|
+
if (thought) {
|
|
59
|
+
bits.push(`thinking:「${clipForFeishu(thought, THINKING_SNIPPET_LIMIT)}」`);
|
|
60
|
+
}
|
|
61
|
+
return bits.length > 0 ? bits.join(";") : undefined;
|
|
62
|
+
}
|
|
63
|
+
function detailBeyondMissingSummary(summaryText) {
|
|
64
|
+
if (summaryText === MISSING_REPLY_OUTCOME_SUMMARY)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (summaryText.startsWith(MISSING_REPLY_OUTCOME_SUMMARY)) {
|
|
67
|
+
const rest = summaryText
|
|
68
|
+
.slice(MISSING_REPLY_OUTCOME_SUMMARY.length)
|
|
69
|
+
.replace(/^[\s.;]+/, "")
|
|
70
|
+
.trim();
|
|
71
|
+
return rest ? clipForFeishu(rest, FAILURE_DETAIL_LIMIT) : undefined;
|
|
72
|
+
}
|
|
73
|
+
return clipForFeishu(summaryText, FAILURE_DETAIL_LIMIT);
|
|
74
|
+
}
|
|
75
|
+
/** User-visible (Feishu/admin) failure text. Keep codes stable; prefer concrete reasons. */
|
|
44
76
|
export function formatFailureReplyText(runId, failure) {
|
|
45
77
|
if (failure?.code === "outcome_missing") {
|
|
46
|
-
|
|
78
|
+
const parts = [
|
|
47
79
|
`处理失败(run_id=${runId},code=outcome_missing):`,
|
|
48
|
-
"期望恰好调用一次 `
|
|
49
|
-
]
|
|
80
|
+
"期望恰好调用一次 `reply_text`、`reply_card` 或 `no_reply`,但 Agent 已结束且均未提交。",
|
|
81
|
+
];
|
|
82
|
+
const detail = failure.summary
|
|
83
|
+
? detailBeyondMissingSummary(failure.summary)
|
|
84
|
+
: undefined;
|
|
85
|
+
if (detail)
|
|
86
|
+
parts.push(`详情:${detail}`);
|
|
87
|
+
return parts.join("");
|
|
88
|
+
}
|
|
89
|
+
if (failure?.summary?.trim()) {
|
|
90
|
+
return `处理失败(run_id=${runId},code=${failure.code}):${clipForFeishu(failure.summary, FAILURE_DETAIL_LIMIT)}`;
|
|
50
91
|
}
|
|
51
92
|
if (failure) {
|
|
52
93
|
return `处理失败(run_id=${runId},code=${failure.code}),请查看 Beacon 本地记录。`;
|
|
@@ -175,17 +216,22 @@ export class RunOrchestrator {
|
|
|
175
216
|
async deliverOutcome(triggerKey, create = true) {
|
|
176
217
|
const current = await this.record(triggerKey);
|
|
177
218
|
const outcome = current.finalOutcome.content;
|
|
178
|
-
if (outcome.reply.kind === "text") {
|
|
219
|
+
if (outcome.reply.kind === "text" || outcome.reply.kind === "card") {
|
|
179
220
|
await this.deliverField(triggerKey, "delivery", current.target, outcome.reply, create && current.delivery === undefined);
|
|
180
221
|
}
|
|
181
222
|
if (outcome.notify && current.notifyTarget) {
|
|
182
223
|
await this.deliverField(triggerKey, "notifyDelivery", current.notifyTarget, outcome.notify, create && current.notifyDelivery === undefined);
|
|
183
224
|
}
|
|
184
225
|
}
|
|
185
|
-
async fail(triggerKey, runId, code, error, submitted) {
|
|
186
|
-
|
|
226
|
+
async fail(triggerKey, runId, code, error, submitted, settle) {
|
|
227
|
+
let detail = isMissingReplyOutcomeError(error)
|
|
187
228
|
? MISSING_REPLY_OUTCOME_SUMMARY
|
|
188
229
|
: summary(error);
|
|
230
|
+
if (code === "outcome_missing") {
|
|
231
|
+
const hint = formatSettleFailureHint(settle ?? {});
|
|
232
|
+
if (hint)
|
|
233
|
+
detail = `${detail}. ${hint}`;
|
|
234
|
+
}
|
|
189
235
|
const failure = { code, summary: detail };
|
|
190
236
|
await this.options.store.update(triggerKey, (current) => ({
|
|
191
237
|
...current,
|
|
@@ -236,6 +282,7 @@ export class RunOrchestrator {
|
|
|
236
282
|
run: { ...current.run, state: "running" },
|
|
237
283
|
}));
|
|
238
284
|
const submission = this.options.outcomes.openRun();
|
|
285
|
+
let settle;
|
|
239
286
|
try {
|
|
240
287
|
const completion = await this.options.runAgent({
|
|
241
288
|
prompt: promptFor(input),
|
|
@@ -250,6 +297,11 @@ export class RunOrchestrator {
|
|
|
250
297
|
name: `Beacon ${this.options.profile.id} ${runId}`,
|
|
251
298
|
},
|
|
252
299
|
});
|
|
300
|
+
settle = {
|
|
301
|
+
stopReason: completion.stopReason,
|
|
302
|
+
thinking: completion.thinking,
|
|
303
|
+
text: completion.text,
|
|
304
|
+
};
|
|
253
305
|
const submitted = submission.takeFeedback();
|
|
254
306
|
const outcome = normalizeAgentOutcome(input, submission.take(), record.notifyTarget);
|
|
255
307
|
await this.options.store.update(triggerKey, (current) => ({
|
|
@@ -278,7 +330,7 @@ export class RunOrchestrator {
|
|
|
278
330
|
: isMissingReplyOutcomeError(error)
|
|
279
331
|
? "outcome_missing"
|
|
280
332
|
: "runtime_exit_failed";
|
|
281
|
-
await this.fail(triggerKey, runId, code, error, submitted);
|
|
333
|
+
await this.fail(triggerKey, runId, code, error, submitted, settle);
|
|
282
334
|
}
|
|
283
335
|
}
|
|
284
336
|
async process(triggerKey, normalize) {
|
|
@@ -359,7 +411,8 @@ export class RunOrchestrator {
|
|
|
359
411
|
if (latest.finalOutcome &&
|
|
360
412
|
(latest.run.state === "succeeded" || latest.run.state === "failed")) {
|
|
361
413
|
const outcome = latest.finalOutcome.content;
|
|
362
|
-
const missingReply = outcome.reply.kind === "text"
|
|
414
|
+
const missingReply = (outcome.reply.kind === "text" || outcome.reply.kind === "card") &&
|
|
415
|
+
!latest.delivery;
|
|
363
416
|
const missingNotify = Boolean(outcome.notify) &&
|
|
364
417
|
Boolean(latest.notifyTarget) &&
|
|
365
418
|
!latest.notifyDelivery;
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { parseOutcomePatch } from "../outcome/content.js";
|
|
3
|
-
export function
|
|
3
|
+
export function replyTextFromToolParams(params) {
|
|
4
4
|
return parseOutcomePatch({
|
|
5
5
|
reply: { kind: "text", text: params.text },
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
+
export function replyCardFromToolParams(params) {
|
|
9
|
+
return parseOutcomePatch({
|
|
10
|
+
reply: {
|
|
11
|
+
kind: "card",
|
|
12
|
+
title: params.title,
|
|
13
|
+
content: params.content,
|
|
14
|
+
buttons: params.buttons ?? [],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
8
18
|
export function noReplyFromToolParams(params) {
|
|
9
19
|
return parseOutcomePatch({
|
|
10
20
|
reply: { kind: "no_reply", reason: params.reason },
|
|
@@ -63,9 +73,9 @@ function acceptedResult() {
|
|
|
63
73
|
}
|
|
64
74
|
export default function registerOutcomeTools(pi) {
|
|
65
75
|
pi.registerTool({
|
|
66
|
-
name: "
|
|
67
|
-
label: "Reply",
|
|
68
|
-
description: "Send a plain-text reply. Inbound Feishu messages quote-reply the user. Schedules message the admin.
|
|
76
|
+
name: "reply_text",
|
|
77
|
+
label: "Reply Text",
|
|
78
|
+
description: "Send a plain-text reply on the conversational channel. Inbound Feishu messages quote-reply the user. Schedules message the admin. Manual Runs print to the operator.",
|
|
69
79
|
parameters: {
|
|
70
80
|
type: "object",
|
|
71
81
|
properties: {
|
|
@@ -83,6 +93,52 @@ export default function registerOutcomeTools(pi) {
|
|
|
83
93
|
return acceptedResult();
|
|
84
94
|
},
|
|
85
95
|
});
|
|
96
|
+
pi.registerTool({
|
|
97
|
+
name: "reply_card",
|
|
98
|
+
label: "Reply Card",
|
|
99
|
+
description: "Send a structured interactive card on the conversational channel (inbound quote-reply, or Schedule admin chat). Closes that channel by itself. Do not also call reply_text or no_reply on the same Run.",
|
|
100
|
+
parameters: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
title: {
|
|
104
|
+
type: "string",
|
|
105
|
+
minLength: 1,
|
|
106
|
+
description: "The card header title.",
|
|
107
|
+
},
|
|
108
|
+
content: {
|
|
109
|
+
type: "string",
|
|
110
|
+
minLength: 1,
|
|
111
|
+
description: "The card body in Feishu-compatible Markdown.",
|
|
112
|
+
},
|
|
113
|
+
buttons: {
|
|
114
|
+
type: "array",
|
|
115
|
+
maxItems: 5,
|
|
116
|
+
items: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
label: { type: "string", minLength: 1 },
|
|
120
|
+
url: { type: "string", pattern: "^https?://" },
|
|
121
|
+
},
|
|
122
|
+
required: ["label", "url"],
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
required: ["title", "content"],
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
},
|
|
130
|
+
async execute(_toolCallId, params, signal) {
|
|
131
|
+
await invokeBeaconCli({
|
|
132
|
+
reply: {
|
|
133
|
+
kind: "card",
|
|
134
|
+
title: params.title,
|
|
135
|
+
content: params.content,
|
|
136
|
+
buttons: params.buttons ?? [],
|
|
137
|
+
},
|
|
138
|
+
}, signal);
|
|
139
|
+
return acceptedResult();
|
|
140
|
+
},
|
|
141
|
+
});
|
|
86
142
|
pi.registerTool({
|
|
87
143
|
name: "no_reply",
|
|
88
144
|
label: "No Reply",
|
|
@@ -107,7 +163,7 @@ export default function registerOutcomeTools(pi) {
|
|
|
107
163
|
pi.registerTool({
|
|
108
164
|
name: "notify_card",
|
|
109
165
|
label: "Notify Card",
|
|
110
|
-
description: "Post a structured Feishu card to the Schedule's configured notify group.
|
|
166
|
+
description: "Post a structured Feishu card to the Schedule's configured notify group. Only valid when this Run has a notify target. You must still close the conversational channel with reply_text, reply_card, or no_reply on the same Run.",
|
|
111
167
|
parameters: {
|
|
112
168
|
type: "object",
|
|
113
169
|
properties: {
|
package/dist/runtime/pi-rpc.js
CHANGED
|
@@ -45,6 +45,27 @@ function collectText(message) {
|
|
|
45
45
|
.join("")
|
|
46
46
|
.trim();
|
|
47
47
|
}
|
|
48
|
+
/** Collect thinking / reasoning blocks from an assistant message content array. */
|
|
49
|
+
export function collectThinking(content) {
|
|
50
|
+
return content
|
|
51
|
+
.map((block) => {
|
|
52
|
+
if (!isObject(block))
|
|
53
|
+
return "";
|
|
54
|
+
if (block.type === "thinking") {
|
|
55
|
+
if (typeof block.thinking === "string")
|
|
56
|
+
return block.thinking;
|
|
57
|
+
if (typeof block.text === "string")
|
|
58
|
+
return block.text;
|
|
59
|
+
}
|
|
60
|
+
if (block.type === "reasoning" && typeof block.text === "string") {
|
|
61
|
+
return block.text;
|
|
62
|
+
}
|
|
63
|
+
return "";
|
|
64
|
+
})
|
|
65
|
+
.filter((part) => part.trim().length > 0)
|
|
66
|
+
.join("\n")
|
|
67
|
+
.trim();
|
|
68
|
+
}
|
|
48
69
|
function buildArguments(request) {
|
|
49
70
|
const args = ["--mode", "rpc", "--no-approve"];
|
|
50
71
|
if (request.session) {
|
|
@@ -257,10 +278,13 @@ export async function runPiAgent(request, options = {}) {
|
|
|
257
278
|
fail(new Error("Pi completed without a textual final response"));
|
|
258
279
|
return;
|
|
259
280
|
}
|
|
281
|
+
const thinking = collectThinking(finalMessage.content);
|
|
260
282
|
const result = {
|
|
261
283
|
text,
|
|
262
284
|
provider: finalMessage.provider,
|
|
263
285
|
model: finalMessage.model,
|
|
286
|
+
stopReason: finalMessage.stopReason,
|
|
287
|
+
...(thinking ? { thinking } : {}),
|
|
264
288
|
};
|
|
265
289
|
finished = true;
|
|
266
290
|
clearTimeout(timer);
|
|
@@ -33,29 +33,30 @@ export function buildAgentSystemPrompt(profile, trigger) {
|
|
|
33
33
|
].join("\n");
|
|
34
34
|
}
|
|
35
35
|
function thisRunCapabilityInstructions(trigger) {
|
|
36
|
+
const notifyLine = trigger.notify
|
|
37
|
+
? "You may also call `notify_card` at most once."
|
|
38
|
+
: "Do not call `notify_card`.";
|
|
36
39
|
if (trigger.kind === "feishu_message") {
|
|
37
40
|
return [
|
|
38
41
|
"This Run is an inbound Feishu message.",
|
|
39
|
-
"Close the conversational channel with
|
|
40
|
-
"Do not call `no_reply`.
|
|
42
|
+
"Close the conversational channel with exactly one of `reply_text` or `reply_card`.",
|
|
43
|
+
"Do not call `no_reply`.",
|
|
44
|
+
notifyLine,
|
|
41
45
|
];
|
|
42
46
|
}
|
|
43
|
-
const notifyLine = trigger.notify
|
|
44
|
-
? "You may also call `notify_card` at most once."
|
|
45
|
-
: "Do not call `notify_card`.";
|
|
46
47
|
if (trigger.kind === "schedule") {
|
|
47
48
|
const label = trigger.scheduleId
|
|
48
49
|
? `This Run is Schedule ${trigger.scheduleId}.`
|
|
49
50
|
: "This Run is a Schedule.";
|
|
50
51
|
return [
|
|
51
52
|
label,
|
|
52
|
-
"Close the conversational channel with exactly one of `
|
|
53
|
+
"Close the conversational channel with exactly one of `reply_text`, `reply_card`, or `no_reply`.",
|
|
53
54
|
notifyLine,
|
|
54
55
|
];
|
|
55
56
|
}
|
|
56
57
|
return [
|
|
57
58
|
"This Run is a manual operator trigger.",
|
|
58
|
-
"Close the conversational channel with exactly one of `
|
|
59
|
+
"Close the conversational channel with exactly one of `reply_text`, `reply_card`, or `no_reply`.",
|
|
59
60
|
notifyLine,
|
|
60
61
|
];
|
|
61
62
|
}
|