@automatalabs/pi-acp 0.3.2 → 0.4.0

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 CHANGED
@@ -88,6 +88,21 @@ cleared synchronously before aborting so queued content never restarts generatio
88
88
  cancellation. Unexpected internal failures also resolve as `{ outcome: "failed" }` rather than a
89
89
  JSON-RPC error.
90
90
 
91
+ ### Loaded-session turn-terminal state (`_session/loaded_turn`)
92
+
93
+ Pi also advertises the `_session/loaded_turn` extension at top-level initialize metadata as
94
+ `_meta: { loadedTurn: { supported: true } }` — the re-attach arm's AUTHORITATIVE completion
95
+ evidence for a session re-opened with `session/load` (the REPL broker's restore path).
96
+ `_session/loaded_turn/query { sessionId }` answers whether the loaded session's founding turn is
97
+ still running right now: `running` while a turn executes in this process (the client then waits
98
+ for the `_session/loaded_turn/ended` push — sent with the turn's stop reason, or its error, when
99
+ the turn finishes, arming and clearing a one-shot watch), `completed` when the session journal's
100
+ last message entry is an assistant message (pi persists every complete LLM message atomically at
101
+ `message_end`, so a completed turn always leaves an assistant leaf and the replay's trailing
102
+ assistant message is the turn's FINAL message — authoritative, never a quiet-gap guess), and
103
+ `interrupted` otherwise (the journal shows an interrupted/abandoned turn — nothing is running, so
104
+ re-issue is safe). A query for an unknown session is the standard `unknown_session` error.
105
+
91
106
  ## Development
92
107
 
93
108
  The `pnpm test` script intentionally runs `tsc -p tsconfig.type-tests.json` before the runtime test suite. This is a small deviation from the test-script example in the frozen specification and ensures the T2b public type-contract check is enforced in local and CI test runs.
package/dist/agent.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { AgentNotificationContext, AgentRequestContext, AuthenticateRequest, CloseSessionRequest, ForkSessionRequest, InitializeRequest, ListSessionsRequest, LoadSessionRequest, NewSessionRequest, PromptRequest, ResumeSessionRequest, SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
2
2
  import type { PiAcpDeps } from "./deps.js";
3
3
  import type { SteeringRequest, SteeringResponse } from "./steering.js";
4
+ import type { LoadedTurnQueryRequest, LoadedTurnStatus } from "./loaded-turn.js";
4
5
  export { PKG_VERSION } from "./version.js";
5
6
  export declare class PiAcpAgent {
6
7
  private readonly deps;
@@ -43,6 +44,9 @@ export declare class PiAcpAgent {
43
44
  steering: {
44
45
  supported: boolean;
45
46
  };
47
+ loadedTurn: {
48
+ supported: boolean;
49
+ };
46
50
  };
47
51
  };
48
52
  authenticate(context: AgentRequestContext<AuthenticateRequest>): Record<string, never>;
@@ -88,6 +92,11 @@ export declare class PiAcpAgent {
88
92
  }>;
89
93
  prompt(context: AgentRequestContext<PromptRequest>): Promise<import("@agentclientprotocol/sdk").PromptResponse>;
90
94
  steer(context: AgentRequestContext<SteeringRequest>): Promise<SteeringResponse>;
95
+ /** `_session/loaded_turn/query` (see `src/loaded-turn.ts`): the loaded
96
+ * session's authoritative founding-turn terminal classification. */
97
+ loadedTurnQuery(context: AgentRequestContext<LoadedTurnQueryRequest>): {
98
+ status: LoadedTurnStatus;
99
+ };
91
100
  cancel(context: AgentNotificationContext<{
92
101
  sessionId: string;
93
102
  }>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAElB,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,6BAA6B,EAC9B,MAAM,0BAA0B,CAAC;AAWlC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAS3C,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGvE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA0H3C,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA8B;IACjE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAM;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,gBAAgB,CAAS;gBAErB,IAAI,EAAE,SAAS;IAI3B,UAAU,CAAC,QAAQ,EAAE,mBAAmB,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmB3D,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,mBAAmB,CAAC;IAI9D,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,IAAI;YAOE,UAAU;YAkCV,SAAS;IAqLvB,OAAO,CAAC,YAAY;IAKpB,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,iBAAiB,CAAC;;;;;IA6B1D,OAAO,CAAC,QAAQ;IAgDhB,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,kBAAkB,CAAC;;;;IAI5D,aAAa,CAAC,OAAO,EAAE,mBAAmB,CAAC,oBAAoB,CAAC;;;;IAIhE,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,kBAAkB,CAAC;;;;;IA8EtD,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,mBAAmB,CAAC;;;;;;;;IAqC9D,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,mBAAmB,CAAC;IA0BpE,OAAO,CAAC,WAAW;IAOnB,eAAe,CAAC,OAAO,EAAE,mBAAmB,CAAC,6BAA6B,CAAC;;;IAU3E,MAAM,CAAC,OAAO,EAAE,mBAAmB,CAAC,aAAa,CAAC;IAIlD,KAAK,CAAC,OAAO,EAAE,mBAAmB,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAW/E,MAAM,CAAC,OAAO,EAAE,wBAAwB,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;YAIxD,eAAe;IAwB7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAWV,iBAAiB;CA4BhC"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAElB,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,6BAA6B,EAC9B,MAAM,0BAA0B,CAAC;AAWlC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAS3C,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGjF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA0H3C,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA8B;IACjE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAM;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,gBAAgB,CAAS;gBAErB,IAAI,EAAE,SAAS;IAI3B,UAAU,CAAC,QAAQ,EAAE,mBAAmB,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmB3D,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,mBAAmB,CAAC;IAI9D,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,IAAI;YAOE,UAAU;YAkCV,SAAS;IAqLvB,OAAO,CAAC,YAAY;IAKpB,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,iBAAiB,CAAC;;;;;IA6B1D,OAAO,CAAC,QAAQ;IAgDhB,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,kBAAkB,CAAC;;;;IAI5D,aAAa,CAAC,OAAO,EAAE,mBAAmB,CAAC,oBAAoB,CAAC;;;;IAIhE,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,kBAAkB,CAAC;;;;;IA8EtD,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,mBAAmB,CAAC;;;;;;;;IAqC9D,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,mBAAmB,CAAC;IA0BpE,OAAO,CAAC,WAAW;IAOnB,eAAe,CAAC,OAAO,EAAE,mBAAmB,CAAC,6BAA6B,CAAC;;;IAU3E,MAAM,CAAC,OAAO,EAAE,mBAAmB,CAAC,aAAa,CAAC;IAIlD,KAAK,CAAC,OAAO,EAAE,mBAAmB,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAW/E;yEACqE;IACrE,eAAe,CAAC,OAAO,EAAE,mBAAmB,CAAC,sBAAsB,CAAC,GAAG;QACrE,MAAM,EAAE,gBAAgB,CAAC;KAC1B;IAID,MAAM,CAAC,OAAO,EAAE,wBAAwB,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;YAIxD,eAAe;IAwB7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAWV,iBAAiB;CA4BhC"}
package/dist/agent.js CHANGED
@@ -148,7 +148,7 @@ export class PiAcpAgent {
148
148
  sessionCapabilities: { resume: {}, fork: {}, list: {}, close: {} },
149
149
  },
150
150
  authMethods: AUTH_METHODS,
151
- _meta: { steering: { supported: true } },
151
+ _meta: { steering: { supported: true }, loadedTurn: { supported: true } },
152
152
  };
153
153
  }
154
154
  authenticate(context) {
@@ -652,6 +652,11 @@ export class PiAcpAgent {
652
652
  return { outcome: "failed" };
653
653
  });
654
654
  }
655
+ /** `_session/loaded_turn/query` (see `src/loaded-turn.ts`): the loaded
656
+ * session's authoritative founding-turn terminal classification. */
657
+ loadedTurnQuery(context) {
658
+ return { status: this.requireLive(context.params.sessionId).loadedTurnStatus() };
659
+ }
655
660
  cancel(context) {
656
661
  this.live.get(context.params.sessionId)?.cancel();
657
662
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The `_session/loaded_turn` vendor extension — turn-TERMINAL state for
3
+ * loaded sessions (the steering-extension precedent; the REPL broker's
4
+ * re-attach arm's authoritative completion evidence). Advertised at
5
+ * initialize via `_meta.loadedTurn.supported === true`; a client seam
6
+ * against a server without the advertisement degrades guest-visibly
7
+ * (never settles partial output, never re-issues a possibly-running
8
+ * turn).
9
+ *
10
+ * - `_session/loaded_turn/query` — the client asks, right after a
11
+ * `session/load` response, whether the loaded session's founding turn
12
+ * is still running RIGHT NOW. pi-acp answers honestly from its own
13
+ * state: `running` when a turn is executing in this process, otherwise
14
+ * `completed` when the session journal's last message entry is an
15
+ * assistant message (the founding turn's final message — pi persists
16
+ * each complete LLM message atomically at `message_end`, so a
17
+ * completed turn always leaves an assistant leaf and the replay's
18
+ * trailing assistant message is authoritative), else `interrupted`
19
+ * (the journal shows an interrupted/abandoned turn — nothing is
20
+ * running, so re-issue is safe).
21
+ * - `_session/loaded_turn/ended` — pushed when a turn that the query
22
+ * classified `running` ends: its ACP stop reason for a response
23
+ * outcome, or its error. (pi-acp can only report `running` while a
24
+ * turn is live in-process, and `session/load` rejects sessions that
25
+ * are already open in-process, so in practice the notification fires
26
+ * for a turn the client loaded while it was still executing — the
27
+ * contract is complete for that case.)
28
+ */
29
+ export declare const LOADED_TURN_QUERY_METHOD: "_session/loaded_turn/query";
30
+ export declare const LOADED_TURN_ENDED_METHOD: "_session/loaded_turn/ended";
31
+ export type LoadedTurnStatus = "completed" | "running" | "interrupted";
32
+ export interface LoadedTurnQueryRequest {
33
+ sessionId: string;
34
+ }
35
+ export interface LoadedTurnQueryResponse {
36
+ status: LoadedTurnStatus;
37
+ }
38
+ export interface LoadedTurnEndedNotification {
39
+ sessionId: string;
40
+ /** The ACP stop-reason vocabulary for a turn that ended with a
41
+ * response; absent when the turn ended by failing. */
42
+ stopReason?: string;
43
+ /** The turn's error, when it ended by failing (the client then
44
+ * rejects the founding call instead of settling). */
45
+ error?: {
46
+ name: string;
47
+ message: string;
48
+ };
49
+ }
50
+ /** Runtime parser used by the ACP SDK's custom-request overload. */
51
+ export declare const loadedTurnQueryParser: {
52
+ parse(value: unknown): LoadedTurnQueryRequest;
53
+ };
54
+ //# sourceMappingURL=loaded-turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loaded-turn.d.ts","sourceRoot":"","sources":["../src/loaded-turn.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,wBAAwB,EAAG,4BAAqC,CAAC;AAC9E,eAAO,MAAM,wBAAwB,EAAG,4BAAqC,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB;2DACuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;0DACsD;IACtD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAMD,oEAAoE;AACpE,eAAO,MAAM,qBAAqB;iBACnB,OAAO,GAAG,sBAAsB;CAM9C,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { RequestError } from "@agentclientprotocol/sdk";
2
+ /**
3
+ * The `_session/loaded_turn` vendor extension — turn-TERMINAL state for
4
+ * loaded sessions (the steering-extension precedent; the REPL broker's
5
+ * re-attach arm's authoritative completion evidence). Advertised at
6
+ * initialize via `_meta.loadedTurn.supported === true`; a client seam
7
+ * against a server without the advertisement degrades guest-visibly
8
+ * (never settles partial output, never re-issues a possibly-running
9
+ * turn).
10
+ *
11
+ * - `_session/loaded_turn/query` — the client asks, right after a
12
+ * `session/load` response, whether the loaded session's founding turn
13
+ * is still running RIGHT NOW. pi-acp answers honestly from its own
14
+ * state: `running` when a turn is executing in this process, otherwise
15
+ * `completed` when the session journal's last message entry is an
16
+ * assistant message (the founding turn's final message — pi persists
17
+ * each complete LLM message atomically at `message_end`, so a
18
+ * completed turn always leaves an assistant leaf and the replay's
19
+ * trailing assistant message is authoritative), else `interrupted`
20
+ * (the journal shows an interrupted/abandoned turn — nothing is
21
+ * running, so re-issue is safe).
22
+ * - `_session/loaded_turn/ended` — pushed when a turn that the query
23
+ * classified `running` ends: its ACP stop reason for a response
24
+ * outcome, or its error. (pi-acp can only report `running` while a
25
+ * turn is live in-process, and `session/load` rejects sessions that
26
+ * are already open in-process, so in practice the notification fires
27
+ * for a turn the client loaded while it was still executing — the
28
+ * contract is complete for that case.)
29
+ */
30
+ export const LOADED_TURN_QUERY_METHOD = "_session/loaded_turn/query";
31
+ export const LOADED_TURN_ENDED_METHOD = "_session/loaded_turn/ended";
32
+ function isRecord(value) {
33
+ return typeof value === "object" && value !== null && !Array.isArray(value);
34
+ }
35
+ /** Runtime parser used by the ACP SDK's custom-request overload. */
36
+ export const loadedTurnQueryParser = {
37
+ parse(value) {
38
+ if (!isRecord(value) || typeof value.sessionId !== "string") {
39
+ throw RequestError.invalidParams(undefined, "invalid _session/loaded_turn/query request");
40
+ }
41
+ return value;
42
+ },
43
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAGxD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB;;;GAqBvD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAIxD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB;;;GAsBvD"}
package/dist/server.js CHANGED
@@ -3,6 +3,7 @@ import { agent as acpAgent, methods, ndJsonStream, } from "@agentclientprotocol/
3
3
  import { PiAcpAgent } from "./agent.js";
4
4
  import { resolveDeps } from "./deps.js";
5
5
  import { SESSION_STEERING_METHOD, steeringRequestParser } from "./steering.js";
6
+ import { LOADED_TURN_QUERY_METHOD, loadedTurnQueryParser } from "./loaded-turn.js";
6
7
  export { PiAcpAgent } from "./agent.js";
7
8
  export async function runAcp(options = {}) {
8
9
  const impl = new PiAcpAgent(await resolveDeps(options.deps));
@@ -18,6 +19,7 @@ export async function runAcp(options = {}) {
18
19
  .onRequest(methods.agent.session.setConfigOption, (context) => impl.setConfigOption(context))
19
20
  .onRequest(methods.agent.session.prompt, (context) => impl.prompt(context))
20
21
  .onRequest(SESSION_STEERING_METHOD, steeringRequestParser, (context) => impl.steer(context))
22
+ .onRequest(LOADED_TURN_QUERY_METHOD, loadedTurnQueryParser, (context) => impl.loadedTurnQuery(context))
21
23
  .onNotification(methods.agent.session.cancel, (context) => impl.cancel(context));
22
24
  const stream = options.stream ?? ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
23
25
  const connection = app.connect(stream);
package/dist/session.d.ts CHANGED
@@ -5,6 +5,7 @@ import type { PiAcpDeps } from "./deps.js";
5
5
  import type { McpBridge, McpResultProjection } from "./mcp-bridge.js";
6
6
  import { type ChildProcessRegistrySlot } from "./child-process-registry.js";
7
7
  import type { SteeringRequest, SteeringResponse } from "./steering.js";
8
+ import { type LoadedTurnStatus } from "./loaded-turn.js";
8
9
  export interface PiSessionOptions {
9
10
  sessionId: string;
10
11
  session: AgentSession;
@@ -45,8 +46,32 @@ export declare class PiSession {
45
46
  private cleanupGeneration;
46
47
  private resourceDisposePromise;
47
48
  private bridgeClosePromise;
49
+ /** The `_session/loaded_turn` extension's watch flag: set when a
50
+ * `loadedTurnStatus()` query answered `running` (a client is waiting
51
+ * for that turn's authoritative end), cleared — and the
52
+ * `_session/loaded_turn/ended` notification sent — when the turn
53
+ * finishes for any reason (response outcome with its stop reason, or
54
+ * a failure with its error). */
55
+ private loadedTurnReportedRunning;
48
56
  constructor(options: PiSessionOptions);
49
57
  get busy(): boolean;
58
+ /**
59
+ * The `_session/loaded_turn/query` answer — the authoritative
60
+ * founding-turn terminal classification for a loaded session (see
61
+ * `packages/pi-acp/src/loaded-turn.ts`):
62
+ *
63
+ * - `running` when a turn is executing in this process right now (the
64
+ * client then waits for the `_session/loaded_turn/ended` push — the
65
+ * watch flag is armed here so the turn's finish sends it),
66
+ * - `completed` when the session journal's last message entry is an
67
+ * assistant message (pi persists every complete LLM message
68
+ * atomically at `message_end`, so a completed turn always leaves an
69
+ * assistant leaf and the replay's trailing assistant message is the
70
+ * turn's FINAL message — authoritative, never a quiet-gap guess),
71
+ * - `interrupted` otherwise (the journal shows an interrupted or
72
+ * abandoned turn — no turn is running, so re-issue is safe).
73
+ */
74
+ loadedTurnStatus(): LoadedTurnStatus;
50
75
  configOptions(): import("@agentclientprotocol/sdk").SessionConfigOption[];
51
76
  publishAvailableModels(models: readonly Model<Api>[]): void;
52
77
  activeTurnSignal(): AbortSignal | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
package/dist/session.js CHANGED
@@ -9,6 +9,7 @@ import { translateEvent } from "./translate.js";
9
9
  import { agentMessages, promptUsage, terminalAssistant, usageUpdate, } from "./usage.js";
10
10
  import { installPermissionWrapper } from "./permissions.js";
11
11
  import { ChildCleanupFailure } from "./child-process-registry.js";
12
+ import { LOADED_TURN_ENDED_METHOD, } from "./loaded-turn.js";
12
13
  export class PiSession {
13
14
  sessionId;
14
15
  pi;
@@ -36,6 +37,13 @@ export class PiSession {
36
37
  cleanupGeneration;
37
38
  resourceDisposePromise;
38
39
  bridgeClosePromise;
40
+ /** The `_session/loaded_turn` extension's watch flag: set when a
41
+ * `loadedTurnStatus()` query answered `running` (a client is waiting
42
+ * for that turn's authoritative end), cleared — and the
43
+ * `_session/loaded_turn/ended` notification sent — when the turn
44
+ * finishes for any reason (response outcome with its stop reason, or
45
+ * a failure with its error). */
46
+ loadedTurnReportedRunning = false;
39
47
  constructor(options) {
40
48
  this.sessionId = options.sessionId;
41
49
  this.pi = options.session;
@@ -80,6 +88,36 @@ export class PiSession {
80
88
  get busy() {
81
89
  return this.activeTurn !== undefined || this.configReserved || this.closing;
82
90
  }
91
+ /**
92
+ * The `_session/loaded_turn/query` answer — the authoritative
93
+ * founding-turn terminal classification for a loaded session (see
94
+ * `packages/pi-acp/src/loaded-turn.ts`):
95
+ *
96
+ * - `running` when a turn is executing in this process right now (the
97
+ * client then waits for the `_session/loaded_turn/ended` push — the
98
+ * watch flag is armed here so the turn's finish sends it),
99
+ * - `completed` when the session journal's last message entry is an
100
+ * assistant message (pi persists every complete LLM message
101
+ * atomically at `message_end`, so a completed turn always leaves an
102
+ * assistant leaf and the replay's trailing assistant message is the
103
+ * turn's FINAL message — authoritative, never a quiet-gap guess),
104
+ * - `interrupted` otherwise (the journal shows an interrupted or
105
+ * abandoned turn — no turn is running, so re-issue is safe).
106
+ */
107
+ loadedTurnStatus() {
108
+ const turn = this.activeTurn;
109
+ if (turn !== undefined && !turn.completed && !this.closing) {
110
+ this.loadedTurnReportedRunning = true;
111
+ return "running";
112
+ }
113
+ const leaf = this.manager.getLeafEntry();
114
+ if (leaf !== undefined &&
115
+ leaf.type === "message" &&
116
+ leaf.message.role === "assistant") {
117
+ return "completed";
118
+ }
119
+ return "interrupted";
120
+ }
83
121
  configOptions() {
84
122
  return [thinkingLevelOption(this.pi), modelOption(this.pi, this.availableModels)];
85
123
  }
@@ -184,6 +222,26 @@ export class PiSession {
184
222
  turn.releaseBoundary = undefined;
185
223
  if (this.activeTurn === turn)
186
224
  this.activeTurn = undefined;
225
+ // The `_session/loaded_turn` extension's authoritative end push: a
226
+ // client that was told this turn was `running` at query time gets the
227
+ // ended notification now — with the turn's stop reason (response
228
+ // outcome) or its error (failure), never both. Best-effort: a
229
+ // failing notification must not break the turn's settlement. The
230
+ // push is ORDERED behind the turn's final update pump (review round
231
+ // 6): the last deltas were only enqueued (the pump delivers them
232
+ // asynchronously), and the re-attach seam settles with the
233
+ // accumulated text at the terminal marker — a marker delivered
234
+ // before the final chunk would durably settle PARTIAL text.
235
+ if (this.loadedTurnReportedRunning) {
236
+ this.loadedTurnReportedRunning = false;
237
+ const notification = "response" in outcome
238
+ ? { sessionId: this.sessionId, stopReason: outcome.response.stopReason }
239
+ : { sessionId: this.sessionId, error: normalizeTurnError(outcome.error) };
240
+ void this.drain()
241
+ .catch(() => undefined)
242
+ .then(() => this.client.notify(LOADED_TURN_ENDED_METHOD, notification))
243
+ .catch(() => undefined);
244
+ }
187
245
  const generation = this.cleanupGeneration;
188
246
  if (generation?.resumeRefreshesOnSettlement && generation.mode === "cancel-only") {
189
247
  this.cleanupGeneration = undefined;
@@ -743,3 +801,18 @@ export class PiSession {
743
801
  });
744
802
  }
745
803
  }
804
+ /** Normalize a turn's failure into the loaded-turn ended notification's
805
+ * `{ name, message }` error shape (best-effort — the notification
806
+ * carries the turn's error verbatim-ish, never the whole stack). */
807
+ function normalizeTurnError(error) {
808
+ if (error !== null && typeof error === "object") {
809
+ const candidate = error;
810
+ return {
811
+ name: typeof candidate.name === "string" ? candidate.name : "Error",
812
+ message: typeof candidate.message === "string"
813
+ ? candidate.message
814
+ : String(candidate.message ?? error),
815
+ };
816
+ }
817
+ return { name: "Error", message: String(error) };
818
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automatalabs/pi-acp",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": ">=22.19.0"
@@ -31,13 +31,13 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@agentclientprotocol/sdk": "1.3.0",
34
- "@earendil-works/pi-ai": "0.84.0",
35
- "@earendil-works/pi-coding-agent": "0.84.0",
34
+ "@earendil-works/pi-ai": "0.84.1",
35
+ "@earendil-works/pi-coding-agent": "0.84.1",
36
36
  "@modelcontextprotocol/sdk": "1.29.0",
37
37
  "typebox": "1.3.2"
38
38
  },
39
39
  "devDependencies": {
40
- "@earendil-works/pi-agent-core": "0.84.0"
40
+ "@earendil-works/pi-agent-core": "0.84.1"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsc -b",