@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.
@@ -340,6 +340,7 @@ var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
340
340
  var SESSION_ROOT_ENV = "BORING_AGENT_SESSION_ROOT";
341
341
  var SUMMARY_PREFIX_BYTES = 64 * 1024;
342
342
  var DEFAULT_LEGACY_WORKSPACE_ID = "default";
343
+ var TRUSTED_LOCAL_USER_ID = "local";
343
344
  function sessionDirForNamespace(namespace, explicitRoot) {
344
345
  const safeNamespace = namespace.trim();
345
346
  if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
@@ -358,6 +359,7 @@ var PiSessionStore = class {
358
359
  cwd;
359
360
  sessionDir;
360
361
  allowLegacyUnscopedAccess;
362
+ pathDerivedLegacyAccess;
361
363
  prefixCache = /* @__PURE__ */ new Map();
362
364
  listInFlight = /* @__PURE__ */ new Map();
363
365
  writerTails = /* @__PURE__ */ new Map();
@@ -366,9 +368,11 @@ var PiSessionStore = class {
366
368
  if (typeof options === "string") {
367
369
  this.sessionDir = options;
368
370
  this.allowLegacyUnscopedAccess = true;
371
+ this.pathDerivedLegacyAccess = false;
369
372
  return;
370
373
  }
371
374
  this.allowLegacyUnscopedAccess = true;
375
+ this.pathDerivedLegacyAccess = options?.sessionDir === void 0 && options?.sessionNamespace === void 0;
372
376
  this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace, options.sessionRoot) : defaultSessionDir(options?.storageCwd ?? cwd, options?.sessionRoot));
373
377
  }
374
378
  getSessionDir() {
@@ -713,6 +717,7 @@ var PiSessionStore = class {
713
717
  }
714
718
  throw new Error(`Session not found: ${sessionId}`);
715
719
  }
720
+ if (ctx) await this.assertFileBelongsToCtx(matchedPath, ctx, sessionId);
716
721
  return this.ensureWrapperForNativeSession(sessionId, matchedPath, ctx);
717
722
  }
718
723
  async assertFileBelongsToCtx(filepath, ctx, sessionId) {
@@ -975,8 +980,16 @@ var PiSessionStore = class {
975
980
  return header ? this.storedCtxBelongsToCtx(readHeaderSessionCtx(header), ctx) : isEmptySessionCtx(ctx);
976
981
  }
977
982
  storedCtxBelongsToCtx(storedCtx, ctx) {
978
- if (storedCtx === null) return this.allowLegacyUnscopedAccess && isLegacyUnscopedCtx(ctx);
979
- return sameSessionCtx(storedCtx, ctx);
983
+ const trustedLocalCtx = !ctx.userId || ctx.userId === TRUSTED_LOCAL_USER_ID;
984
+ if (storedCtx === null) {
985
+ return this.allowLegacyUnscopedAccess && (isLegacyUnscopedCtx(ctx) || this.pathDerivedLegacyAccess && trustedLocalCtx);
986
+ }
987
+ if (sameSessionCtx(storedCtx, ctx)) return true;
988
+ if (!this.pathDerivedLegacyAccess || !trustedLocalCtx) return false;
989
+ if (isEmptySessionCtx(storedCtx)) return true;
990
+ return Boolean(
991
+ storedCtx.workspaceId && storedCtx.workspaceId === ctx.workspaceId && (!storedCtx.userId || !ctx.userId && storedCtx.userId === TRUSTED_LOCAL_USER_ID)
992
+ );
980
993
  }
981
994
  };
982
995
  async function readJsonlPrefix(filepath, maxBytes = SUMMARY_PREFIX_BYTES) {
@@ -42,6 +42,7 @@ var AGENT_EFFECT_METHODS = {
42
42
  };
43
43
  function withAgentEffectAdmission(service, admit) {
44
44
  return {
45
+ ...service.ensurePiSessionBound ? { ensurePiSessionBound: (ctx, sessionId) => service.ensurePiSessionBound(ctx, sessionId) } : {},
45
46
  ...service.listSessions ? { listSessions: (ctx, options) => service.listSessions(ctx, options) } : {},
46
47
  async createSession(ctx, init) {
47
48
  await admit(ctx);
@@ -6,12 +6,12 @@ import {
6
6
  readConfiguredDefaultModel,
7
7
  registerConfiguredModelProviders,
8
8
  withPiHarnessDefaults
9
- } from "./chunk-IWBC6SF7.js";
9
+ } from "./chunk-2TMS2RU2.js";
10
10
  import {
11
11
  AgentEffectAdmissionError,
12
12
  createAgentRuntimeBridge,
13
13
  isObservedSynchronousServiceError
14
- } from "./chunk-CJ2BW2W2.js";
14
+ } from "./chunk-EOMY2HDT.js";
15
15
  import {
16
16
  AgentDefinitionValidationError,
17
17
  AgentDeploymentValidationError,
@@ -4605,6 +4605,18 @@ var HarnessPiChatService = class {
4605
4605
  const seq = envelope?.chunk?.seq;
4606
4606
  return typeof seq === "number" && Number.isInteger(seq) && seq >= 0 ? seq : tailIndex + 1;
4607
4607
  }
4608
+ /**
4609
+ * Validate the persisted session identity and instantiate its lazily-created
4610
+ * Pi adapter under the same full cache key. This is intentionally a narrow
4611
+ * trusted-host seam rather than another browser route.
4612
+ */
4613
+ async ensurePiSessionBound(ctx, sessionId) {
4614
+ this.lifecycle.assertOpen();
4615
+ await this.assertCanAccessSession(ctx, sessionId);
4616
+ const adapter = await this.getAdapter(ctx, sessionId, "", { authorize: false });
4617
+ await this.ensureChannel(ctx, sessionId, adapter);
4618
+ this.lifecycle.assertOpen();
4619
+ }
4608
4620
  async assertCanAccessSession(ctx, sessionId) {
4609
4621
  try {
4610
4622
  await this.sessionStore.load(toSessionCtx(ctx), sessionId);
@@ -4801,7 +4813,7 @@ function createAgentRuntimeBridge2(config, options = {}) {
4801
4813
  });
4802
4814
  }
4803
4815
  async function createRuntime(config, options) {
4804
- const harnessFactory = config.harnessFactory ?? (await import("./createHarness-PMYRKDE4.js")).createPiCodingAgentHarness;
4816
+ const harnessFactory = config.harnessFactory ?? (await import("./createHarness-UCD74XXK.js")).createPiCodingAgentHarness;
4805
4817
  const harnessInput = {
4806
4818
  tools: config.tools ?? [],
4807
4819
  cwd: config.workdir ?? DEFAULT_WORKDIR,
@@ -6957,7 +6969,7 @@ async function buildAgentComposition(input) {
6957
6969
  sessionNamespace: configuredNamespace,
6958
6970
  sessionDir: compatibility?.sessionDir ?? factoryInput.sessionDir
6959
6971
  }) : async (factoryInput) => {
6960
- const { createPiCodingAgentHarness } = await import("./createHarness-PMYRKDE4.js");
6972
+ const { createPiCodingAgentHarness } = await import("./createHarness-UCD74XXK.js");
6961
6973
  return createPiCodingAgentHarness({
6962
6974
  ...factoryInput,
6963
6975
  pi,
@@ -7311,7 +7323,16 @@ var EmbeddedAgentGateway = class {
7311
7323
  });
7312
7324
  this.runtime.activity.set(claim.workspaceScopeId, ref, "running");
7313
7325
  return { ...receipt, disposition: "prompt" };
7314
- }, true);
7326
+ }, {
7327
+ duplicateReceipt: true,
7328
+ guard: async () => await this.promptAdmission(
7329
+ scope,
7330
+ claim,
7331
+ ref,
7332
+ command.requestId,
7333
+ command.requireIdle === true
7334
+ )
7335
+ });
7315
7336
  }
7316
7337
  return await this.sessionEffect(ref, claim, "session.followup", command.requestId, command, async () => {
7317
7338
  const receipt = await service.followUp(context(claim, command.requestId), ref.sessionId, {
@@ -7321,7 +7342,7 @@ var EmbeddedAgentGateway = class {
7321
7342
  clientSeq: command.clientSeq
7322
7343
  });
7323
7344
  return { ...receipt, disposition: "followup" };
7324
- }, true);
7345
+ }, { duplicateReceipt: true });
7325
7346
  }
7326
7347
  async renameSession(input) {
7327
7348
  const claim = await this.verify(input.scope);
@@ -7395,16 +7416,18 @@ var EmbeddedAgentGateway = class {
7395
7416
  if (!summary) throw new AgentGatewayError(AgentGatewayErrorCode.AGENT_SESSION_NOT_FOUND, "session was not found");
7396
7417
  return summary;
7397
7418
  }
7398
- sessionEffect(ref, claim, operation, requestId, payload, action, duplicateReceipt = false, legacyAlias = false) {
7419
+ sessionEffect(ref, claim, operation, requestId, payload, action, options = {}) {
7399
7420
  return this.effect(
7400
7421
  claim,
7401
7422
  operation,
7402
7423
  sessionTarget(ref),
7403
7424
  requestId,
7404
7425
  payload,
7405
- () => this.withWriter(claim.workspaceScopeId, ref, action),
7406
- duplicateReceipt,
7407
- legacyAlias
7426
+ action,
7427
+ {
7428
+ ...options,
7429
+ serialize: (effect) => this.withWriter(claim.workspaceScopeId, ref, effect)
7430
+ }
7408
7431
  );
7409
7432
  }
7410
7433
  /** Internal legacy-alias seam; never exposed through the public Gateway contract. */
@@ -7419,8 +7442,7 @@ var EmbeddedAgentGateway = class {
7419
7442
  input.requestId,
7420
7443
  input.payload,
7421
7444
  input.action,
7422
- false,
7423
- true
7445
+ { legacyAlias: true }
7424
7446
  );
7425
7447
  }
7426
7448
  if (input.target.kind !== "session") throw new TypeError(`${input.operation} requires a session target`);
@@ -7431,12 +7453,20 @@ var EmbeddedAgentGateway = class {
7431
7453
  input.requestId,
7432
7454
  input.payload,
7433
7455
  input.action,
7434
- false,
7435
- true
7456
+ {
7457
+ legacyAlias: true,
7458
+ guard: input.retryableGuard
7459
+ }
7436
7460
  );
7437
7461
  }
7438
- async effect(claim, operation, target, requestId, payload, action, duplicateReceipt = false, legacyAlias = false) {
7462
+ async effect(claim, operation, target, requestId, payload, action, options = {}) {
7439
7463
  this.assertOpen();
7464
+ const {
7465
+ duplicateReceipt = false,
7466
+ legacyAlias = false,
7467
+ serialize,
7468
+ guard
7469
+ } = options;
7440
7470
  const key = {
7441
7471
  workspaceScopeId: claim.workspaceScopeId,
7442
7472
  authSubjectId: claim.authSubjectId,
@@ -7452,8 +7482,9 @@ var EmbeddedAgentGateway = class {
7452
7482
  const id = requestKeyString(key);
7453
7483
  const existing = this.effects.get(id);
7454
7484
  if (existing) return this.replayReceipt(await existing, duplicateReceipt);
7455
- const running = this.runtime.trackEffect((async () => {
7485
+ const execute = async () => {
7456
7486
  const current = await this.runtime.ledger.read(key);
7487
+ let admissionReceipt;
7457
7488
  if (current?.state === "pending-admission") {
7458
7489
  const admission = await this.runtime.effectAdmission.admit({ key, digest: digest2, scope: claim, operation, target });
7459
7490
  if (admission.type === "retryable") throw gatewayError(admission.error);
@@ -7461,71 +7492,87 @@ var EmbeddedAgentGateway = class {
7461
7492
  await this.runtime.ledger.reject(key, { kind: "gateway", error: admission.error });
7462
7493
  throw gatewayError(admission.error);
7463
7494
  }
7464
- await this.runtime.ledger.acceptAdmission(key, admission.admissionReceipt);
7465
- }
7466
- await this.runtime.ledger.beginEffect(key);
7467
- let actionResult;
7468
- try {
7469
- actionResult = action();
7470
- } catch (error) {
7471
- if (legacyAlias) {
7472
- if (error instanceof AgentEffectAdmissionError) {
7495
+ admissionReceipt = admission.admissionReceipt;
7496
+ }
7497
+ const runEffect = async () => {
7498
+ const locked = await this.runtime.ledger.read(key);
7499
+ if (locked?.state === "pending-admission") {
7500
+ if (admissionReceipt === void 0) {
7501
+ throw new AgentGatewayError(
7502
+ AgentGatewayErrorCode.AGENT_REQUEST_OUTCOME_UNKNOWN,
7503
+ "request admission receipt was unavailable"
7504
+ );
7505
+ }
7506
+ const retryableGuardError = await guard?.();
7507
+ if (retryableGuardError) throw gatewayError(retryableGuardError);
7508
+ await this.runtime.ledger.acceptAdmission(key, admissionReceipt);
7509
+ }
7510
+ await this.runtime.ledger.beginEffect(key);
7511
+ let actionResult;
7512
+ try {
7513
+ actionResult = action();
7514
+ } catch (error) {
7515
+ if (legacyAlias) {
7516
+ if (error instanceof AgentEffectAdmissionError) {
7517
+ const details = projectJson(error.details);
7518
+ const failure2 = {
7519
+ kind: "legacy-admission",
7520
+ code: error.code,
7521
+ statusCode: 500,
7522
+ message: error.message,
7523
+ ...details === void 0 ? {} : { details }
7524
+ };
7525
+ await this.runtime.ledger.reject(key, failure2);
7526
+ throw this.failure(failure2, true);
7527
+ }
7528
+ const failure = legacyServiceFailure(error);
7529
+ await this.runtime.ledger.reject(key, failure);
7530
+ throw this.failure(failure, true);
7531
+ }
7532
+ const unknown = new AgentGatewayError(
7533
+ AgentGatewayErrorCode.AGENT_REQUEST_OUTCOME_UNKNOWN,
7534
+ "effect outcome could not be safely replayed"
7535
+ );
7536
+ await this.runtime.ledger.markOutcomeUnknown(key, unknown.toJSON()).catch(() => {
7537
+ });
7538
+ throw error;
7539
+ }
7540
+ try {
7541
+ const receipt = await actionResult;
7542
+ this.runtime.assertOpen();
7543
+ await this.runtime.ledger.complete(key, receipt);
7544
+ return receipt;
7545
+ } catch (error) {
7546
+ if (legacyAlias && error instanceof AgentEffectAdmissionError) {
7473
7547
  const details = projectJson(error.details);
7474
- const failure2 = {
7548
+ const failure = {
7475
7549
  kind: "legacy-admission",
7476
7550
  code: error.code,
7477
7551
  statusCode: 500,
7478
7552
  message: error.message,
7479
7553
  ...details === void 0 ? {} : { details }
7480
7554
  };
7481
- await this.runtime.ledger.reject(key, failure2);
7482
- throw this.failure(failure2, true);
7555
+ await this.runtime.ledger.reject(key, failure);
7556
+ throw this.failure(failure, true);
7483
7557
  }
7484
- const failure = legacyServiceFailure(error);
7485
- await this.runtime.ledger.reject(key, failure);
7486
- throw this.failure(failure, true);
7487
- }
7488
- const unknown = new AgentGatewayError(
7489
- AgentGatewayErrorCode.AGENT_REQUEST_OUTCOME_UNKNOWN,
7490
- "effect outcome could not be safely replayed"
7491
- );
7492
- await this.runtime.ledger.markOutcomeUnknown(key, unknown.toJSON()).catch(() => {
7493
- });
7494
- throw error;
7495
- }
7496
- try {
7497
- const receipt = await actionResult;
7498
- this.runtime.assertOpen();
7499
- await this.runtime.ledger.complete(key, receipt);
7500
- return receipt;
7501
- } catch (error) {
7502
- if (legacyAlias && error instanceof AgentEffectAdmissionError) {
7503
- const details = projectJson(error.details);
7504
- const failure = {
7505
- kind: "legacy-admission",
7506
- code: error.code,
7507
- statusCode: 500,
7508
- message: error.message,
7509
- ...details === void 0 ? {} : { details }
7510
- };
7511
- await this.runtime.ledger.reject(key, failure);
7512
- throw this.failure(failure, true);
7513
- }
7514
- if (legacyAlias && (isObservedSynchronousServiceError(error) || isKnownLegacyServiceFailure(error))) {
7515
- const failure = legacyServiceFailure(error);
7516
- await this.runtime.ledger.reject(key, failure);
7517
- throw this.failure(failure, true);
7558
+ if (legacyAlias && (isObservedSynchronousServiceError(error) || isKnownLegacyServiceFailure(error))) {
7559
+ const failure = legacyServiceFailure(error);
7560
+ await this.runtime.ledger.reject(key, failure);
7561
+ throw this.failure(failure, true);
7562
+ }
7563
+ const unknown = new AgentGatewayError(
7564
+ AgentGatewayErrorCode.AGENT_REQUEST_OUTCOME_UNKNOWN,
7565
+ "effect outcome could not be safely replayed"
7566
+ );
7567
+ await this.runtime.ledger.markOutcomeUnknown(key, unknown.toJSON()).catch(() => {
7568
+ });
7569
+ if (legacyAlias) throw unknown;
7570
+ throw error;
7518
7571
  }
7519
- const unknown = new AgentGatewayError(
7520
- AgentGatewayErrorCode.AGENT_REQUEST_OUTCOME_UNKNOWN,
7521
- "effect outcome could not be safely replayed"
7522
- );
7523
- await this.runtime.ledger.markOutcomeUnknown(key, unknown.toJSON()).catch(() => {
7524
- });
7525
- if (legacyAlias) throw unknown;
7526
- throw error;
7527
- }
7528
- })(), key);
7572
+ };
7573
+ return serialize ? serialize(runEffect) : runEffect();
7574
+ };
7575
+ const running = this.runtime.trackEffect(execute(), key);
7529
7576
  this.effects.set(id, running);
7530
7577
  try {
7531
7578
  return await running;
@@ -7558,6 +7605,20 @@ var EmbeddedAgentGateway = class {
7558
7605
  "effect admission was rejected"
7559
7606
  );
7560
7607
  }
7608
+ async promptAdmission(scope, claim, ref, requestId, requireIdle) {
7609
+ const binding = await this.bindingForSession(scope, claim, ref);
7610
+ const snapshot = await binding.composition.service.readState(
7611
+ context(claim, requestId, binding.scope.identity),
7612
+ ref.sessionId
7613
+ );
7614
+ const admissible = requireIdle ? snapshot.status === "idle" : snapshot.status === "idle" || snapshot.status === "error";
7615
+ if (admissible) return void 0;
7616
+ return new AgentGatewayError(
7617
+ AgentGatewayErrorCode.AGENT_COMMAND_INVALID_STATE,
7618
+ requireIdle ? "session is not idle" : "prompt is invalid in current state",
7619
+ { status: snapshot.status }
7620
+ ).toJSON();
7621
+ }
7561
7622
  async withWriter(workspaceScopeId, ref, action) {
7562
7623
  const key = sessionKey(workspaceScopeId, ref);
7563
7624
  const previous = this.writerTails.get(key) ?? Promise.resolve();
@@ -8167,6 +8228,7 @@ var PromptBodySchema = z4.object({
8167
8228
  clientNonce: NonEmptyString.max(128),
8168
8229
  content: NonEmptyString.max(1e6),
8169
8230
  displayContent: NonEmptyString.max(1e6).optional(),
8231
+ requireIdle: z4.literal(true).optional(),
8170
8232
  model: ChatModelSelectionSchema.optional(),
8171
8233
  thinkingLevel: z4.enum(["off", "low", "medium", "high"]).optional(),
8172
8234
  attachments: z4.array(ChatAttachmentPayloadSchema).max(20).optional()
@@ -8486,7 +8548,8 @@ function createLegacyPiChatCompatibilityService(input) {
8486
8548
  target: options.target,
8487
8549
  requestId: options.requestId,
8488
8550
  payload: jsonProjection(options.payload),
8489
- action: options.action
8551
+ action: options.action,
8552
+ ...options.retryableGuard ? { retryableGuard: options.retryableGuard } : {}
8490
8553
  });
8491
8554
  return {
8492
8555
  ...input.service.listSessions ? { listSessions: (ctx, options) => input.service.listSessions(ctx, options) } : {},
@@ -8517,6 +8580,17 @@ function createLegacyPiChatCompatibilityService(input) {
8517
8580
  target: sessionTarget2(input.agentTypeId, sessionId),
8518
8581
  requestId: requestIdForPayload(ctx, payload.clientNonce),
8519
8582
  payload,
8583
+ ...payload.requireIdle ? {
8584
+ retryableGuard: async () => {
8585
+ const snapshot = await input.service.readState(ctx, sessionId);
8586
+ if (snapshot.status === "idle") return void 0;
8587
+ return new AgentGatewayError(
8588
+ AgentGatewayErrorCode.AGENT_COMMAND_INVALID_STATE,
8589
+ "session is not idle",
8590
+ { status: snapshot.status }
8591
+ ).toJSON();
8592
+ }
8593
+ } : {},
8520
8594
  action: () => input.service.prompt(ctx, sessionId, payload)
8521
8595
  }),
8522
8596
  followUp: (ctx, sessionId, payload) => effect({
@@ -9732,7 +9806,7 @@ async function dispatchGatewayInput(binding, input) {
9732
9806
  scope: binding.scope,
9733
9807
  agentTypeId: binding.agentTypeId,
9734
9808
  requestId,
9735
- title: contentToText(input.content ?? input.message).slice(0, 80) || void 0
9809
+ title: input.title?.trim() || contentToText(input.content ?? input.message).slice(0, 80) || void 0
9736
9810
  });
9737
9811
  const connection = await binding.gateway.connectSession({
9738
9812
  scope: binding.scope,
@@ -10897,6 +10971,75 @@ async function mountOrderedAgentHostLegacyRoutes(input) {
10897
10971
  );
10898
10972
  }
10899
10973
 
10974
+ // src/server/trustedPiSessionBinding.ts
10975
+ async function bindTrustedPiSession(input) {
10976
+ const sessionContext = trustedPiSessionContext(input.ctx, input.request, input.requested);
10977
+ await input.withServices(async ({ binding }) => {
10978
+ if (!binding.ensurePiSessionBound) {
10979
+ throw createWorkspaceAgentDispatcherError(
10980
+ ErrorCode.enum.INTERNAL_ERROR,
10981
+ "Pi session binding is unavailable",
10982
+ 500
10983
+ );
10984
+ }
10985
+ return await binding.ensurePiSessionBound(sessionContext, input.sessionId);
10986
+ });
10987
+ return {
10988
+ visibleUserMessageTarget: {
10989
+ async isIdle() {
10990
+ return await input.withServices(async ({ binding }) => {
10991
+ const snapshot = await binding.readState(sessionContext, input.sessionId);
10992
+ return snapshot.status === "idle";
10993
+ });
10994
+ },
10995
+ async sendIfIdle(sendInput) {
10996
+ const requestId = sendInput.requestId.trim();
10997
+ if (!requestId) throw new TypeError("requestId must be a non-empty string");
10998
+ try {
10999
+ const receipt = await input.withServices(async ({ prompt }) => await prompt.prompt(
11000
+ sessionContext,
11001
+ input.sessionId,
11002
+ {
11003
+ message: sendInput.message,
11004
+ displayMessage: sendInput.displayMessage ?? sendInput.message,
11005
+ clientNonce: requestId,
11006
+ requireIdle: true
11007
+ }
11008
+ ));
11009
+ return {
11010
+ status: "accepted",
11011
+ cursor: receipt.cursor,
11012
+ ...receipt.duplicate ? { duplicate: true } : {}
11013
+ };
11014
+ } catch (error) {
11015
+ if (error instanceof AgentGatewayError && error.code === AgentGatewayErrorCode.AGENT_COMMAND_INVALID_STATE) {
11016
+ return { status: "busy" };
11017
+ }
11018
+ if (isGoneSessionError(error)) return { status: "gone" };
11019
+ throw error;
11020
+ }
11021
+ }
11022
+ }
11023
+ };
11024
+ }
11025
+ function isGoneSessionError(error) {
11026
+ const code = error?.code;
11027
+ return code === AgentGatewayErrorCode.AGENT_SESSION_NOT_FOUND || code === AgentGatewayErrorCode.AGENT_SCOPE_DENIED || code === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH || code === ErrorCode.enum.SESSION_NOT_FOUND || code === ErrorCode.enum.UNAUTHORIZED;
11028
+ }
11029
+ function trustedPiSessionContext(ctx, request, requested) {
11030
+ if (requested?.workspaceId && requested.workspaceId !== ctx.workspaceId) {
11031
+ throw createWorkspaceAgentDispatcherError(ErrorCode.enum.UNAUTHORIZED, "Pi session workspace context mismatch", 401);
11032
+ }
11033
+ if (requested?.userId && requested.userId !== ctx.userId) {
11034
+ throw createWorkspaceAgentDispatcherError(ErrorCode.enum.UNAUTHORIZED, "Pi session user context mismatch", 401);
11035
+ }
11036
+ return {
11037
+ workspaceId: requested?.workspaceId ?? ctx.workspaceId,
11038
+ authSubject: requested?.userId ?? ctx.userId,
11039
+ requestId: request?.id ?? `trusted:${ctx.workspaceId}:${ctx.userId}`
11040
+ };
11041
+ }
11042
+
10900
11043
  // src/server/agentHostLegacyRouteRuntime.ts
10901
11044
  var DEFAULT_WORKSPACE_ID6 = "default";
10902
11045
  var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
@@ -11391,6 +11534,11 @@ async function mountAgentHostLegacyRouteRuntime(app, opts, mountInput, scopePoli
11391
11534
  tools,
11392
11535
  readyTracker,
11393
11536
  piChatService: composition.service,
11537
+ trustedPiChatService: agentHost.createPiChatService({
11538
+ service: composition.service,
11539
+ scope: authorizedScope,
11540
+ agentTypeId: defaultAgentTypeId
11541
+ }),
11394
11542
  authorizedScope,
11395
11543
  hostScope
11396
11544
  };
@@ -11526,6 +11674,7 @@ async function mountAgentHostLegacyRouteRuntime(app, opts, mountInput, scopePoli
11526
11674
  throw error;
11527
11675
  }
11528
11676
  return {
11677
+ binding,
11529
11678
  dispatcher: createBoundWorkspaceAgentDispatcher({
11530
11679
  gateway: agentHost.gateway,
11531
11680
  scope: binding.authorizedScope,
@@ -11538,6 +11687,25 @@ async function mountAgentHostLegacyRouteRuntime(app, opts, mountInput, scopePoli
11538
11687
  binding = await getOrCreateRuntimeBinding(boundCtx.workspaceId, void 0, { trustedCtx: boundCtx });
11539
11688
  }
11540
11689
  }
11690
+ async function ensureTrustedPiSessionBound(initialBinding, boundCtx, boundSessionId, request, requestedSessionCtx) {
11691
+ return await bindTrustedPiSession({
11692
+ ctx: boundCtx,
11693
+ request,
11694
+ sessionId: boundSessionId,
11695
+ requested: requestedSessionCtx,
11696
+ withServices: async (effect) => {
11697
+ const operation = await acquireDispatcherOperation(initialBinding, boundCtx, request);
11698
+ try {
11699
+ return await effect({
11700
+ binding: operation.binding.piChatService,
11701
+ prompt: operation.binding.trustedPiChatService
11702
+ });
11703
+ } finally {
11704
+ operation.release();
11705
+ }
11706
+ }
11707
+ });
11708
+ }
11541
11709
  function createLeasedWorkspaceAgentDispatcher(initialBinding, boundCtx, request) {
11542
11710
  return {
11543
11711
  async dispatch(input) {
@@ -11616,14 +11784,28 @@ async function mountAgentHostLegacyRouteRuntime(app, opts, mountInput, scopePoli
11616
11784
  }
11617
11785
  return {
11618
11786
  dispatcher: createLeasedWorkspaceAgentDispatcher(staticBinding, boundCtx, options?.request),
11619
- workspace: staticBinding.runtimeBundle.workspace
11787
+ workspace: staticBinding.runtimeBundle.workspace,
11788
+ bindPiSession: async (boundSessionId, requestedSessionCtx) => await ensureTrustedPiSessionBound(
11789
+ staticBinding,
11790
+ boundCtx,
11791
+ boundSessionId,
11792
+ options?.request,
11793
+ requestedSessionCtx
11794
+ )
11620
11795
  };
11621
11796
  }
11622
11797
  const binding = await getOrCreateRuntimeBinding(boundCtx.workspaceId, options?.request, { trustedCtx: boundCtx });
11623
11798
  bindingLifecycle.assertAdmission(boundCtx.workspaceId, options?.request);
11624
11799
  return {
11625
11800
  dispatcher: createLeasedWorkspaceAgentDispatcher(binding, boundCtx, options?.request),
11626
- workspace: binding.runtimeBundle.workspace
11801
+ workspace: binding.runtimeBundle.workspace,
11802
+ bindPiSession: async (boundSessionId, requestedSessionCtx) => await ensureTrustedPiSessionBound(
11803
+ binding,
11804
+ boundCtx,
11805
+ boundSessionId,
11806
+ options?.request,
11807
+ requestedSessionCtx
11808
+ )
11627
11809
  };
11628
11810
  }
11629
11811
  });
@@ -1,8 +1,8 @@
1
1
  import { A as AgentHarness, a as AgentReadiness, b as Agent } from '../harness-Cc48o6uS.js';
2
2
  export { c as AGENT_NOT_IMPLEMENTED_UNTIL_T1, d as AgentActor, e as AgentCoreHarness, f as AgentCoreHarnessFactory, g as AgentCorePromptInput, h as AgentCoreSessionAdapter, i as AgentCoreSessionSnapshot, j as AgentEvent, k as AgentMessageContent, l as AgentMessagePart, m as AgentNotImplementedError, n as AgentReadinessStatus, o as AgentResolveInputResponse, p as AgentSendInput, q as AgentStartReceipt, r as AgentStreamOptions } from '../harness-Cc48o6uS.js';
3
3
  import { S as SessionStore } from '../piChatEvent-BsMp3MSo.js';
4
- import { A as AgentCoreSessionService, a as AgentEffectAdmission } from '../piChatSessionService-BrPqKrpY.js';
5
- export { b as AGENT_EFFECT_METHODS, c as AgentEffectAdmissionError, P as PiChatEventStreamResult, d as PiChatEventStreamSubscription, e as PiChatEventSubscriber, f as PiChatReplayRangeError, g as PiChatSessionService, h as PiSessionCreateInit, i as PiSessionRequestContext, w as withAgentEffectAdmission } from '../piChatSessionService-BrPqKrpY.js';
4
+ import { A as AgentCoreSessionService, a as AgentEffectAdmission } from '../piChatSessionService-C0KbcGw9.js';
5
+ export { b as AGENT_EFFECT_METHODS, c as AgentEffectAdmissionError, P as PiChatEventStreamResult, d as PiChatEventStreamSubscription, e as PiChatEventSubscriber, f as PiChatReplayRangeError, g as PiChatSessionService, h as PiSessionCreateInit, i as PiSessionRequestContext, w as withAgentEffectAdmission } from '../piChatSessionService-C0KbcGw9.js';
6
6
  import '@mariozechner/pi-coding-agent';
7
7
  import 'zod';
8
8
 
@@ -3,7 +3,7 @@ import {
3
3
  AgentEffectAdmissionError,
4
4
  createAgent,
5
5
  withAgentEffectAdmission
6
- } from "../chunk-CJ2BW2W2.js";
6
+ } from "../chunk-EOMY2HDT.js";
7
7
  import {
8
8
  AGENT_NOT_IMPLEMENTED_UNTIL_T1,
9
9
  AgentNotImplementedError
@@ -4,7 +4,7 @@ import {
4
4
  deriveSourcePlugin,
5
5
  mergePiPackageSources,
6
6
  withPiHarnessDefaults
7
- } from "./chunk-IWBC6SF7.js";
7
+ } from "./chunk-2TMS2RU2.js";
8
8
  import "./chunk-AQBXNPMD.js";
9
9
  import "./chunk-2GBHYJVC.js";
10
10
  export {
@@ -101,6 +101,8 @@ interface AgentPromptCommand {
101
101
  readonly clientNonce: string;
102
102
  readonly content: string;
103
103
  readonly displayContent?: string;
104
+ /** Require an actually idle Pi session; busy is a retryable admission result. */
105
+ readonly requireIdle?: true;
104
106
  readonly model?: ChatModelSelection;
105
107
  readonly thinkingLevel?: ThinkingLevel;
106
108
  readonly attachments?: readonly ChatAttachmentPayload[];