@hachej/boring-agent 0.1.93 → 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.
@@ -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);
@@ -11,7 +11,7 @@ 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);
@@ -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
@@ -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[];
@@ -1,7 +1,7 @@
1
+ import { T as ToolUiMetadata, B as BoringChatPart, a as BoringChatMessage, P as PiChatStatus, Q as QueuedUserMessage, C as ChatError, b as PiChatEvent, c as PromptPayload, d as PromptReceipt, F as FollowUpPayload, e as FollowUpReceipt, f as QueueClearPayload, g as QueueClearReceipt, I as InterruptPayload, h as CommandReceipt, i as StopPayload, j as StopReceipt, k as SessionSummary } from '../piChatEvent-BsMp3MSo.js';
1
2
  import * as react from 'react';
2
3
  import { ComponentProps, HTMLAttributes, FormEvent, ReactNode, ComponentType } from 'react';
3
4
  import { FileUIPart, ChatStatus, UIMessage } from 'ai';
4
- import { T as ToolUiMetadata, B as BoringChatPart, a as BoringChatMessage, P as PiChatStatus, Q as QueuedUserMessage, C as ChatError, b as PiChatEvent, c as PromptPayload, d as PromptReceipt, F as FollowUpPayload, e as FollowUpReceipt, f as QueueClearPayload, g as QueueClearReceipt, I as InterruptPayload, h as CommandReceipt, i as StopPayload, j as StopReceipt, k as SessionSummary } from '../piChatEvent-BsMp3MSo.js';
5
5
  import { InputGroupAddon, InputGroupButton, InputGroupTextarea, Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from '@hachej/boring-ui-kit';
6
6
  import { Streamdown } from 'streamdown';
7
7
  import { StickToBottom } from 'use-stick-to-bottom';
@@ -176,6 +176,11 @@ interface SlashCommand {
176
176
  source?: 'local' | 'extension' | 'prompt' | 'skill';
177
177
  /** Originating plugin/package name (when known), shown as a tag. */
178
178
  sourcePlugin?: string;
179
+ /**
180
+ * Narrow browser-local escape hatch for independent controls that must remain
181
+ * usable while Pi is running. The predicate must admit only exact safe subcommands.
182
+ */
183
+ allowWhileBusy?: (args: string) => boolean;
179
184
  handler: SlashCommandHandler;
180
185
  }
181
186
  interface SlashCommandContext {
@@ -210,7 +215,7 @@ declare function createCommandRegistry(initial?: SlashCommand[]): CommandRegistr
210
215
 
211
216
  declare const builtinCommands: SlashCommand[];
212
217
 
213
- type ToolState = 'input-streaming' | 'input-available' | 'approval-requested' | 'approval-responded' | 'output-available' | 'output-error' | 'output-denied' | 'aborted';
218
+ type ToolState$1 = 'input-streaming' | 'input-available' | 'approval-requested' | 'approval-responded' | 'output-available' | 'output-error' | 'output-denied' | 'aborted';
214
219
 
215
220
  type BoringToolCallPart = Extract<BoringChatPart, {
216
221
  type: 'tool-call';
@@ -220,25 +225,25 @@ interface ToolRendererResolution {
220
225
  source: 'rendererId' | 'toolName' | 'fallback';
221
226
  requestedRendererId?: string;
222
227
  }
223
- interface ToolPart {
228
+ interface ToolPart$1 {
224
229
  /** Neutral render-model type. Legacy `tool-<name>` values are accepted at the renderer boundary. */
225
230
  type: 'tool-call' | `tool-${string}` | 'dynamic-tool';
226
231
  toolName: string;
227
232
  /** Stable tool call id; for Pi-native BoringChatPart this is BoringChatPart.id. */
228
233
  toolCallId: string;
229
- state: ToolState;
234
+ state: ToolState$1;
230
235
  input?: unknown;
231
236
  output?: unknown;
232
237
  errorText?: string;
233
238
  ui?: ToolUiMetadata;
234
239
  rendererResolution?: ToolRendererResolution;
235
240
  }
236
- type ToolRenderablePart = ToolPart | BoringToolCallPart | unknown;
237
- type ToolRenderer = (part: ToolPart) => ReactNode;
241
+ type ToolRenderablePart = ToolPart$1 | BoringToolCallPart | unknown;
242
+ type ToolRenderer = (part: ToolPart$1) => ReactNode;
238
243
  type ToolRendererOverrides = Partial<Record<string, ToolRenderer>>;
239
244
  declare const defaultToolRenderers: Record<string, ToolRenderer>;
240
245
  declare function mergeToolRenderers(overrides?: ToolRendererOverrides): Record<string, ToolRenderer>;
241
- declare function resolveToolRenderer(toolNameOrPart: string | ToolPart, overrides?: ToolRendererOverrides): ToolRenderer;
246
+ declare function resolveToolRenderer(toolNameOrPart: string | ToolPart$1, overrides?: ToolRendererOverrides): ToolRenderer;
242
247
 
243
248
  type ChatPanelRuntimeDependenciesWarmupStatus = {
244
249
  state: 'preparing' | 'ready' | 'failed';
@@ -770,6 +775,37 @@ interface AgentCommandOptions {
770
775
  }
771
776
  declare function getAgentCommands(options?: AgentCommandOptions): AgentCommandContribution[];
772
777
 
778
+ type ComposerDraftUpdate = (currentDraft: string) => string;
779
+ interface ComposerActionContributionProps {
780
+ updateDraft(update: ComposerDraftUpdate, options?: {
781
+ focus?: boolean;
782
+ }): void;
783
+ }
784
+ interface ComposerContribution {
785
+ id: string;
786
+ Top?: ComponentType;
787
+ Action?: ComponentType<ComposerActionContributionProps>;
788
+ commands?: readonly SlashCommand[];
789
+ }
790
+ declare function ComposerContributionProvider({ contribution, children, }: {
791
+ contribution: ComposerContribution;
792
+ children: ReactNode;
793
+ }): react.JSX.Element;
794
+
795
+ interface ChatMessageContributionProps {
796
+ message: BoringChatMessage;
797
+ }
798
+ interface ChatMessageContribution {
799
+ id: string;
800
+ /** Pure, hook-free claim predicate. */
801
+ matches(message: BoringChatMessage): boolean;
802
+ Component: ComponentType<ChatMessageContributionProps>;
803
+ }
804
+ declare function ChatMessageContributionProvider({ contribution, children, }: {
805
+ contribution: ChatMessageContribution;
806
+ children: ReactNode;
807
+ }): react.JSX.Element;
808
+
773
809
  declare function mergeShadcnToolRenderers(overrides?: ToolRendererOverrides): Record<string, ToolRenderer>;
774
810
 
775
811
  type GroupedToolEntry = {
@@ -832,6 +868,30 @@ type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
832
868
  };
833
869
  declare const CodeBlock: ({ code, language, showLineNumbers, className, children, ...props }: CodeBlockProps) => react.JSX.Element;
834
870
 
871
+ type ToolProps = ComponentProps<typeof Collapsible>;
872
+ declare const Tool: ({ className, ...props }: ToolProps) => react.JSX.Element;
873
+ type ToolState = "input-streaming" | "input-available" | "approval-requested" | "approval-responded" | "output-available" | "output-denied" | "output-error" | "aborted";
874
+ type ToolPart = {
875
+ type: `tool-${string}` | "dynamic-tool" | "tool-call";
876
+ state: ToolState;
877
+ toolName?: string;
878
+ input?: unknown;
879
+ output?: unknown;
880
+ errorText?: string;
881
+ };
882
+ type ToolHeaderProps = {
883
+ title?: ReactNode;
884
+ className?: string;
885
+ icon?: ReactNode;
886
+ type: ToolPart["type"];
887
+ state: ToolState;
888
+ toolName?: string;
889
+ statusLabel?: string;
890
+ };
891
+ declare const ToolHeader: ({ className, title, type, state, icon, toolName, statusLabel, ...props }: ToolHeaderProps) => react.JSX.Element;
892
+ type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
893
+ declare const ToolContent: ({ className, ...props }: ToolContentProps) => react.JSX.Element;
894
+
835
895
  declare function cn(...inputs: ClassValue[]): string;
836
896
 
837
- export { type AgentCommandContribution, type AgentCommandOptions, type AgentPluginReloadResult, ArtifactOpenProvider, type AvailableModel, ChatEmptyState, type ChatEmptyStateProps, PiChatPanel as ChatPanel, type PiChatPanelProps as ChatPanelProps, type ChatSuggestion, CodeBlock, type CommandRegistry, type ComposerBlocker, type ComposerBlockerAction, Conversation, ConversationContent, ConversationScrollButton, DebugDrawer, type GroupedToolEntry, Message, MessageAction, MessageActions, MessageContent, MessageResponse, ModelPickerMenu, ModelSelect, ModelSelectTrigger, type ModelSelection, type OpenArtifactHandler, type ParsedCommand, PiChatPanel, type PiChatPanelProps, SessionBrowser as PiSessionBrowser, type PiSessionCreateInit, SessionList as PiSessionList, type PiSessionSearchItem, type PiSessionSearchOptions, type PiSessionSearchSortMode, PromptInput, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, type SessionListProps, type SlashCommand, type SlashCommandContext, type ThinkingLevel, ThinkingSelect, ToolCallGroup, type ToolPart, type ToolRenderer, type ToolRendererOverrides, type UploadFileOptions, type UploadFileResult, type UsePiSessionsOptions, type UsePiSessionsResult, activeSessionStorageKey, builtinCommands, clearActiveSessionId, cn, createCommandRegistry, defaultChatSuggestions, defaultToolRenderers, getAgentCommands, matchPiSessionSearch, mergeShadcnToolRenderers, mergeToolRenderers, parsePiSessionSearchQuery, parseSlashCommand, readActiveSessionId, resolveToolRenderer, searchPiSessions, uploadFile, useOpenArtifact, usePiSessions, writeActiveSessionId };
897
+ export { type AgentCommandContribution, type AgentCommandOptions, type AgentPluginReloadResult, ArtifactOpenProvider, type AvailableModel, BoringChatMessage, ChatEmptyState, type ChatEmptyStateProps, type ChatMessageContribution, type ChatMessageContributionProps, ChatMessageContributionProvider, PiChatPanel as ChatPanel, type PiChatPanelProps as ChatPanelProps, type ChatSuggestion, CodeBlock, type CommandRegistry, type ComposerActionContributionProps, type ComposerBlocker, type ComposerBlockerAction, type ComposerContribution, ComposerContributionProvider, type ComposerDraftUpdate, Conversation, ConversationContent, ConversationScrollButton, DebugDrawer, type GroupedToolEntry, Message, MessageAction, MessageActions, MessageContent, MessageResponse, ModelPickerMenu, ModelSelect, ModelSelectTrigger, type ModelSelection, type OpenArtifactHandler, type ParsedCommand, PiChatPanel, type PiChatPanelProps, SessionBrowser as PiSessionBrowser, type PiSessionCreateInit, SessionList as PiSessionList, type PiSessionSearchItem, type PiSessionSearchOptions, type PiSessionSearchSortMode, PromptInput, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, type SessionListProps, type SlashCommand, type SlashCommandContext, type ThinkingLevel, ThinkingSelect, Tool, ToolCallGroup, ToolContent, ToolHeader, type ToolPart$1 as ToolPart, type ToolRenderer, type ToolRendererOverrides, type UploadFileOptions, type UploadFileResult, type UsePiSessionsOptions, type UsePiSessionsResult, activeSessionStorageKey, builtinCommands, clearActiveSessionId, cn, createCommandRegistry, defaultChatSuggestions, defaultToolRenderers, getAgentCommands, matchPiSessionSearch, mergeShadcnToolRenderers, mergeToolRenderers, parsePiSessionSearchQuery, parseSlashCommand, readActiveSessionId, resolveToolRenderer, searchPiSessions, uploadFile, useOpenArtifact, usePiSessions, writeActiveSessionId };