@hachej/boring-agent 0.1.92 → 0.1.94

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.
@@ -286,6 +286,83 @@ function gatewayConformance(options) {
286
286
  expect(await connection.stop({ requestId: "stop" })).toEqual(stop);
287
287
  await connection.close();
288
288
  });
289
+ it("keeps strict-idle busy admission retryable and idempotent", async () => {
290
+ const fixture = await options.createFixture();
291
+ const scope = fixture.issueScope();
292
+ const ref = await createSession(fixture, scope, "strict-idle-retry");
293
+ const connection = await fixture.gateway.connectSession({ scope, ref });
294
+ const input = {
295
+ kind: "prompt",
296
+ requestId: "strict-review",
297
+ clientNonce: "strict-review",
298
+ content: "model-facing review",
299
+ displayContent: "Visible review",
300
+ requireIdle: true
301
+ };
302
+ fixture.setActivity(ref, "running");
303
+ await expectCode(connection.send(input), AgentGatewayErrorCode.AGENT_COMMAND_INVALID_STATE);
304
+ fixture.setActivity(ref, "idle");
305
+ const accepted = await connection.send(input);
306
+ expect(accepted).toMatchObject({ accepted: true, disposition: "prompt" });
307
+ await expect(connection.send(input)).resolves.toMatchObject({ ...accepted, duplicate: true });
308
+ await expectCode(
309
+ connection.send({ ...input, displayContent: "changed payload" }),
310
+ AgentGatewayErrorCode.AGENT_REQUEST_CONFLICT
311
+ );
312
+ await connection.close();
313
+ });
314
+ it("admits exactly one of two concurrent strict-idle prompts", async () => {
315
+ const fixture = await options.createFixture();
316
+ const scope = fixture.issueScope();
317
+ const ref = await createSession(fixture, scope, "strict-idle-race");
318
+ const connection = await fixture.gateway.connectSession({ scope, ref });
319
+ fixture.setActivity(ref, "idle");
320
+ const outcome = async (requestId) => {
321
+ try {
322
+ await connection.send({
323
+ kind: "prompt",
324
+ requestId,
325
+ clientNonce: requestId,
326
+ content: requestId,
327
+ requireIdle: true
328
+ });
329
+ return "accepted";
330
+ } catch (error) {
331
+ return error.code;
332
+ }
333
+ };
334
+ const outcomes = await Promise.all([outcome("strict-a"), outcome("strict-b")]);
335
+ expect(outcomes.sort()).toEqual([
336
+ AgentGatewayErrorCode.AGENT_COMMAND_INVALID_STATE,
337
+ "accepted"
338
+ ].sort());
339
+ await connection.close();
340
+ });
341
+ it("keeps ordinary error recovery while strict-idle prompts reject error state", async () => {
342
+ const fixture = await options.createFixture();
343
+ const scope = fixture.issueScope();
344
+ const strictRef = await createSession(fixture, scope, "strict-error");
345
+ const strictConnection = await fixture.gateway.connectSession({ scope, ref: strictRef });
346
+ fixture.setActivity(strictRef, "error");
347
+ await expectCode(strictConnection.send({
348
+ kind: "prompt",
349
+ requestId: "strict-error-prompt",
350
+ clientNonce: "strict-error-prompt",
351
+ content: "strict",
352
+ requireIdle: true
353
+ }), AgentGatewayErrorCode.AGENT_COMMAND_INVALID_STATE);
354
+ await strictConnection.close();
355
+ const recoveryRef = await createSession(fixture, scope, "ordinary-error");
356
+ const recoveryConnection = await fixture.gateway.connectSession({ scope, ref: recoveryRef });
357
+ fixture.setActivity(recoveryRef, "error");
358
+ await expect(recoveryConnection.send({
359
+ kind: "prompt",
360
+ requestId: "ordinary-recovery",
361
+ clientNonce: "ordinary-recovery",
362
+ content: "recover"
363
+ })).resolves.toMatchObject({ disposition: "prompt" });
364
+ await recoveryConnection.close();
365
+ });
289
366
  it("covers the complete command-state table and queue selector semantics", async () => {
290
367
  const fixture = await options.createFixture();
291
368
  const scope = fixture.issueScope();
@@ -1,18 +1,18 @@
1
- import { P as ProvisionWorkspaceRuntimeOptions, W as WorkspaceProvisioningResult, R as RuntimeModeAdapter, a as RuntimeModeId, B as BuiltinRuntimeModeId, b as AgentRuntimeHostOperations, c as RuntimeBashStrategy, d as RuntimeFilesystemStrategy, M as ModeContext, e as WorkspaceProvisioningAdapter, f as RuntimeBundle, g as PiHarnessOptions, h as AgentMeteringSink, i as RuntimeFilesystemBinding, j as MeteringErrorLogger, k as ReadyStatusTracker, l as ResolvedAgentRuntimeScope, C as CreateAgentHostOptions, m as CompiledAgentHostAgentSpec, n as AgentRequestLedger, o as AgentEffectAdmission, p as AgentRequestKey, q as CreatedAgentHost, r as AgentFleetCompiler, A as AgentGatewayEffect, s as AgentRequestTarget, t as AgentRequestLedgerRecord, u as AgentRequestFailure, v as AgentHostLegacyRoutePolicy } from '../types-uQozUhGt.js';
2
- export { w as AgentHostAddressedHttpProjectionOptions, x as AgentHostAgentSpec, y as AgentHostDescription, z as AgentHostHandle, D as AgentHostHttpProjectionOptions, E as AgentHostLegacyHttpProjectionOptions, F as AgentHostLegacyProjectionComposition, G as AgentHostLegacyProjectionLifecycle, H as AgentHostLegacyProjectionRuntime, I as AgentHostLegacyRoutePolicyMountInput, J as ConfiguredAgentHostAgentSpec, L as LegacyDefaultAgentHostSpec, K as MeteringReleaseInput, N as MeteringReleaseReason, O as MeteringReservationResult, Q as MeteringReserveInput, S as MeteringRunKind, T as MeteringRunScope, U as MeteringRunStatus, V as MeteringSettleInput, X as MeteringUsage, Y as MeteringUsageInput, Z as PI_PACKAGE_RESOURCE_FILTERS, _ as PiExtensionFactory, $ as PiPackageSource, a0 as PluginSkillSource, a1 as ResolvedEnvironmentScope, a2 as RuntimeFilesystemBindingOperations, a3 as RuntimeNodePackageSpec, a4 as RuntimeProvisioningContribution, a5 as RuntimePythonSpec, a6 as RuntimeTemplateContribution, a7 as WorkspaceProvisioningExecResult, a8 as compactPiPackages, a9 as createResourceSettingsManager, aa as mergePiPackageSources, ab as normalizeMeteringUsage, ac as piPackageSourceKey } from '../types-uQozUhGt.js';
1
+ import { P as ProvisionWorkspaceRuntimeOptions, W as WorkspaceProvisioningResult, R as RuntimeModeAdapter, a as RuntimeModeId, B as BuiltinRuntimeModeId, b as AgentRuntimeHostOperations, c as RuntimeBashStrategy, d as RuntimeFilesystemStrategy, M as ModeContext, e as WorkspaceProvisioningAdapter, f as RuntimeBundle, g as PiHarnessOptions, h as AgentMeteringSink, i as RuntimeFilesystemBinding, j as MeteringErrorLogger, k as ReadyStatusTracker, l as ResolvedAgentRuntimeScope, C as CreateAgentHostOptions, m as CompiledAgentHostAgentSpec, n as AgentRequestLedger, o as AgentEffectAdmission, p as AgentRequestKey, q as CreatedAgentHost, r as AgentFleetCompiler, A as AgentGatewayEffect, s as AgentRequestTarget, t as AgentRequestLedgerRecord, u as AgentRequestFailure, v as AgentHostLegacyRoutePolicy } from '../types-C-Eroi__.js';
2
+ export { w as AgentHostAddressedHttpProjectionOptions, x as AgentHostAgentSpec, y as AgentHostDescription, z as AgentHostHandle, D as AgentHostHttpProjectionOptions, E as AgentHostLegacyHttpProjectionOptions, F as AgentHostLegacyProjectionComposition, G as AgentHostLegacyProjectionLifecycle, H as AgentHostLegacyProjectionRuntime, I as AgentHostLegacyRoutePolicyMountInput, J as ConfiguredAgentHostAgentSpec, L as LegacyDefaultAgentHostSpec, K as MeteringReleaseInput, N as MeteringReleaseReason, O as MeteringReservationResult, Q as MeteringReserveInput, S as MeteringRunKind, T as MeteringRunScope, U as MeteringRunStatus, V as MeteringSettleInput, X as MeteringUsage, Y as MeteringUsageInput, Z as PI_PACKAGE_RESOURCE_FILTERS, _ as PiExtensionFactory, $ as PiPackageSource, a0 as PluginSkillSource, a1 as ResolvedEnvironmentScope, a2 as RuntimeFilesystemBindingOperations, a3 as RuntimeNodePackageSpec, a4 as RuntimeProvisioningContribution, a5 as RuntimePythonSpec, a6 as RuntimeTemplateContribution, a7 as WorkspaceProvisioningExecResult, a8 as compactPiPackages, a9 as createResourceSettingsManager, aa as mergePiPackageSources, ab as normalizeMeteringUsage, ac as piPackageSourceKey } from '../types-C-Eroi__.js';
3
3
  import { E as ExecOptions, a as ExecResult, W as WorkspaceChangeEvent, b as Workspace, S as Sandbox } from '../sandbox-DAD8cWQR.js';
4
4
  import { R as RemoteWorkerWorkspaceOp, a as RemoteWorkerWorkspaceResult, b as RemoteWorkerExecRequest } from '../middleware-CzW8n7B7.js';
5
5
  export { c as REMOTE_WORKER_PROVIDER, d as REMOTE_WORKER_RUNTIME_CWD, e as RemoteWorkerErrorPayload, f as RemoteWorkerExecResponse, g as RemoteWorkerFsEventEnvelope, W as WORKER_INTERNAL_TOKEN_HEADER, h as WORKER_REQUEST_ID_HEADER, i as WORKER_WORKSPACE_ID_HEADER, j as createAgentAuthMiddleware } from '../middleware-CzW8n7B7.js';
6
6
  import { SandboxProviderV1, WorkspaceSandboxPairV1 } from '@hachej/boring-sandbox/shared';
7
- import { S as SandboxHandleStore, C as CompiledAgentBundle, a as Sha256Digest, A as AgentDeployment, b as ShareEntryStore, P as PluginRestartWarning, W as WorkspaceAgentDispatcherContext, c as WorkspaceAgentDispatcher, d as ProviderId, e as CredentialFieldId, R as ResolvedCredentialMaterialV1, f as AuthorizedWorkspaceCredentialScopeV1, V as VerifiedWorkspaceCredentialAuthorityV1, g as WorkspaceCredentialAuthorityVerifierV1, h as CredentialConsumerBindingRegistryV1, i as ProviderRegistryV1, j as WorkspaceCredentialResolverV1, k as ProviderCredentialRefV1, l as ResolvedCredentialLeaseV1 } from '../lease-BqeCLGPL.js';
7
+ import { S as SandboxHandleStore, C as CompiledAgentBundle, a as Sha256Digest, A as AgentDeployment, b as ShareEntryStore, P as PluginRestartWarning, W as WorkspaceAgentDispatcherContext, c as WorkspaceAgentDispatcher, d as ProviderId, e as CredentialFieldId, R as ResolvedCredentialMaterialV1, f as AuthorizedWorkspaceCredentialScopeV1, V as VerifiedWorkspaceCredentialAuthorityV1, g as WorkspaceCredentialAuthorityVerifierV1, h as CredentialConsumerBindingRegistryV1, i as ProviderRegistryV1, j as WorkspaceCredentialResolverV1, k as ProviderCredentialRefV1, l as ResolvedCredentialLeaseV1 } from '../lease-CusB-uLg.js';
8
8
  import { s as AgentConfig, b as Agent, d as AgentActor, j as AgentEvent, t as AgentTool, u as AgentHarnessFactory, T as TelemetrySink, A as AgentHarness } from '../harness-Cc48o6uS.js';
9
9
  export { v as AgentHarnessFactoryInput } from '../harness-Cc48o6uS.js';
10
10
  import { b as PiChatEvent, E as ErrorCode, l as SessionCtx, S as SessionStore, m as SessionListOptions, k as SessionSummary, n as PiChatSnapshot, c as PromptPayload, d as PromptReceipt, F as FollowUpPayload, e as FollowUpReceipt, f as QueueClearPayload, g as QueueClearReceipt, I as InterruptPayload, i as StopPayload, j as StopReceipt } from '../piChatEvent-BsMp3MSo.js';
11
- import { A as AgentGateway, a as AuthorizedAgentScope, b as AgentSessionRef, c as AgentSessionActivity, V as VerifiedAgentScopeClaim, d as AgentSessionSummary, e as AgentSessionStateSnapshot, f as AgentSessionConnection, J as JsonValue, g as AgentGatewayErrorDTO } from '../errors-C38f6pol.js';
11
+ import { A as AgentGateway, a as AuthorizedAgentScope, b as AgentSessionRef, c as AgentSessionActivity, V as VerifiedAgentScopeClaim, d as AgentSessionSummary, e as AgentSessionStateSnapshot, f as AgentSessionConnection, J as JsonValue, g as AgentGatewayErrorDTO } from '../errors-G6T6S7PQ.js';
12
12
  import { IncomingMessage, ServerResponse } from 'node:http';
13
13
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
14
14
  import { FastifyRequest, FastifyInstance, FastifyPluginAsync } from 'fastify';
15
- import { i as PiSessionRequestContext, g as PiChatSessionService, h as PiSessionCreateInit, e as PiChatEventSubscriber, P as PiChatEventStreamResult, a as AgentEffectAdmission$1 } from '../piChatSessionService-BrPqKrpY.js';
15
+ import { i as PiSessionRequestContext, g as PiChatSessionService, h as PiSessionCreateInit, e as PiChatEventSubscriber, P as PiChatEventStreamResult, a as AgentEffectAdmission$1 } from '../piChatSessionService-C0KbcGw9.js';
16
16
  import { BoringAgentRuntimePaths } from '@hachej/boring-sandbox/providers/node-workspace';
17
17
  import '@hachej/boring-bash/agent';
18
18
  import '@mariozechner/pi-coding-agent';
@@ -558,9 +558,39 @@ interface RuntimeEnvContribution {
558
558
  interface WorkspaceAgentDispatcherResolveOptions {
559
559
  request?: FastifyRequest;
560
560
  }
561
+ interface PiSessionVisibleUserTurnTarget {
562
+ /** Advisory snapshot; sendIfIdle is the authoritative atomic admission. */
563
+ isIdle(): Promise<boolean>;
564
+ sendIfIdle(input: {
565
+ /** Stable across busy/transient retries; also used as the Pi client nonce. */
566
+ requestId: string;
567
+ message: string;
568
+ displayMessage?: string;
569
+ }): Promise<{
570
+ status: 'accepted';
571
+ cursor: number;
572
+ duplicate?: boolean;
573
+ } | {
574
+ status: 'busy';
575
+ } | {
576
+ status: 'gone';
577
+ }>;
578
+ }
579
+ interface BoundPiSession {
580
+ /** Trusted target closed over one exact logical Pi session. */
581
+ visibleUserMessageTarget: PiSessionVisibleUserTurnTarget;
582
+ }
561
583
  interface WorkspaceAgentDispatcherBinding {
562
584
  dispatcher: WorkspaceAgentDispatcher;
563
585
  workspace: Workspace;
586
+ /**
587
+ * Trusted host seam used by local integrations that must bind durable work
588
+ * to the exact logical Pi session before accepting it.
589
+ */
590
+ bindPiSession?(sessionId: string, sessionCtx?: {
591
+ workspaceId?: string;
592
+ userId?: string;
593
+ }): Promise<BoundPiSession>;
564
594
  }
565
595
  interface WorkspaceAgentDispatcherResolver {
566
596
  resolve(ctx: WorkspaceAgentDispatcherContext, options?: WorkspaceAgentDispatcherResolveOptions): Promise<WorkspaceAgentDispatcher>;
@@ -759,6 +789,12 @@ declare class HarnessPiChatService implements PiChatSessionService {
759
789
  private createChannelOnce;
760
790
  private buildChannel;
761
791
  private readDurableLatestPiChatSeq;
792
+ /**
793
+ * Validate the persisted session identity and instantiate its lazily-created
794
+ * Pi adapter under the same full cache key. This is intentionally a narrow
795
+ * trusted-host seam rather than another browser route.
796
+ */
797
+ ensurePiSessionBound(ctx: PiSessionRequestContext, sessionId: string): Promise<void>;
762
798
  private assertCanAccessSession;
763
799
  private sessionKey;
764
800
  }
@@ -861,6 +897,7 @@ interface CreateValidatingAgentFleetCompilerOptions {
861
897
  */
862
898
  declare function createValidatingAgentFleetCompiler(options: CreateValidatingAgentFleetCompilerOptions): AgentFleetCompiler;
863
899
 
900
+ type RetryableSessionGuard = () => Promise<AgentGatewayErrorDTO | undefined>;
864
901
  declare class EmbeddedAgentGateway implements AgentGateway {
865
902
  private readonly runtime;
866
903
  private readonly cursorSecret;
@@ -912,10 +949,12 @@ declare class EmbeddedAgentGateway implements AgentGateway {
912
949
  readonly requestId: string;
913
950
  readonly payload: JsonValue;
914
951
  readonly action: () => Promise<unknown>;
952
+ readonly retryableGuard?: RetryableSessionGuard;
915
953
  }): Promise<unknown>;
916
954
  private effect;
917
955
  private replayReceipt;
918
956
  private failure;
957
+ private promptAdmission;
919
958
  private withWriter;
920
959
  private encodeCursor;
921
960
  private decodeCursor;
@@ -62,15 +62,15 @@ import {
62
62
  sandboxRuntimeHostOperations,
63
63
  shareResourceUri,
64
64
  withResolvedCredential
65
- } from "../chunk-V3ZD3T3G.js";
65
+ } from "../chunk-N4AATPH7.js";
66
66
  import {
67
67
  PI_PACKAGE_RESOURCE_FILTERS,
68
68
  compactPiPackages,
69
69
  createResourceSettingsManager,
70
70
  mergePiPackageSources,
71
71
  piPackageSourceKey
72
- } from "../chunk-IWBC6SF7.js";
73
- import "../chunk-CJ2BW2W2.js";
72
+ } from "../chunk-2TMS2RU2.js";
73
+ import "../chunk-EOMY2HDT.js";
74
74
  import "../chunk-NITBKFG6.js";
75
75
  import "../chunk-AQBXNPMD.js";
76
76
  import "../chunk-WSQ5QNIY.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  WORKER_INTERNAL_TOKEN_HEADER,
3
3
  constantTimeTokenEqual
4
- } from "../../chunk-V3ZD3T3G.js";
5
- import "../../chunk-IWBC6SF7.js";
6
- import "../../chunk-CJ2BW2W2.js";
4
+ } from "../../chunk-N4AATPH7.js";
5
+ import "../../chunk-2TMS2RU2.js";
6
+ import "../../chunk-EOMY2HDT.js";
7
7
  import "../../chunk-NITBKFG6.js";
8
8
  import "../../chunk-AQBXNPMD.js";
9
9
  import "../../chunk-WSQ5QNIY.js";
@@ -1,13 +1,13 @@
1
1
  import { t as AgentTool } from '../harness-Cc48o6uS.js';
2
2
  export { c as AGENT_NOT_IMPLEMENTED_UNTIL_T1, b as Agent, d as AgentActor, e as AgentCoreHarness, f as AgentCoreHarnessFactory, g as AgentCorePromptInput, h as AgentCoreSessionAdapter, i as AgentCoreSessionSnapshot, j as AgentEvent, A as AgentHarness, k as AgentMessageContent, l as AgentMessagePart, m as AgentNotImplementedError, a as AgentReadiness, n as AgentReadinessStatus, o as AgentResolveInputResponse, x as AgentRuntimeAdapter, p as AgentSendInput, q as AgentStartReceipt, r as AgentStreamOptions, s as CoreAgentConfig, J as JSONSchema, M as MessageAttachment, R as RunContext, S as SendMessageInput, y as TelemetryEvent, T as TelemetrySink, z as ToolExecContext, B as ToolResult, C as noopTelemetry, D as safeCapture, E as sessionStreamPath } from '../harness-Cc48o6uS.js';
3
- import { m as SchemaValidationError, n as AgentSchemaIssue, a as Sha256Digest, o as AgentSchemaValidationResult, p as CredentialConsumerBindingId, e as CredentialFieldId, k as ProviderCredentialRefV1, f as AuthorizedWorkspaceCredentialScopeV1 } from '../lease-BqeCLGPL.js';
4
- export { q as AgentDefinition, r as AgentDefinitionDigestAsset, s as AgentDefinitionReference, t as AgentDefinitionValidationError, A as AgentDeployment, u as AgentDeploymentValidationError, v as CommandNotifyPayload, C as CompiledAgentBundle, w as CompiledAgentDefinition, x as CreateShareEntryInput, h as CredentialConsumerBindingRegistryV1, y as CredentialConsumerBindingV1, z as CredentialConsumerKindV1, B as CredentialDeliveryV1, D as CredentialFieldDefinitionV1, E as ExternalManagedAccountReferenceDefinitionV1, I as InMemoryShareEntryStore, O as OpaqueRefSchema, F as OpaqueShareLocatorIdSchema, G as PROVIDER_CREDENTIAL_REF_VERSION, H as ProviderCategoryV1, J as ProviderCredentialDefinitionV1, K as ProviderCredentialRefFactoryV1, L as ProviderDefinitionV1, d as ProviderId, i as ProviderRegistryV1, l as ResolvedCredentialLeaseV1, R as ResolvedCredentialMaterialV1, M as SandboxHandleRecord, S as SandboxHandleStore, N as Sha256DigestSchema, Q as ShareEntryErrorCode, T as ShareEntryProvenance, U as ShareEntryProvenanceSchema, X as ShareEntryResolution, b as ShareEntryStore, Y as ShareEntryTombstone, Z as ShareEntryV1, _ as ShareEntryV1Schema, $ as ShareEntryValidationError, V as VerifiedWorkspaceCredentialAuthorityV1, a0 as VerifiedWorkspaceCredentialPrincipalV1, a1 as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, a2 as WORKSPACE_COMMAND_NOTIFY_EVENT, a3 as WorkspaceAgentDispatch, c as WorkspaceAgentDispatcher, W as WorkspaceAgentDispatcherContext, a4 as WorkspaceAgentDispatcherDispatchInput, a5 as WorkspaceAgentDispatcherSendInput, a6 as WorkspaceAgentGatewayBinding, g as WorkspaceCredentialAuthorityVerifierV1, j as WorkspaceCredentialResolverV1, a7 as createAgentAssetDigest, a8 as createAgentDefinitionDigest, a9 as createAgentDeploymentDigest, aa as createCredentialConsumerBindingRegistryV1, ab as createProviderCredentialRefFactoryV1, ac as createProviderRegistryV1, ad as resolveShareEntry, ae as validateAgentDefinition, af as validateAgentDeployment } from '../lease-BqeCLGPL.js';
3
+ import { m as SchemaValidationError, n as AgentSchemaIssue, a as Sha256Digest, o as AgentSchemaValidationResult, p as CredentialConsumerBindingId, e as CredentialFieldId, k as ProviderCredentialRefV1, f as AuthorizedWorkspaceCredentialScopeV1 } from '../lease-CusB-uLg.js';
4
+ export { q as AgentDefinition, r as AgentDefinitionDigestAsset, s as AgentDefinitionReference, t as AgentDefinitionValidationError, A as AgentDeployment, u as AgentDeploymentValidationError, v as CommandNotifyPayload, C as CompiledAgentBundle, w as CompiledAgentDefinition, x as CreateShareEntryInput, h as CredentialConsumerBindingRegistryV1, y as CredentialConsumerBindingV1, z as CredentialConsumerKindV1, B as CredentialDeliveryV1, D as CredentialFieldDefinitionV1, E as ExternalManagedAccountReferenceDefinitionV1, I as InMemoryShareEntryStore, O as OpaqueRefSchema, F as OpaqueShareLocatorIdSchema, G as PROVIDER_CREDENTIAL_REF_VERSION, H as ProviderCategoryV1, J as ProviderCredentialDefinitionV1, K as ProviderCredentialRefFactoryV1, L as ProviderDefinitionV1, d as ProviderId, i as ProviderRegistryV1, l as ResolvedCredentialLeaseV1, R as ResolvedCredentialMaterialV1, M as SandboxHandleRecord, S as SandboxHandleStore, N as Sha256DigestSchema, Q as ShareEntryErrorCode, T as ShareEntryProvenance, U as ShareEntryProvenanceSchema, X as ShareEntryResolution, b as ShareEntryStore, Y as ShareEntryTombstone, Z as ShareEntryV1, _ as ShareEntryV1Schema, $ as ShareEntryValidationError, V as VerifiedWorkspaceCredentialAuthorityV1, a0 as VerifiedWorkspaceCredentialPrincipalV1, a1 as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, a2 as WORKSPACE_COMMAND_NOTIFY_EVENT, a3 as WorkspaceAgentDispatch, c as WorkspaceAgentDispatcher, W as WorkspaceAgentDispatcherContext, a4 as WorkspaceAgentDispatcherDispatchInput, a5 as WorkspaceAgentDispatcherSendInput, a6 as WorkspaceAgentGatewayBinding, g as WorkspaceCredentialAuthorityVerifierV1, j as WorkspaceCredentialResolverV1, a7 as createAgentAssetDigest, a8 as createAgentDefinitionDigest, a9 as createAgentDeploymentDigest, aa as createCredentialConsumerBindingRegistryV1, ab as createProviderCredentialRefFactoryV1, ac as createProviderRegistryV1, ad as resolveShareEntry, ae as validateAgentDefinition, af as validateAgentDeployment } from '../lease-CusB-uLg.js';
5
5
  import { b as Workspace, S as Sandbox } from '../sandbox-DAD8cWQR.js';
6
6
  export { e as Entry, E as ExecOptions, a as ExecResult, I as IsolatedCodeInput, f as IsolatedCodeOutput, g as SandboxCapability, d as Stat, c as WorkspaceRuntimeContext, h as WorkspaceWatchControlEvent } from '../sandbox-DAD8cWQR.js';
7
7
  import { B as BoringChatPart, T as ToolUiMetadata, A as AgentConsumptionErrorCode } from '../piChatEvent-BsMp3MSo.js';
8
8
  export { p as AgentDefinitionErrorCode, q as AgentDeploymentErrorCode, r as ApiErrorPayload, s as ApiErrorPayloadSchema, t as ApiErrorResponse, u as ApiErrorResponseSchema, a as BoringChatMessage, v as ChatAttachmentPayload, C as ChatError, o as ChatModelSelection, w as ChatSubmitPayload, h as CommandReceipt, x as ERROR_CODES, E as ErrorCode, y as ErrorLogFields, z as ErrorLogFieldsSchema, F as FollowUpPayload, e as FollowUpReceipt, I as InterruptPayload, D as InterruptReceipt, b as PiChatEvent, G as PiChatHeartbeatFrame, n as PiChatSnapshot, P as PiChatStatus, H as PiChatStreamFrame, c as PromptPayload, d as PromptReceipt, f as QueueClearPayload, g as QueueClearReceipt, Q as QueuedUserMessage, l as SessionCtx, J as SessionDetail, S as SessionStore, k as SessionSummary, i as StopPayload, j as StopReceipt, K as ThinkingLevel, L as extractToolUiMetadata, M as isToolUiMetadata } from '../piChatEvent-BsMp3MSo.js';
9
- import { F as FileSearch } from '../errors-C38f6pol.js';
10
- export { i as AGENT_GATEWAY_ERROR_CODES, j as AgentFollowUpCommand, A as AgentGateway, k as AgentGatewayError, l as AgentGatewayErrorCode, g as AgentGatewayErrorDTO, m as AgentPromptCommand, h as AgentScopeVerifier, n as AgentSendReceipt, c as AgentSessionActivity, f as AgentSessionConnection, o as AgentSessionEvent, p as AgentSessionPage, b as AgentSessionRef, e as AgentSessionStateSnapshot, d as AgentSessionSummary, q as AgentSummary, r as AuthSubjectId, a as AuthorizedAgentScope, s as AuthorizedAgentSessionQuery, C as ConnectAgentSessionInput, t as CreateAgentSessionInput, D as DeleteAgentSessionInput, I as IdempotentAgentControl, u as IdempotentAgentSend, v as IdempotentQueueClear, w as JsonPrimitive, x as JsonSafe, J as JsonValue, L as ListAgentsInput, R as ReadAgentSessionStateInput, y as RenameAgentSessionInput, V as VerifiedAgentScopeClaim, W as WorkspaceScopeId } from '../errors-C38f6pol.js';
9
+ import { F as FileSearch } from '../errors-G6T6S7PQ.js';
10
+ export { i as AGENT_GATEWAY_ERROR_CODES, j as AgentFollowUpCommand, A as AgentGateway, k as AgentGatewayError, l as AgentGatewayErrorCode, g as AgentGatewayErrorDTO, m as AgentPromptCommand, h as AgentScopeVerifier, n as AgentSendReceipt, c as AgentSessionActivity, f as AgentSessionConnection, o as AgentSessionEvent, p as AgentSessionPage, b as AgentSessionRef, e as AgentSessionStateSnapshot, d as AgentSessionSummary, q as AgentSummary, r as AuthSubjectId, a as AuthorizedAgentScope, s as AuthorizedAgentSessionQuery, C as ConnectAgentSessionInput, t as CreateAgentSessionInput, D as DeleteAgentSessionInput, I as IdempotentAgentControl, u as IdempotentAgentSend, v as IdempotentQueueClear, w as JsonPrimitive, x as JsonSafe, J as JsonValue, L as ListAgentsInput, R as ReadAgentSessionStateInput, y as RenameAgentSessionInput, V as VerifiedAgentScopeClaim, W as WorkspaceScopeId } from '../errors-G6T6S7PQ.js';
11
11
  import { z } from 'zod';
12
12
  import '@mariozechner/pi-coding-agent';
13
13
 
@@ -1,13 +1,13 @@
1
1
  import * as fastify from 'fastify';
2
2
  import { FastifyRequest, FastifyPluginAsync } from 'fastify';
3
3
  import { w as ToolReadinessRequirement, T as TelemetrySink, b as Agent, A as AgentHarness, t as AgentTool, u as AgentHarnessFactory } from './harness-Cc48o6uS.js';
4
- import { F as FileSearch, b as AgentSessionRef, V as VerifiedAgentScopeClaim, g as AgentGatewayErrorDTO, J as JsonValue, a as AuthorizedAgentScope, A as AgentGateway, h as AgentScopeVerifier } from './errors-C38f6pol.js';
4
+ import { F as FileSearch, b as AgentSessionRef, V as VerifiedAgentScopeClaim, g as AgentGatewayErrorDTO, J as JsonValue, a as AuthorizedAgentScope, A as AgentGateway, h as AgentScopeVerifier } from './errors-G6T6S7PQ.js';
5
5
  import { o as ChatModelSelection } from './piChatEvent-BsMp3MSo.js';
6
6
  import { b as Workspace, c as WorkspaceRuntimeContext, S as Sandbox } from './sandbox-DAD8cWQR.js';
7
7
  import { RuntimeHostOperations } from '@hachej/boring-bash/agent';
8
8
  import { CreateNodeWorkspaceOptions, BoringAgentRuntimePaths } from '@hachej/boring-sandbox/providers/node-workspace';
9
9
  import { PackageSource, ExtensionFactory, SettingsManager } from '@mariozechner/pi-coding-agent';
10
- import { A as AgentCoreSessionService, g as PiChatSessionService } from './piChatSessionService-BrPqKrpY.js';
10
+ import { A as AgentCoreSessionService, g as PiChatSessionService } from './piChatSessionService-C0KbcGw9.js';
11
11
 
12
12
  /**
13
13
  * Generic metering seam for hosts that bill native Pi usage.
@@ -0,0 +1,152 @@
1
+ # AgentGateway v0 — the session contract
2
+
3
+ **Kind:** technical contract. **Governs:** the gateway surface only.
4
+ **Does not govern:** sequencing (see `docs/DIRECTION.md`), architecture rulings
5
+ (see `docs/DECISIONS.md`), or history (see `docs/issues/909/plan.md`).
6
+
7
+ This file supersedes `docs/issues/909/plan.md` §6 as the binding description of
8
+ the gateway contract. §6 was written before implementation and drifted; it is
9
+ retained there as a historical record, not as a build target.
10
+
11
+ Source of truth is the code: `packages/agent/src/shared/gateway/types.ts` and
12
+ `errors.ts`. If this file and those files disagree, the code wins and this file
13
+ is a bug. Keep them together — that colocation is the point. The v0 contract
14
+ lived in an issue folder nobody updated, and it silently stopped matching the
15
+ types within one release.
16
+
17
+ ## Scope discipline
18
+
19
+ `AuthorizedAgentScope` is an **issuer-owned runtime capability, not a transport
20
+ DTO**. It carries a `unique symbol` brand, so it cannot be forged by spreading
21
+ an object across a boundary. It must be re-checked against issuer identity and
22
+ current membership on every use — possession is not authorization.
23
+
24
+ ```ts
25
+ interface AuthorizedAgentScope {
26
+ readonly workspaceScopeId: WorkspaceScopeId
27
+ readonly authSubjectId: AuthSubjectId
28
+ readonly [authorizedAgentScope]: true // brand
29
+ }
30
+ ```
31
+
32
+ Only the app-owned verifier returns identity facts, as
33
+ `VerifiedAgentScopeClaim`. `AgentSessionRef` deliberately carries **no
34
+ `hostId`** — session refs are `{ agentTypeId, sessionId }` and nothing else.
35
+
36
+ ## The 7 methods (+ close)
37
+
38
+ ```ts
39
+ interface AgentGateway {
40
+ listAgents(input: ListAgentsInput): Promise<readonly AgentSummary[]>
41
+ listSessions(input: AuthorizedAgentSessionQuery): Promise<AgentSessionPage>
42
+ createSession(input: CreateAgentSessionInput): Promise<AgentSessionRef>
43
+ connectSession(input: ConnectAgentSessionInput): Promise<AgentSessionConnection>
44
+ readSessionState(input: ReadAgentSessionStateInput): Promise<AgentSessionStateSnapshot>
45
+ renameSession(input: RenameAgentSessionInput): Promise<AgentSessionSummary>
46
+ deleteSession(input: DeleteAgentSessionInput): Promise<void>
47
+ close(): Promise<void>
48
+ }
49
+ ```
50
+
51
+ ### Input DTOs — scope is NESTED
52
+
53
+ **This is the correction.** §6 froze the scope fields as inherited
54
+ (`interface CreateAgentSessionInput extends AuthorizedAgentScope`). The shipped
55
+ contract nests scope under its own field on **every** input:
56
+
57
+ ```ts
58
+ interface ListAgentsInput { scope }
59
+ interface AuthorizedAgentSessionQuery{ scope; agentTypeId?; cursor?; limit? }
60
+ interface CreateAgentSessionInput { scope; agentTypeId; requestId; title? }
61
+ interface ConnectAgentSessionInput { scope; ref; cursor? }
62
+ interface ReadAgentSessionStateInput { scope; ref }
63
+ interface RenameAgentSessionInput { scope; ref; requestId; title }
64
+ interface DeleteAgentSessionInput { scope; ref; requestId }
65
+ ```
66
+
67
+ Nesting keeps the branded capability a single indivisible value instead of
68
+ scattering its fields across every DTO, which is why callers adopted it. Code
69
+ written against §6's inherited form does not typecheck.
70
+
71
+ Note `listAgents` takes `ListAgentsInput`, not a bare `AuthorizedAgentScope`.
72
+
73
+ ### Output DTOs
74
+
75
+ `AgentSummary` (`agentTypeId`, `label`, `description?`, `definition?{version,digest}`),
76
+ `AgentSessionSummary` (`ref`, `title`, `status`, `createdAt`, `updatedAt`),
77
+ `AgentSessionPage` (`sessions`, `nextCursor?`), and the receipts
78
+ (`CommandReceipt`, `AgentSendReceipt`, `QueueClearReceipt`, `StopReceipt`) are
79
+ unchanged from §6, field for field.
80
+
81
+ Session activity is the extracted alias `AgentSessionActivity`:
82
+ `'idle' | 'running' | 'aborting' | 'error'`.
83
+
84
+ `AgentSessionStateSnapshot.state` is **`JsonSafe<PiChatSnapshot>`**, not the raw
85
+ snapshot §6 named — the transport boundary is expressed in the type.
86
+
87
+ `JsonSafe<T>` itself differs from §6's version: it hoists `unknown extends T` to
88
+ the top, maps functions to `never`, and terminates non-object/non-array at
89
+ `never`. Read it from `types.ts`; do not reimplement it from memory.
90
+
91
+ ### Commands
92
+
93
+ `AgentPromptCommand` and `AgentFollowUpCommand` both require
94
+ **`{ requestId, clientNonce, content }`** — there is no `text` or `kind:'user'`
95
+ shorthand. Prompts add `displayContent?`, `model?`, `thinkingLevel?`,
96
+ `attachments?`; follow-ups add `clientSeq`.
97
+
98
+ ## Error codes
99
+
100
+ Thirteen, exhaustive, order-stable, exported as both a const map and
101
+ `AGENT_GATEWAY_ERROR_CODES`:
102
+
103
+ `AGENT_TYPE_UNKNOWN` · `AGENT_SESSION_NOT_FOUND` · `AGENT_SCOPE_DENIED` ·
104
+ `AGENT_SESSION_REPLAY_GAP` · `AGENT_SESSION_CURSOR_AHEAD` ·
105
+ `AGENT_SESSION_CURSOR_EXPIRED` · `AGENT_SESSION_CURSOR_INVALID` ·
106
+ `AGENT_REQUEST_CONFLICT` · `AGENT_REQUEST_OUTCOME_UNKNOWN` ·
107
+ `AGENT_COMMAND_INVALID_STATE` · `AGENT_SESSION_RUNTIME_SCOPE_MISMATCH` ·
108
+ `AGENT_SHARED_ENVIRONMENT_UNAVAILABLE` · `AGENT_GATEWAY_CLOSED`
109
+
110
+ HTTP mapping lives in `server/agent-host/httpProjection.ts`
111
+ (`statusForGatewayError`). Note `AGENT_SESSION_RUNTIME_SCOPE_MISMATCH`,
112
+ `AGENT_REQUEST_CONFLICT` and `AGENT_COMMAND_INVALID_STATE` project to **409**.
113
+
114
+ ## Conformance levels
115
+
116
+ - **Level B** — bounded replay within a process lifetime, plus snapshot
117
+ rehydrate. This is what ships. A cursor older than the replay window, or one
118
+ from before a restart, yields `REPLAY_GAP` / `CURSOR_AHEAD` and the client
119
+ refetches state. Never a silent gap.
120
+ - **Level D** — durable replay across restarts and processes. Specified,
121
+ deliberately skipped, and owner-annotated in
122
+ `server/agent-host/testing/gatewayConformance.ts`. Owned by the streaming
123
+ lane (#1009).
124
+
125
+ The suite is parameterized by `replayLevel`/`paginationLevel`; a Level D host
126
+ must pass the same file with the flag raised.
127
+
128
+ ## Keyset pagination
129
+
130
+ Cursors are HMAC-signed and bound to scope and filter; tampering or reuse under
131
+ a different filter yields `AGENT_SESSION_CURSOR_INVALID`. Ordering is total, so
132
+ a session mutated between pages shifts predictably rather than vanishing.
133
+
134
+ ## Known descope
135
+
136
+ §10 of the 909 plan carries an unchecked acceptance box demanding recursive
137
+ JSON/size/depth validation of attachment and path leaves on the transport
138
+ boundary. §6.4 descoped exactly that to v2, and the conformance suite skips it
139
+ (`v2 remote wire validates JSON event leaves, paths, depth, and size`). The
140
+ plan is internally inconsistent on this point; the descope is what shipped.
141
+ Anything relying on recursive leaf validation must not assume it exists.
142
+
143
+ ## Construction
144
+
145
+ `createAgentHost()` is the single construction funnel — see
146
+ `server/agent-host/createAgentHost.ts` and Decision 29. It is a
147
+ deployment-static composition helper that returns a gateway and a `close()`; it
148
+ is **not** the AgentHost deployment/publication machinery that Decisions 25–28
149
+ rejected. Future internal/external host tiers belong to #905, not here.
150
+
151
+ `scripts/check-alignment-invariants.mjs` enforces that nothing outside an
152
+ allowlist calls it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
@@ -91,9 +91,9 @@
91
91
  "use-stick-to-bottom": "^1.1.6",
92
92
  "yaml": "^2.9.0",
93
93
  "zod": "^3.25.76",
94
- "@hachej/boring-bash": "0.1.92",
95
- "@hachej/boring-sandbox": "0.1.92",
96
- "@hachej/boring-ui-kit": "0.1.92"
94
+ "@hachej/boring-bash": "0.1.94",
95
+ "@hachej/boring-sandbox": "0.1.94",
96
+ "@hachej/boring-ui-kit": "0.1.94"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@vercel/sandbox": "2.7.1",