pi2dsh 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1139,9 +1139,19 @@ function messageText(message) {
1139
1139
  * is one surface, however many Pi packages contribute threads to it.
1140
1140
  */
1141
1141
  var BrowserSurfaces = class {
1142
+ /** Last time a browser client touched the /pi2dsh route (ms epoch). */
1143
+ lastClientContactMs;
1142
1144
  #byParent = /* @__PURE__ */ new Map();
1143
1145
  #commandRunners = /* @__PURE__ */ new Map();
1144
1146
  #surfaces = /* @__PURE__ */ new Map();
1147
+ /**
1148
+ * Retained components from `(tui, theme) => Component` factories, keyed by
1149
+ * their slot (session + package + widget key). Retained because the driver
1150
+ * handed to the factory must stay LIVE: requestRender re-renders this
1151
+ * component into its slot for as long as the package holds the handle.
1152
+ * Replacing or clearing the slot disposes the previous component.
1153
+ */
1154
+ #factoryComponents = /* @__PURE__ */ new Map();
1145
1155
  #entrySources = /* @__PURE__ */ new Map();
1146
1156
  #draftRequests = /* @__PURE__ */ new Map();
1147
1157
  #liveDrafts = /* @__PURE__ */ new Map();
@@ -1342,11 +1352,56 @@ var BrowserSurfaces = class {
1342
1352
  setWidget(sessionId, packageName, key, content, theme) {
1343
1353
  if (sessionId.length === 0) return;
1344
1354
  const view = this.#view(sessionId, packageName);
1355
+ const slot = `${sessionId}${packageName ?? "pi"}${key}`;
1356
+ this.#factoryComponents.get(slot)?.dispose?.();
1357
+ this.#factoryComponents.delete(slot);
1358
+ if (typeof content === "function") {
1359
+ this.#mountFactory(slot, content, theme, (text) => {
1360
+ const live = this.#view(sessionId, packageName);
1361
+ if (text === void 0) delete live.widgets[key];
1362
+ else live.widgets[key] = text;
1363
+ });
1364
+ return;
1365
+ }
1345
1366
  const text = surfaceText(content, theme);
1346
1367
  if (text === void 0) delete view.widgets[key];
1347
1368
  else view.widgets[key] = text;
1348
1369
  }
1349
1370
  /**
1371
+ * Mount one `(tui, theme) => Component` factory: run it with a live driver,
1372
+ * render the retained component now, and re-render it into the same slot on
1373
+ * every requestRender. A factory or render that throws leaves the slot
1374
+ * empty — the package's own bug stays its own — but a RETAINED handle keeps
1375
+ * working for the component's lifetime.
1376
+ * @param slot - identity for the retained component (dispose-on-replace).
1377
+ * @param factory - the package's factory, exactly as passed to setWidget.
1378
+ * @param theme - the bridge's headless theme.
1379
+ * @param store - writes rendered text (or undefined to clear) into the slot.
1380
+ */
1381
+ #mountFactory(slot, factory, theme, store) {
1382
+ let component;
1383
+ const render = () => {
1384
+ if (component === void 0) return;
1385
+ if (this.#factoryComponents.get(slot) !== component) return;
1386
+ store(surfaceText(component, theme));
1387
+ };
1388
+ const driver = { requestRender: () => {
1389
+ render();
1390
+ } };
1391
+ try {
1392
+ component = factory(driver, theme);
1393
+ } catch {
1394
+ store(void 0);
1395
+ return;
1396
+ }
1397
+ if (component === null || typeof component?.render !== "function") {
1398
+ store(void 0);
1399
+ return;
1400
+ }
1401
+ this.#factoryComponents.set(slot, component);
1402
+ render();
1403
+ }
1404
+ /**
1350
1405
  * Record one simple presentation call (working chrome, title, header,
1351
1406
  * footer). The value is a string, a Pi component, a working-indicator
1352
1407
  * options object, or a header/footer factory; each renders to text here so
@@ -1606,6 +1661,7 @@ function registerBrowserSurfaceRoute(ctx, registry, hooks) {
1606
1661
  const response = res;
1607
1662
  const method = String(req.method ?? "GET");
1608
1663
  const url = new URL(String(req.url ?? "/"), "http://pi2dsh.invalid");
1664
+ registry.lastClientContactMs = Date.now();
1609
1665
  if (method === "POST" && url.pathname === "/pi2dsh/editor-draft") {
1610
1666
  const chunks = [];
1611
1667
  const body = await new Promise((settle) => {
@@ -3007,6 +3063,9 @@ const DSH_COMPAT_FIELD_PROTOCOLS = {
3007
3063
  supportsDeveloperRole: COMPLETIONS_AND_RESPONSES,
3008
3064
  supportsReasoningEffort: COMPLETIONS,
3009
3065
  supportsUsageInStreaming: COMPLETIONS,
3066
+ supportsFinishReason: COMPLETIONS,
3067
+ chatTemplateArgs: COMPLETIONS,
3068
+ supportsThinkingTokenBudget: COMPLETIONS,
3010
3069
  maxTokensField: COMPLETIONS,
3011
3070
  requiresToolResultName: COMPLETIONS,
3012
3071
  requiresAssistantAfterToolResult: COMPLETIONS,
@@ -4501,7 +4560,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
4501
4560
  return state.shared.browserSurfaces !== void 0 && state.shared.browserSurfacesRouted === true ? "tui" : "rpc";
4502
4561
  },
4503
4562
  get hasUI() {
4504
- return state.tuiSurfaces?.available === true || humanAnswererAvailable(userQuestions, agent);
4563
+ return state.tuiSurfaces?.available === true || humanAnswererAvailable(userQuestions, agent, state.shared);
4505
4564
  },
4506
4565
  cwd: contextCwd,
4507
4566
  sessionManager: session === void 0 ? state.bridge.readonlySessionManager({
@@ -4513,7 +4572,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
4513
4572
  scopedModels: [],
4514
4573
  thinkingLevel: thinkingLevelOf(state, agent),
4515
4574
  isIdle: () => command,
4516
- isProjectTrusted: () => false,
4575
+ isProjectTrusted: () => agent !== void 0 && contextCwd.length > 0,
4517
4576
  signal,
4518
4577
  abort: () => {
4519
4578
  const target = agent;
@@ -6802,11 +6861,18 @@ function sessionNameOf(ctx, state, session) {
6802
6861
  * @param userQuestions - the mounted question service, if any.
6803
6862
  * @param agent - the agent whose turn this context belongs to.
6804
6863
  */
6805
- function humanAnswererAvailable(userQuestions, agent) {
6864
+ function humanAnswererAvailable(userQuestions, agent, shared) {
6806
6865
  if (userQuestions === void 0) return false;
6807
6866
  if (isSubagentOrigin(agent)) return false;
6808
6867
  const service = userQuestions;
6809
- if (typeof service.registerProvider !== "function") return false;
6868
+ if (typeof service.registerProvider !== "function") {
6869
+ const events = service.ctx?.events;
6870
+ if (typeof events?.dispatch === "function") try {
6871
+ if (events.dispatch("pi2dsh:hasui-probe", ["user-questions/request"]).length > 0) return true;
6872
+ } catch {}
6873
+ const last = shared?.browserSurfaces?.lastClientContactMs;
6874
+ return typeof last === "number" && Date.now() - last < 15e3;
6875
+ }
6810
6876
  let dispose;
6811
6877
  try {
6812
6878
  dispose = service.registerProvider({ ask: async () => {
@@ -7334,7 +7400,7 @@ async function applyPiPackage(ctx, options) {
7334
7400
  });
7335
7401
  }
7336
7402
  const browserSurfaces = state.shared.browserSurfaces ??= new BrowserSurfaces();
7337
- if (state.shared.browserSurfacesRouted !== true) {
7403
+ if (options.config?.browserPresentation === true && state.shared.browserSurfacesRouted !== true) {
7338
7404
  const sessionCwd = (session) => {
7339
7405
  const cwd = ctx.get("sessions")?.get?.(session)?.header?.cwd;
7340
7406
  if (typeof cwd === "string" && cwd.length > 0) return cwd;
@@ -7605,4 +7671,4 @@ const runtimeInternals = {
7605
7671
  //#endregion
7606
7672
  export { overlayProviderConfig as a, runtimeInternals as c, normalizeToolSchema as i, getSharedChildExtensionCatalog as n, registerChildExtensionCatalog as o, mergeProviderRegistration as r, registerVisionCompanions as s, applyPiPackage as t };
7607
7673
 
7608
- //# sourceMappingURL=runtime-CGDq6LKi.mjs.map
7674
+ //# sourceMappingURL=runtime-Dnk9mhrc.mjs.map