@treeseed/dsh-chapters 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -184,11 +184,12 @@ window.__ModuleLoader__.load({
184
184
  const ACTIVE_POLL_MS = 1e4;
185
185
  const QUIET_POLL_MS = 6e4;
186
186
  function pollSpacingFor(s) {
187
+ if (s.turnEndPulse === true) return ACTIVE_POLL_MS;
187
188
  if (s.edges) {
188
189
  if (s.status === "analyzing" || s.autorun === "armed") return ACTIVE_POLL_MS;
189
190
  return null;
190
191
  }
191
- const active = s.status === "analyzing" || s.autorun === "armed" || s.running;
192
+ const active = s.status === "analyzing" || s.autorun === "armed";
192
193
  if (!active && s.hidden) return null;
193
194
  return active ? ACTIVE_POLL_MS : QUIET_POLL_MS;
194
195
  }
@@ -392,6 +393,26 @@ window.__ModuleLoader__.load({
392
393
  function edgeKickDelayMs(msSinceLastPull) {
393
394
  return Math.max(EDGE_DEBOUNCE_MS, EDGE_FLOOR_MS - msSinceLastPull);
394
395
  }
396
+ /**
397
+ * #221: the turn-end pulse, derived from the host's own turn-boundary projection — no plugin
398
+ * record API needed, so it works on 0.2.0 hosts (where appendPluginRecord is absent) exactly as
399
+ * on newer ones. The host maintains a `turnBoundary` projection (the agent loop reads its
400
+ * lastTurn for its own bookkeeping); reading what the host already folds costs zero writes and
401
+ * delivers reactively through the same standard useProjection seat as any other projection.
402
+ * Returns true when a NEW turn ended since the last observation; the dock calls it once per
403
+ * render and stores the returned stamp back into `seen`.
404
+ */
405
+ function turnEndPulse(lastTurn, seen) {
406
+ const cur = typeof lastTurn === "number" && Number.isFinite(lastTurn) ? lastTurn : 0;
407
+ if (cur > seen) return {
408
+ pulse: true,
409
+ seen: cur
410
+ };
411
+ return {
412
+ pulse: false,
413
+ seen
414
+ };
415
+ }
395
416
  // src/client/prompt-dock.ts
396
417
  /**
397
418
  * The Suggested Next Step dock row (#113) — thin by design.
@@ -519,7 +540,9 @@ window.__ModuleLoader__.load({
519
540
  const { sessionId, inputActions, exec } = props;
520
541
  const sessionSnapshot = (props.useSession ?? (() => void 0))((snapshot) => snapshot);
521
542
  const running = props.running ?? deriveRunning(sessionSnapshot);
522
- const pushed = (props.useProjection ?? (() => void 0))(PROMPT_PROJECTION_KEY);
543
+ const useProjectionHook = props.useProjection ?? (() => void 0);
544
+ const pushed = useProjectionHook(PROMPT_PROJECTION_KEY);
545
+ const turnBoundary = useProjectionHook("turnBoundary");
523
546
  const [state, dispatch] = (0, react.useReducer)(promptBarReducer, initialBarState(), (s) => {
524
547
  const pushEvs = pushEventsOf(pushed);
525
548
  if (pushEvs !== null) return {
@@ -544,6 +567,7 @@ window.__ModuleLoader__.load({
544
567
  const lastPullAt = (0, react.useRef)(null);
545
568
  const edgeTimer = (0, react.useRef)(void 0);
546
569
  const pushLiveRef = (0, react.useRef)(false);
570
+ const seenTurnRef = (0, react.useRef)(0);
547
571
  const lastPushAt = (0, react.useRef)(null);
548
572
  const answered = (0, react.useRef)(false);
549
573
  const replyStatus = (0, react.useRef)("unknown");
@@ -605,13 +629,18 @@ window.__ModuleLoader__.load({
605
629
  pulled.current = true;
606
630
  let timer;
607
631
  const effectiveStatus = () => replyStatus.current === "unknown" ? stateRef.current.status : replyStatus.current;
608
- const spacingNow = () => pollSpacingFor({
609
- status: effectiveStatus(),
610
- autorun: stateRef.current.autorun,
611
- running: stateRef.current.running,
612
- hidden: typeof document !== "undefined" && document.hidden === true,
613
- edges: edgesRef.current
614
- });
632
+ const spacingNow = () => {
633
+ const pe = turnEndPulse(turnBoundary?.lastTurn, seenTurnRef.current);
634
+ if (pe.pulse) seenTurnRef.current = pe.seen;
635
+ return pollSpacingFor({
636
+ status: effectiveStatus(),
637
+ autorun: stateRef.current.autorun,
638
+ running: stateRef.current.running,
639
+ hidden: typeof document !== "undefined" && document.hidden === true,
640
+ edges: edgesRef.current,
641
+ turnEndPulse: pe.pulse
642
+ });
643
+ };
615
644
  const duePull = () => {
616
645
  const c = sessionId !== void 0 ? stateCache.get(sessionId) : void 0;
617
646
  return mountPullDecision({
@@ -997,13 +1026,14 @@ window.__ModuleLoader__.load({
997
1026
  return tryClick();
998
1027
  }
999
1028
  function makeDoorRunner(remote, sessions, door, verifySwitched, opts) {
1000
- return async function run(sessionId) {
1029
+ return async function run(sessionId, anchorSeq) {
1001
1030
  try {
1002
1031
  if (remote.commands?.execute === void 0) return {
1003
1032
  ok: false,
1004
1033
  message: "commands remote unavailable"
1005
1034
  };
1006
- const res = await remote.commands.execute(sessionId, door.command, []);
1035
+ const line = typeof anchorSeq === "number" && Number.isFinite(anchorSeq) ? `${door.command} ${String(Math.trunc(anchorSeq))}` : door.command;
1036
+ const res = await remote.commands.execute(sessionId, line, []);
1007
1037
  if (res.ok !== true) return {
1008
1038
  ok: false,
1009
1039
  message: (res.error?.message ?? `the ${door.noun} call failed`).slice(0, 90)
@@ -1246,7 +1276,16 @@ window.__ModuleLoader__.load({
1246
1276
  name: "conversation.chat.assistant-actions",
1247
1277
  id: door.id,
1248
1278
  label: door.label,
1249
- inject: (sessionId) => ({ run: () => run(sessionId) })
1279
+ inject: (sessionId, owner) => ({ run: async () => {
1280
+ let anchor = null;
1281
+ const mid = typeof owner?.messageId === "string" ? owner.messageId : "";
1282
+ if (mid !== "") try {
1283
+ const out = await runCommandText(remote, sessionId, `/chapters-anchor ${mid}`);
1284
+ const n = Number.parseInt(out.trim(), 10);
1285
+ if (Number.isFinite(n) && n > 0) anchor = n;
1286
+ } catch {}
1287
+ return run(sessionId, anchor);
1288
+ } })
1250
1289
  }, component));
1251
1290
  console.info(`[client:chapters] door factory ran: ${String(effects.length)} register effect(s), types: ${effects.map((e) => typeof e).join(",")}`);
1252
1291
  return effects;