@prohost/cli 0.7.0 → 0.8.1
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/CHANGELOG.md +36 -0
- package/README.md +4 -0
- package/dist/agent/api.d.ts +1 -0
- package/dist/agent/api.js +2 -2
- package/dist/agent/contract.d.ts +44 -0
- package/dist/agent/contract.js +42 -0
- package/dist/agent/prompt.js +46 -7
- package/dist/agent/run.js +18 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ Versions follow [semver](https://semver.org/). Publishing is automated: merging
|
|
|
4
4
|
a version bump to `main` triggers `.github/workflows/npm-publish-cli.yml`, which
|
|
5
5
|
builds via `prepack`, runs the suite, publishes, and tags `cli-v<version>`.
|
|
6
6
|
|
|
7
|
+
## 0.8.1
|
|
8
|
+
|
|
9
|
+
A long message no longer reaches a paired agent as a fragment it can't recover.
|
|
10
|
+
|
|
11
|
+
- **Clipped trigger text is called out.** When the server reports that the
|
|
12
|
+
message that woke the agent was clipped (`trigger.text_truncated: true`, new
|
|
13
|
+
in the run event), the prompt says so and, when the agent has ProhostAI tools
|
|
14
|
+
and a conversation, tells it to read the full message with
|
|
15
|
+
`get_conversation_messages` before answering. An agent without tools is told
|
|
16
|
+
to say the message reached it truncated instead of answering the fragment.
|
|
17
|
+
Keyed on the explicit flag, never on a trailing `…` a person could have typed;
|
|
18
|
+
a server that predates the flag reads as "not clipped". Pairs with the server raising the excerpt
|
|
19
|
+
cap from 500 to 16,000 characters and returning full message bodies from
|
|
20
|
+
`get_conversation_messages`, so this path is now rare.
|
|
21
|
+
|
|
22
|
+
## 0.8.0
|
|
23
|
+
|
|
24
|
+
A paired agent woken as a bystander can now stay silent.
|
|
25
|
+
|
|
26
|
+
- **`reply_expected: false`.** When a team-chat message lands in a thread the
|
|
27
|
+
agent belongs to but is addressed to someone else (ProhostAI's participation
|
|
28
|
+
walk), the run event now says so. The prompt tells the agent the message was
|
|
29
|
+
not addressed to it and nobody is waiting on it — instead of "someone is
|
|
30
|
+
waiting on a reply" — and renders the server's `trigger.brief`, the same
|
|
31
|
+
bystander guidance ProhostAI's in-product agents get. Absent (every older
|
|
32
|
+
server) keeps today's prompt.
|
|
33
|
+
- **`[NO_REPLY]`.** An agent whose whole answer is `[NO_REPLY]` (any case,
|
|
34
|
+
surrounding whitespace ignored) posts nothing, and the run is completed
|
|
35
|
+
`{status: "succeeded", outcome: "silent"}` so ProhostAI records a deliberate
|
|
36
|
+
silence and clears the thinking indicator as one. Checked on every run, so
|
|
37
|
+
the literal token can never reach a thread. A server that predates `outcome`
|
|
38
|
+
ignores it and records a plain success.
|
|
39
|
+
- **`trigger.brief` is rendered** whenever the server sends one that is not just
|
|
40
|
+
the message restated — a work order used to be dropped on any run that also
|
|
41
|
+
had a message.
|
|
42
|
+
|
|
7
43
|
## 0.7.0
|
|
8
44
|
|
|
9
45
|
Machines, accounts and agents: a paired AI employee now runs on a named
|
package/README.md
CHANGED
|
@@ -117,6 +117,10 @@ nothing is posted and the run is left open.
|
|
|
117
117
|
4. Your command's **stdout** is the reply. It's posted into the conversation
|
|
118
118
|
verbatim, so print the message and nothing else — no preamble, no reasoning.
|
|
119
119
|
(Claude Code and Codex are the exceptions: see below.)
|
|
120
|
+
To reply with nothing, print exactly `[NO_REPLY]`: nothing is posted and the
|
|
121
|
+
run is completed as a deliberate silence. That is the expected answer when
|
|
122
|
+
your agent is woken only because it is in a thread where someone else was
|
|
123
|
+
addressed — the prompt says so when that is the case.
|
|
120
124
|
5. The run is completed either way. A command that crashes, times out, or prints
|
|
121
125
|
nothing reports `failed`; a run is never left hanging.
|
|
122
126
|
|
package/dist/agent/api.d.ts
CHANGED
package/dist/agent/api.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* `completion.path`), which is why these take a path rather than building one.
|
|
11
11
|
*/
|
|
12
12
|
import { USER_AGENT } from '../version.js';
|
|
13
|
-
import { MAX_ERROR_CHARS, MAX_MODEL_CHARS, MAX_PROGRESS_CHARS, MAX_TOOL_EVENTS_PER_BATCH, RUN_STATUS_FAILED, RUN_STATUS_SUCCEEDED, } from './contract.js';
|
|
13
|
+
import { MAX_ERROR_CHARS, MAX_MODEL_CHARS, MAX_PROGRESS_CHARS, MAX_TOOL_EVENTS_PER_BATCH, RUN_OUTCOME_SILENT, RUN_STATUS_FAILED, RUN_STATUS_SUCCEEDED, } from './contract.js';
|
|
14
14
|
/** Deadline for the cheap calls — a heartbeat answers in milliseconds. */
|
|
15
15
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
16
16
|
/**
|
|
@@ -176,7 +176,7 @@ export async function heartbeatRun(options, path, progress) {
|
|
|
176
176
|
*/
|
|
177
177
|
export async function completeRun(options, path, outcome, model) {
|
|
178
178
|
const body = outcome.ok
|
|
179
|
-
? { status: RUN_STATUS_SUCCEEDED }
|
|
179
|
+
? { status: RUN_STATUS_SUCCEEDED, ...(outcome.silent ? { outcome: RUN_OUTCOME_SILENT } : {}) }
|
|
180
180
|
: { status: RUN_STATUS_FAILED, error: outcome.error.slice(0, MAX_ERROR_CHARS) };
|
|
181
181
|
if (model)
|
|
182
182
|
body.model = model.slice(0, MAX_MODEL_CHARS);
|
package/dist/agent/contract.d.ts
CHANGED
|
@@ -28,6 +28,27 @@ export declare const EVENT_MESSAGE_TEAM_CHAT = "message.team_chat";
|
|
|
28
28
|
/** Terminal statuses accepted by the run-completion endpoint. */
|
|
29
29
|
export declare const RUN_STATUS_SUCCEEDED = "succeeded";
|
|
30
30
|
export declare const RUN_STATUS_FAILED = "failed";
|
|
31
|
+
/**
|
|
32
|
+
* Completion `outcome` for a run that deliberately posted nothing.
|
|
33
|
+
*
|
|
34
|
+
* Sent with `status: succeeded`: declining is a finished run, not a failure.
|
|
35
|
+
* The server records it the way an in-product `[NO_REPLY]` run is recorded and
|
|
36
|
+
* clears the thinking indicator as a "chose silence" rather than implying a
|
|
37
|
+
* reply landed. A server that predates the field ignores it, so it needs no
|
|
38
|
+
* version negotiation.
|
|
39
|
+
*/
|
|
40
|
+
export declare const RUN_OUTCOME_SILENT = "silent";
|
|
41
|
+
/**
|
|
42
|
+
* What the agent writes as its WHOLE answer to say "no reply".
|
|
43
|
+
*
|
|
44
|
+
* The same token the in-product brain uses, so an agent's instructions read the
|
|
45
|
+
* same on either runtime. Matched on the trimmed output, case-insensitively —
|
|
46
|
+
* and never posted: the literal token landing in a thread is the one outcome
|
|
47
|
+
* that is worse than either replying or not.
|
|
48
|
+
*/
|
|
49
|
+
export declare const NO_REPLY_TOKEN = "[NO_REPLY]";
|
|
50
|
+
/** Whether the agent's output is the no-reply token and nothing else. */
|
|
51
|
+
export declare function isNoReply(output: string): boolean;
|
|
31
52
|
/** Server-side cap on the `error` field of a completion (`MAX_ERROR_CHARS`). */
|
|
32
53
|
export declare const MAX_ERROR_CHARS = 1000;
|
|
33
54
|
/** Server-side cap on the `model` field of a completion (`MAX_MODEL_CHARS`). */
|
|
@@ -180,6 +201,8 @@ export declare const MAX_ATTACHMENTS = 10;
|
|
|
180
201
|
* quietly stop half-way through with nothing to explain it.
|
|
181
202
|
*/
|
|
182
203
|
export declare const MAX_SYSTEM_PROMPT_CHARS = 20000;
|
|
204
|
+
/** Ceiling on `trigger.brief` — the server's `MAX_TRIGGER_BRIEF_CHARS`. */
|
|
205
|
+
export declare const MAX_TRIGGER_BRIEF_CHARS = 32000;
|
|
183
206
|
/** The `agent.run_requested` payload, as far as the harness cares about it. */
|
|
184
207
|
export interface AgentRunRequest {
|
|
185
208
|
run_id: string;
|
|
@@ -212,6 +235,12 @@ export interface AgentRunRequest {
|
|
|
212
235
|
trigger_message_id?: string;
|
|
213
236
|
/** Capped inline excerpt of the message that woke the agent. */
|
|
214
237
|
trigger_text?: string;
|
|
238
|
+
/**
|
|
239
|
+
* The server clipped `trigger_text` (`trigger.text_truncated`). Explicit
|
|
240
|
+
* metadata rather than a guess from a trailing ellipsis, which a person can
|
|
241
|
+
* also type. Absent on servers that predate it — read as "not clipped".
|
|
242
|
+
*/
|
|
243
|
+
trigger_text_truncated?: boolean;
|
|
215
244
|
/**
|
|
216
245
|
* Images and files on the message that woke the agent.
|
|
217
246
|
*
|
|
@@ -220,6 +249,21 @@ export interface AgentRunRequest {
|
|
|
220
249
|
*/
|
|
221
250
|
trigger_attachments?: RunAttachment[];
|
|
222
251
|
trigger_user_id?: string;
|
|
252
|
+
/**
|
|
253
|
+
* The server's work order for this run, when it wrote one: a routine's
|
|
254
|
+
* instructions, a heartbeat's standing assignment, or — on a team-chat
|
|
255
|
+
* participant wakeup — the guidance for a message not addressed to the agent.
|
|
256
|
+
* Absent on older servers and on plain mentions.
|
|
257
|
+
*/
|
|
258
|
+
trigger_brief?: string;
|
|
259
|
+
/**
|
|
260
|
+
* `false` when the message was NOT addressed to this agent: it was woken only
|
|
261
|
+
* because it is a member of the thread. Nobody is waiting on it, and the
|
|
262
|
+
* right answer is usually silence. `true` for everything else — including
|
|
263
|
+
* every event from a server that predates the field (parsed events always
|
|
264
|
+
* carry it; absent means `true`).
|
|
265
|
+
*/
|
|
266
|
+
reply_expected?: boolean;
|
|
223
267
|
/** For a trigger fan-out with no surface: the event type that fired it. */
|
|
224
268
|
trigger_type?: string;
|
|
225
269
|
/** For a trigger fan-out with no surface: the entity that event was about. */
|
package/dist/agent/contract.js
CHANGED
|
@@ -28,6 +28,29 @@ export const EVENT_MESSAGE_TEAM_CHAT = 'message.team_chat';
|
|
|
28
28
|
/** Terminal statuses accepted by the run-completion endpoint. */
|
|
29
29
|
export const RUN_STATUS_SUCCEEDED = 'succeeded';
|
|
30
30
|
export const RUN_STATUS_FAILED = 'failed';
|
|
31
|
+
/**
|
|
32
|
+
* Completion `outcome` for a run that deliberately posted nothing.
|
|
33
|
+
*
|
|
34
|
+
* Sent with `status: succeeded`: declining is a finished run, not a failure.
|
|
35
|
+
* The server records it the way an in-product `[NO_REPLY]` run is recorded and
|
|
36
|
+
* clears the thinking indicator as a "chose silence" rather than implying a
|
|
37
|
+
* reply landed. A server that predates the field ignores it, so it needs no
|
|
38
|
+
* version negotiation.
|
|
39
|
+
*/
|
|
40
|
+
export const RUN_OUTCOME_SILENT = 'silent';
|
|
41
|
+
/**
|
|
42
|
+
* What the agent writes as its WHOLE answer to say "no reply".
|
|
43
|
+
*
|
|
44
|
+
* The same token the in-product brain uses, so an agent's instructions read the
|
|
45
|
+
* same on either runtime. Matched on the trimmed output, case-insensitively —
|
|
46
|
+
* and never posted: the literal token landing in a thread is the one outcome
|
|
47
|
+
* that is worse than either replying or not.
|
|
48
|
+
*/
|
|
49
|
+
export const NO_REPLY_TOKEN = '[NO_REPLY]';
|
|
50
|
+
/** Whether the agent's output is the no-reply token and nothing else. */
|
|
51
|
+
export function isNoReply(output) {
|
|
52
|
+
return output.trim().toUpperCase() === NO_REPLY_TOKEN;
|
|
53
|
+
}
|
|
31
54
|
/** Server-side cap on the `error` field of a completion (`MAX_ERROR_CHARS`). */
|
|
32
55
|
export const MAX_ERROR_CHARS = 1000;
|
|
33
56
|
/** Server-side cap on the `model` field of a completion (`MAX_MODEL_CHARS`). */
|
|
@@ -125,6 +148,8 @@ export const MAX_ATTACHMENTS = 10;
|
|
|
125
148
|
* quietly stop half-way through with nothing to explain it.
|
|
126
149
|
*/
|
|
127
150
|
export const MAX_SYSTEM_PROMPT_CHARS = 20_000;
|
|
151
|
+
/** Ceiling on `trigger.brief` — the server's `MAX_TRIGGER_BRIEF_CHARS`. */
|
|
152
|
+
export const MAX_TRIGGER_BRIEF_CHARS = 32_000;
|
|
128
153
|
/**
|
|
129
154
|
* Fallback for a server that predates `reply_instructions.body_field`.
|
|
130
155
|
*
|
|
@@ -229,6 +254,16 @@ function systemPrompt(value) {
|
|
|
229
254
|
return undefined;
|
|
230
255
|
return text.length > MAX_SYSTEM_PROMPT_CHARS ? text.slice(0, MAX_SYSTEM_PROMPT_CHARS) : text;
|
|
231
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Bound the server's work order. Same backstop as {@link systemPrompt}, at the
|
|
259
|
+
* server's own ceiling for the field (`MAX_TRIGGER_BRIEF_CHARS`).
|
|
260
|
+
*/
|
|
261
|
+
function brief(value) {
|
|
262
|
+
const text = str(value);
|
|
263
|
+
if (!text)
|
|
264
|
+
return undefined;
|
|
265
|
+
return text.length > MAX_TRIGGER_BRIEF_CHARS ? text.slice(0, MAX_TRIGGER_BRIEF_CHARS) : text;
|
|
266
|
+
}
|
|
232
267
|
function parseMessage(entry) {
|
|
233
268
|
const attachments = attachmentList(entry.attachments);
|
|
234
269
|
// Not `str()`: an image-only message carries an empty string here and is a
|
|
@@ -302,8 +337,15 @@ export function parseRunRequest(data) {
|
|
|
302
337
|
trigger_source: str(trigger.source) ?? str(data.trigger_source),
|
|
303
338
|
trigger_message_id: str(trigger.message_id),
|
|
304
339
|
trigger_text: str(trigger.text_excerpt),
|
|
340
|
+
// Only an explicit `true` counts: anything else is an unclipped message.
|
|
341
|
+
trigger_text_truncated: trigger.text_truncated === true,
|
|
305
342
|
trigger_attachments: attachmentList(trigger.attachments ?? data.trigger_attachments),
|
|
306
343
|
trigger_user_id: str(trigger.user_id),
|
|
344
|
+
trigger_brief: brief(trigger.brief),
|
|
345
|
+
// Only an explicit `false` opts out. Absent, null, or anything malformed
|
|
346
|
+
// keeps today's behaviour — an agent that stays quiet when it was asked
|
|
347
|
+
// something is a worse failure than one that answers an aside.
|
|
348
|
+
reply_expected: data.reply_expected !== false,
|
|
307
349
|
trigger_type: str(trigger.type),
|
|
308
350
|
trigger_entity_id: str(trigger.entity_id),
|
|
309
351
|
reply_surface: surface,
|
package/dist/agent/prompt.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* a shell script that greps it. The reply contract is stated explicitly so an
|
|
7
7
|
* agent that would otherwise narrate its reasoning returns something sendable.
|
|
8
8
|
*/
|
|
9
|
+
import { NO_REPLY_TOKEN } from './contract.js';
|
|
9
10
|
/**
|
|
10
11
|
* Say the run's budget in a unit a reader thinks in.
|
|
11
12
|
*
|
|
@@ -129,15 +130,23 @@ export function buildPrompt(run, context) {
|
|
|
129
130
|
const name = run.agent_name ?? context.agentName;
|
|
130
131
|
const canReply = Boolean(run.reply_surface !== 'none' && run.reply_path && run.reply_body_key);
|
|
131
132
|
const title = run.agent_title ? ` (${run.agent_title})` : '';
|
|
133
|
+
// Woken as a bystander: a message landed in a thread the agent belongs to,
|
|
134
|
+
// addressed to somebody else. Telling it "someone is waiting on a reply"
|
|
135
|
+
// here is what made a paired agent post a long, unrequested summary onto a
|
|
136
|
+
// message that @-mentioned three people — so the opening says the opposite.
|
|
137
|
+
const bystander = canReply && run.reply_expected === false;
|
|
132
138
|
const lines = [
|
|
133
139
|
`You are "${name}"${title}, an AI teammate in a ProhostAI workspace.`,
|
|
134
|
-
|
|
135
|
-
?
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
bystander
|
|
141
|
+
? `A new message was posted in a ${run.reply_surface} you are a member of. It was not ` +
|
|
142
|
+
'addressed to you, and nobody is waiting on a reply from you.'
|
|
143
|
+
: canReply && run.reply_surface === 'conversation'
|
|
144
|
+
? 'You were mentioned in a conversation and someone is waiting on a reply.'
|
|
145
|
+
: run.reply_surface === 'none'
|
|
146
|
+
? 'You have been given work to do. There is nowhere to reply.'
|
|
147
|
+
: canReply
|
|
148
|
+
? `You were mentioned on a ${run.reply_surface} and someone is waiting on a reply.`
|
|
149
|
+
: `You have been given work on a ${run.reply_surface}. There is nowhere to post a reply.`,
|
|
141
150
|
'',
|
|
142
151
|
];
|
|
143
152
|
// The operator's own configuration comes first, above everything the server
|
|
@@ -187,11 +196,34 @@ export function buildPrompt(run, context) {
|
|
|
187
196
|
if (run.recent_messages?.length) {
|
|
188
197
|
lines.push('Conversation so far (oldest first):', ...run.recent_messages.map(renderMessage), '--- end of conversation history ---', '');
|
|
189
198
|
}
|
|
199
|
+
// The server's own work order, when it wrote one that is not simply the
|
|
200
|
+
// message restated (a run with no message of its own already received the
|
|
201
|
+
// brief as its excerpt).
|
|
202
|
+
if (run.trigger_brief && run.trigger_brief !== run.trigger_text) {
|
|
203
|
+
lines.push('Your instructions for this run, from ProhostAI:', run.trigger_brief, '');
|
|
204
|
+
}
|
|
190
205
|
const triggerAttachments = run.trigger_attachments ?? [];
|
|
191
206
|
if (run.trigger_text) {
|
|
192
207
|
// The server caps this excerpt, so say so rather than let an agent assume
|
|
193
208
|
// it has the whole message.
|
|
194
209
|
lines.push('Message that woke you (may be truncated by the server):', run.trigger_text, '');
|
|
210
|
+
// An agent that is only told the text "may be" truncated answers the
|
|
211
|
+
// fragment — or asks the sender to repost — when the whole message is one
|
|
212
|
+
// tool call away. Keyed on the server's explicit flag, never on a trailing
|
|
213
|
+
// ellipsis a person could have typed.
|
|
214
|
+
if (run.trigger_text_truncated) {
|
|
215
|
+
lines.push(...(context.mcpServerName && run.conversation_id
|
|
216
|
+
? [
|
|
217
|
+
'ProhostAI cut that message short. Before answering, read it in full with',
|
|
218
|
+
`the ProhostAI get_conversation_messages tool (conversation ${run.conversation_id})`,
|
|
219
|
+
'and answer the whole message, not the excerpt above.',
|
|
220
|
+
]
|
|
221
|
+
: [
|
|
222
|
+
'ProhostAI cut that message short, and you have no tool to read the rest.',
|
|
223
|
+
'Answer what you can see and say plainly that the message reached you',
|
|
224
|
+
'truncated.',
|
|
225
|
+
]), '');
|
|
226
|
+
}
|
|
195
227
|
}
|
|
196
228
|
else if (triggerAttachments.length > 0) {
|
|
197
229
|
// Distinct from "the server didn't send the text": here the message really
|
|
@@ -305,6 +337,13 @@ export function buildPrompt(run, context) {
|
|
|
305
337
|
// Postability is a separate question from what the run is about: a task run
|
|
306
338
|
// has no comment route in the external API yet, so the agent must be told its
|
|
307
339
|
// output won't be posted rather than left to assume it will.
|
|
340
|
+
if (bystander) {
|
|
341
|
+
// Silence is stated as the default and given a concrete spelling, because
|
|
342
|
+
// "reply only if useful" without a way to NOT reply still ends in stdout
|
|
343
|
+
// that gets posted.
|
|
344
|
+
lines.push('Staying silent is the expected outcome here. Reply only if you can add', 'something clearly distinct that nobody else in the thread was asked for. If', `you decide not to reply, output ONLY the token ${NO_REPLY_TOKEN} — nothing is`, 'posted and the run is recorded as a deliberate silence. Never write a message', 'explaining that you are staying quiet.', 'If you do reply, write the message text only — no preamble, no explanation of', `your reasoning, no surrounding quotes. Anything other than ${NO_REPLY_TOKEN} is`, `posted verbatim as your reply on the ${run.reply_surface}.`);
|
|
345
|
+
return `${lines.join('\n')}\n`;
|
|
346
|
+
}
|
|
308
347
|
lines.push(canReply
|
|
309
348
|
? 'Write your reply to that message. Reply with the message text only — no'
|
|
310
349
|
: 'Summarize what you did, in one short message. Output the message only — no', 'preamble, no explanation of your reasoning, no surrounding quotes. Your', canReply
|
package/dist/agent/run.js
CHANGED
|
@@ -33,7 +33,7 @@ import { APPS_FEATURE, MCP_API_KEY_ENV_VAR, PLUGINS_DISABLED_OVERRIDE, codexBina
|
|
|
33
33
|
import { ToolEventQueue } from './events.js';
|
|
34
34
|
import { EXTRA_MCP_FILENAME, loadExtraMcpServers } from './extras.js';
|
|
35
35
|
import { MCP_SERVER_NAME, MCP_TOOL_PATTERN } from './mcp.js';
|
|
36
|
-
import { EVENT_AGENT_RUN_REQUESTED, EVENT_MENTION_CREATED, EVENT_MESSAGE_TEAM_CHAT, RUN_ERROR_CANCELLED_BY_USER, eventData, heartbeatPathFor, parseRunRequest, } from './contract.js';
|
|
36
|
+
import { EVENT_AGENT_RUN_REQUESTED, EVENT_MENTION_CREATED, EVENT_MESSAGE_TEAM_CHAT, RUN_ERROR_CANCELLED_BY_USER, eventData, heartbeatPathFor, isNoReply, parseRunRequest, } from './contract.js';
|
|
37
37
|
import { ensureWorkspace, streamUrlFor, workspacePath } from './credentials.js';
|
|
38
38
|
import { execAgent } from './exec.js';
|
|
39
39
|
import { HeartbeatLoop } from './heartbeat.js';
|
|
@@ -927,6 +927,23 @@ async function handleRun(run, options, runtime, api, log, sleep, control) {
|
|
|
927
927
|
log(` [dry-run] nothing was sent and the run was left open`);
|
|
928
928
|
return 'skipped';
|
|
929
929
|
}
|
|
930
|
+
// The agent decided not to reply — the usual answer when it was woken as a
|
|
931
|
+
// bystander (`reply_expected: false`). A finished run, not a failure: nothing
|
|
932
|
+
// is posted, and the completion says so, so the server records a deliberate
|
|
933
|
+
// silence and clears the thinking indicator as one. Checked on every run, not
|
|
934
|
+
// only bystander ones: the literal token must never reach a thread.
|
|
935
|
+
if (isNoReply(reply)) {
|
|
936
|
+
log(`${timestamp()} ✓ ${label} chose not to reply (${seconds}s)`);
|
|
937
|
+
const completed = await completeWithRetry(api, run.completion_path, { ok: true, silent: true }, log, sleep, runtime.model());
|
|
938
|
+
if (!completed.ok) {
|
|
939
|
+
log(` ! could not mark the run complete (${describeFailure(completed)})`);
|
|
940
|
+
return 'unreported';
|
|
941
|
+
}
|
|
942
|
+
if (result.sessionId) {
|
|
943
|
+
runtime.sessions.remember(sessionKeyFor(run), result.sessionId);
|
|
944
|
+
}
|
|
945
|
+
return 'succeeded';
|
|
946
|
+
}
|
|
930
947
|
// A run with no reply surface is a legitimate outcome, not an error: it was
|
|
931
948
|
// started by a trigger rather than by someone addressing the agent, so there
|
|
932
949
|
// is nowhere to post. Complete it as succeeded so any thinking indicator
|
package/dist/version.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* `package.json`, so a release bump that forgets this file fails the suite
|
|
8
8
|
* instead of shipping a `User-Agent` that lies about which build is calling.
|
|
9
9
|
*/
|
|
10
|
-
export declare const CLI_VERSION = "0.
|
|
10
|
+
export declare const CLI_VERSION = "0.8.1";
|
|
11
11
|
/** Sent on every HTTP request the CLI makes back into ProhostAI. */
|
|
12
|
-
export declare const USER_AGENT = "prohost-cli/0.
|
|
12
|
+
export declare const USER_AGENT = "prohost-cli/0.8.1";
|
package/dist/version.js
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* `package.json`, so a release bump that forgets this file fails the suite
|
|
8
8
|
* instead of shipping a `User-Agent` that lies about which build is calling.
|
|
9
9
|
*/
|
|
10
|
-
export const CLI_VERSION = '0.
|
|
10
|
+
export const CLI_VERSION = '0.8.1';
|
|
11
11
|
/** Sent on every HTTP request the CLI makes back into ProhostAI. */
|
|
12
12
|
export const USER_AGENT = `prohost-cli/${CLI_VERSION}`;
|