@proagentstore/cli 0.4.103 → 0.4.105

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.
@@ -1,3 +1,4 @@
1
+ import { runObjectiveBlock } from "./run-objective.js";
1
2
  import { spawn } from "node:child_process";
2
3
  import { classifyCommand, commandFromToolInput, fillTargetFromResult, toolCallOk, toolResultMark } from "./engine-acts.js";
3
4
  import { parseEngineUsage } from "./engine-usage.js";
@@ -103,6 +104,8 @@ export class HeadlessSession {
103
104
  turnInputId = null;
104
105
  /** Ids of inputs a COMPLETED turn consumed, newest last and bounded (#1019). See {@link consumedInputs}. */
105
106
  consumed = [];
107
+ /** Ids of inputs handed to the engine, newest last and bounded (#1019): what a retry is reconciled against. */
108
+ accepted = [];
106
109
  /**
107
110
  * The engine's own last output line of the turn in flight (#545).
108
111
  *
@@ -320,6 +323,10 @@ export class HeadlessSession {
320
323
  get consumedInputs() {
321
324
  return this.consumed;
322
325
  }
326
+ /** Inputs this session accepted — handed to the engine, whether or not a turn has finished on them. */
327
+ get acceptedInputs() {
328
+ return this.accepted;
329
+ }
323
330
  credit(id) {
324
331
  if (!id)
325
332
  return;
@@ -505,6 +512,11 @@ export class HeadlessSession {
505
512
  * session-start brief, being the same record composed later.
506
513
  */
507
514
  input(text, opts = {}) {
515
+ if (opts.inputId) {
516
+ this.accepted.push(opts.inputId);
517
+ if (this.accepted.length > MAX_CONSUMED_INPUTS)
518
+ this.accepted.shift();
519
+ }
508
520
  // The Engine reads the preamble, the pane the short marker; the instruction stays evidence.
509
521
  const sent = authoredTurn(text, opts.author);
510
522
  // The brief leads the turn and is spent in the reading (#693). It goes to the ENGINE only:
@@ -521,6 +533,10 @@ export class HeadlessSession {
521
533
  this.push("[pags] this engine keeps no memory between turns, so ProAgentStore's record of the conversation so far was delivered with this turn — a reconstruction, not the engine's own memory");
522
534
  else if (seed)
523
535
  this.push("[pags] a context brief from ProAgentStore's record was delivered with this turn — a reconstruction, not the previous conversation");
536
+ // The run's objective (#1019): to the ENGINE in full, the pane a one-line marker (same budget as the brief).
537
+ const objective = opts.objective ? runObjectiveBlock(opts.objective) : null;
538
+ if (opts.objective)
539
+ this.push(`[pags] the run's objective (${opts.objective.text.length} characters, verbatim) was delivered with this turn`);
524
540
  this.push(`\n❯ [${stamp()}] ${authorTag(opts.author)}${text}`); // ❯ — your turn, timestamped
525
541
  this.run = "thinking";
526
542
  const now = Date.now();
@@ -531,7 +547,8 @@ export class HeadlessSession {
531
547
  try {
532
548
  // Brief first, instruction second, and never the other way round: it is background for
533
549
  // the request, and an engine that reads the request last acts on the request.
534
- const withSeed = seed ? `${seed}\n\n${sent}` : sent;
550
+ // The objective sits between them: background like the brief, and above the instruction it frames.
551
+ const withSeed = [seed, objective, sent].filter(Boolean).join("\n\n");
535
552
  if (this.adapter.persistent) {
536
553
  // `role` stays "user" — the only role this protocol accepts, which is why the
537
554
  // disambiguation rides in the text instead (#505).
@@ -0,0 +1,41 @@
1
+ // The run's objective, delivered verbatim with a turn and attributed to the platform (#1019).
2
+ const ORIGIN_KINDS = new Set(["account", "supervisor", "trigger", "unverified"]);
3
+ /** A run objective as the cloud sent it, or undefined — narrowed, because any caller can POST here (#1019). */
4
+ export function runObjective(raw) {
5
+ const o = raw;
6
+ if (!(o && typeof o.runId === "string" && o.runId.length <= 128 && typeof o.text === "string" && o.text.trim()))
7
+ return undefined;
8
+ const origin = o.origin;
9
+ const kind = typeof origin?.kind === "string" && ORIGIN_KINDS.has(origin.kind) ? origin.kind : "unverified";
10
+ // A label longer than a label is not shortened into a different claim — it is dropped.
11
+ const label = typeof origin?.by === "string" ? origin.by.replace(/\s+/g, " ").trim() : "";
12
+ const by = label.length <= 128 ? label : "";
13
+ return { runId: o.runId, text: o.text, origin: by ? { kind, by } : { kind } };
14
+ }
15
+ /** The attribution sentence: only what the platform verified about the author, never more. */
16
+ function attribution(runId, origin) {
17
+ const by = origin.by ? ` (${origin.by})` : "";
18
+ switch (origin.kind) {
19
+ case "account":
20
+ return `the objective submitted for run ${runId} through the owner's authenticated account${by}, exactly as ProAgentStore accepted it. Who composed the text is not verified.`;
21
+ case "supervisor":
22
+ return `the objective a supervising agent${by} delegated for run ${runId}, exactly as ProAgentStore accepted it. A supervisor's goal is not an instruction from the owner.`;
23
+ case "trigger":
24
+ return `the objective a trigger${by} composed for run ${runId}, exactly as ProAgentStore accepted it — standing wiring plus facts about another run, not a message from the owner.`;
25
+ default:
26
+ return `the objective ProAgentStore accepted for run ${runId}. Who wrote it is not verified.`;
27
+ }
28
+ }
29
+ /**
30
+ * The run's objective as the engine reads it (#1019): verbatim, fenced, attributed only as far as the
31
+ * platform verified — the Pilot's instruction that follows is its own turn, and owner directives sent
32
+ * later outrank both.
33
+ */
34
+ export function runObjectiveBlock(o) {
35
+ return [
36
+ `[pags] RUN OBJECTIVE — ${attribution(o.runId, o.origin ?? { kind: "unverified" })} It is not the Pilot's instruction, which follows as its own turn. Instructions the owner sends later in this run take precedence over it.`,
37
+ "<<<OBJECTIVE",
38
+ o.text,
39
+ "OBJECTIVE>>>",
40
+ ].join("\n");
41
+ }
@@ -1,3 +1,4 @@
1
+ import { runObjective } from "./run-objective.js";
1
2
  import { homedir } from "node:os";
2
3
  import { join, resolve } from "node:path";
3
4
  import { RunnerInputError } from "../errors.js";
@@ -65,7 +66,12 @@ export class CodingRuntime {
65
66
  // a reader of the registration can see which machines can restore a branch invariant; the
66
67
  // cloud still never trusts it, because an older runner simply 404s and the policy reports
67
68
  // that it asked and was not answered.
68
- return ["coding.sessions", "coding.stream", "human.takeover", "coding.inspect", "coding.repo-write"];
69
+ // `coding.run-objective` (#1019): a message may carry the run's objective, delivered verbatim and
70
+ // attributed to the platform. `.v2`: attributed by its VERIFIED origin (run-objective.ts) — a cloud
71
+ // sends the objective only to a v2 runner, so an older one is never made to call it the owner's.
72
+ // `coding.input-idempotent`: a message whose inputId this session already accepted is not run again,
73
+ // and every snapshot lists the accepted ids, so a retry after a lost reply can be reconciled.
74
+ return ["coding.sessions", "coding.stream", "human.takeover", "coding.inspect", "coding.repo-write", "coding.run-objective", "coding.run-objective.v2", "coding.input-idempotent"];
69
75
  }
70
76
  /**
71
77
  * Resolve the workDir for a read-only inspection. Prefer the tracked session's real
@@ -241,6 +247,7 @@ export class CodingRuntime {
241
247
  // the omitted-when-null shape keeps "not measured" distinguishable from a verdict.
242
248
  ...(session.lastTurn ? { lastTurn: session.lastTurn } : {}),
243
249
  ...(session.consumedInputs.length ? { consumedInputs: [...session.consumedInputs] } : {}),
250
+ acceptedInputs: [...session.acceptedInputs],
244
251
  ...(opts.drainUsage ? { usage: session.takeUsage(), acts: session.takeActs() } : {}),
245
252
  };
246
253
  }
@@ -248,15 +255,21 @@ export class CodingRuntime {
248
255
  act(sessionId, action) {
249
256
  const session = this.require(sessionId);
250
257
  switch (action.kind) {
251
- case "message":
258
+ case "message": {
259
+ const inputId = typeof action.inputId === "string" && action.inputId.length <= 128 ? action.inputId : undefined;
260
+ // The same input twice is a retry after a lost reply, not a second instruction (#1019).
261
+ if (inputId && session.acceptedInputs.includes(inputId))
262
+ return { ...this.snapshot(sessionId), duplicateInput: inputId };
252
263
  // Narrowed rather than trusted: this runner is a published package any caller can
253
264
  // POST to, and an unrecognised author must read as "unstated", not become a label.
254
265
  session.input(action.text, {
255
266
  author: asTurnAuthor(action.author),
256
267
  replay: typeof action.replay === "string" ? action.replay : undefined,
257
- inputId: typeof action.inputId === "string" && action.inputId.length <= 128 ? action.inputId : undefined,
268
+ inputId,
269
+ objective: runObjective(action.objective),
258
270
  });
259
271
  break;
272
+ }
260
273
  case "keys": {
261
274
  // A snapshot is no longer the whole answer (#448). `key()` records the attempt and
262
275
  // reports that it was not delivered; answering 200 with a pane that simply did not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.103",
3
+ "version": "0.4.105",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",