@treeseed/dsh-chapters 0.2.9 → 0.2.12
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 +77 -18
- package/lib/client.js.map +1 -1
- package/lib/commands.js +59 -0
- package/lib/commands.js.map +1 -1
- package/lib/index.d.ts +5 -0
- package/lib/index.js +46 -1
- package/lib/index.js.map +1 -1
- package/lib/prompt-state.d.ts +4 -0
- package/lib/prompt.d.ts +9 -0
- package/lib/prompt.js.map +1 -1
- package/lib/tools.js +31 -5
- package/lib/tools.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -182,15 +182,29 @@ window.__ModuleLoader__.load({
|
|
|
182
182
|
* autorun and pausing it silently would be the loop dying with nobody told.
|
|
183
183
|
*/
|
|
184
184
|
const ACTIVE_POLL_MS = 1e4;
|
|
185
|
-
|
|
185
|
+
/**
|
|
186
|
+
* #229: the quiet heartbeat is CONFIG-GATED (`promptQuietHeartbeatSec`) and defaults to ZERO.
|
|
187
|
+
* Measured on the maintainer's live log after 0.2.11 installed correctly (gaps exactly 60.0s): every
|
|
188
|
+
* pull is two durable nodes — a host `command/run` + `command/done` pair — and the user's rule is
|
|
189
|
+
* absolute: never repeated chapters-prompt executions. The sanctioned moments (mount discovery once
|
|
190
|
+
* per page load, the turn-end pulse, visibility return with an untrusted cache, forced confirms)
|
|
191
|
+
* remain; the ambient drumbeat does not exist unless configured. This module is client-safe (no node
|
|
192
|
+
* builtins), so the wiring injects the config value at boot via `configureQuietHeartbeat`.
|
|
193
|
+
*/
|
|
194
|
+
let QUIET_POLL_MS = 0;
|
|
195
|
+
function configureQuietHeartbeat(seconds) {
|
|
196
|
+
QUIET_POLL_MS = typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 ? Math.trunc(seconds * 1e3) : 0;
|
|
197
|
+
}
|
|
186
198
|
function pollSpacingFor(s) {
|
|
199
|
+
if (s.turnEndPulse === true) return ACTIVE_POLL_MS;
|
|
187
200
|
if (s.edges) {
|
|
188
201
|
if (s.status === "analyzing" || s.autorun === "armed") return ACTIVE_POLL_MS;
|
|
189
202
|
return null;
|
|
190
203
|
}
|
|
191
|
-
const active = s.status === "analyzing" || s.autorun === "armed"
|
|
204
|
+
const active = s.status === "analyzing" || s.autorun === "armed";
|
|
192
205
|
if (!active && s.hidden) return null;
|
|
193
|
-
|
|
206
|
+
if (!active) return QUIET_POLL_MS > 0 ? QUIET_POLL_MS : null;
|
|
207
|
+
return ACTIVE_POLL_MS;
|
|
194
208
|
}
|
|
195
209
|
/**
|
|
196
210
|
* #203: whether a freshly mounted bar pulls, or renders its cache and stays silent. The maintainer's
|
|
@@ -204,7 +218,7 @@ window.__ModuleLoader__.load({
|
|
|
204
218
|
if (!c.hasCache) return true;
|
|
205
219
|
if (c.status === "off") return false;
|
|
206
220
|
if (c.autorun === "armed" || c.status === "analyzing") return true;
|
|
207
|
-
return c.ageMs > QUIET_POLL_MS;
|
|
221
|
+
return QUIET_POLL_MS > 0 && c.ageMs > QUIET_POLL_MS;
|
|
208
222
|
}
|
|
209
223
|
/**
|
|
210
224
|
* #203: fold a cached wire event back into row state through the SAME reducer the live path uses —
|
|
@@ -271,6 +285,7 @@ window.__ModuleLoader__.load({
|
|
|
271
285
|
}
|
|
272
286
|
scalars[key] !== void 0 ? scalars[key] += ` ${value}` : scalars[key] = value;
|
|
273
287
|
}
|
|
288
|
+
configureQuietHeartbeat(Number.parseInt((scalars["heartbeat"] ?? "").trim(), 10) || 0);
|
|
274
289
|
const turnRaw = scalars["turn"] ?? "";
|
|
275
290
|
const turnSeq = /^\d+$/.test(turnRaw) ? Number(turnRaw) : null;
|
|
276
291
|
const errorRaw = scalars["error"] ?? "";
|
|
@@ -392,6 +407,26 @@ window.__ModuleLoader__.load({
|
|
|
392
407
|
function edgeKickDelayMs(msSinceLastPull) {
|
|
393
408
|
return Math.max(EDGE_DEBOUNCE_MS, EDGE_FLOOR_MS - msSinceLastPull);
|
|
394
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* #221: the turn-end pulse, derived from the host's own turn-boundary projection — no plugin
|
|
412
|
+
* record API needed, so it works on 0.2.0 hosts (where appendPluginRecord is absent) exactly as
|
|
413
|
+
* on newer ones. The host maintains a `turnBoundary` projection (the agent loop reads its
|
|
414
|
+
* lastTurn for its own bookkeeping); reading what the host already folds costs zero writes and
|
|
415
|
+
* delivers reactively through the same standard useProjection seat as any other projection.
|
|
416
|
+
* Returns true when a NEW turn ended since the last observation; the dock calls it once per
|
|
417
|
+
* render and stores the returned stamp back into `seen`.
|
|
418
|
+
*/
|
|
419
|
+
function turnEndPulse(lastTurn, seen) {
|
|
420
|
+
const cur = typeof lastTurn === "number" && Number.isFinite(lastTurn) ? lastTurn : 0;
|
|
421
|
+
if (cur > seen) return {
|
|
422
|
+
pulse: true,
|
|
423
|
+
seen: cur
|
|
424
|
+
};
|
|
425
|
+
return {
|
|
426
|
+
pulse: false,
|
|
427
|
+
seen
|
|
428
|
+
};
|
|
429
|
+
}
|
|
395
430
|
// src/client/prompt-dock.ts
|
|
396
431
|
/**
|
|
397
432
|
* The Suggested Next Step dock row (#113) — thin by design.
|
|
@@ -419,11 +454,12 @@ window.__ModuleLoader__.load({
|
|
|
419
454
|
".dsh-chapters_promptBar{isolation:isolate;box-sizing:border-box;width:100%;border-radius:var(--dsw-radius-md);height:36px;box-shadow:var(--dsw-elevation-panel);border:0;align-items:center;gap:10px;padding:4px 5px 4px 12px;display:flex;position:relative}",
|
|
420
455
|
".dsh-chapters_promptBar::before{z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu);backdrop-filter:var(--dsw-menu-backdrop-filter);content:\"\";pointer-events:none;position:absolute;inset:0}",
|
|
421
456
|
".dsh-chapters_promptExpanded::before{align-items:flex-start;position:absolute;inset:0}",
|
|
457
|
+
".dsh-chapters_promptExpanded{height:auto;min-height:36px;padding-top:8px;padding-bottom:8px}",
|
|
422
458
|
".dsh-chapters_promptGlyph{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}",
|
|
423
459
|
".dsh-chapters_promptLabel{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:24px}",
|
|
424
460
|
".dsh-chapters_promptText{min-width:0;color:var(--dsw-alias-label-primary-dimmed);font-size:13px;line-height:20px;cursor:pointer;border:0;background:0 0;padding:0;text-align:left;flex:1}",
|
|
425
461
|
".dsh-chapters_promptEllipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;display:block}",
|
|
426
|
-
".dsh-chapters_promptFull{white-space:pre-wrap;overflow:
|
|
462
|
+
".dsh-chapters_promptFull{white-space:pre-wrap;overflow-wrap:anywhere;line-height:20px;font-size:13px;color:var(--dsw-alias-label-primary);display:block}",
|
|
427
463
|
".dsh-chapters_promptError{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:20px;flex:none;max-width:34%}",
|
|
428
464
|
".dsh-chapters_promptNote{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px;min-width:0}",
|
|
429
465
|
".dsh-chapters_promptIntent{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}",
|
|
@@ -519,7 +555,9 @@ window.__ModuleLoader__.load({
|
|
|
519
555
|
const { sessionId, inputActions, exec } = props;
|
|
520
556
|
const sessionSnapshot = (props.useSession ?? (() => void 0))((snapshot) => snapshot);
|
|
521
557
|
const running = props.running ?? deriveRunning(sessionSnapshot);
|
|
522
|
-
const
|
|
558
|
+
const useProjectionHook = props.useProjection ?? (() => void 0);
|
|
559
|
+
const pushed = useProjectionHook(PROMPT_PROJECTION_KEY);
|
|
560
|
+
const turnBoundary = useProjectionHook("turnBoundary");
|
|
523
561
|
const [state, dispatch] = (0, react.useReducer)(promptBarReducer, initialBarState(), (s) => {
|
|
524
562
|
const pushEvs = pushEventsOf(pushed);
|
|
525
563
|
if (pushEvs !== null) return {
|
|
@@ -544,6 +582,7 @@ window.__ModuleLoader__.load({
|
|
|
544
582
|
const lastPullAt = (0, react.useRef)(null);
|
|
545
583
|
const edgeTimer = (0, react.useRef)(void 0);
|
|
546
584
|
const pushLiveRef = (0, react.useRef)(false);
|
|
585
|
+
const seenTurnRef = (0, react.useRef)(0);
|
|
547
586
|
const lastPushAt = (0, react.useRef)(null);
|
|
548
587
|
const answered = (0, react.useRef)(false);
|
|
549
588
|
const replyStatus = (0, react.useRef)("unknown");
|
|
@@ -605,13 +644,19 @@ window.__ModuleLoader__.load({
|
|
|
605
644
|
pulled.current = true;
|
|
606
645
|
let timer;
|
|
607
646
|
const effectiveStatus = () => replyStatus.current === "unknown" ? stateRef.current.status : replyStatus.current;
|
|
608
|
-
const spacingNow = () =>
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
647
|
+
const spacingNow = () => {
|
|
648
|
+
if (stateRef.current.autorun === "armed") return ACTIVE_POLL_MS;
|
|
649
|
+
const pe = turnEndPulse(turnBoundary?.lastTurn, seenTurnRef.current);
|
|
650
|
+
if (pe.pulse) seenTurnRef.current = pe.seen;
|
|
651
|
+
return pollSpacingFor({
|
|
652
|
+
status: effectiveStatus(),
|
|
653
|
+
autorun: stateRef.current.autorun,
|
|
654
|
+
running: stateRef.current.running,
|
|
655
|
+
hidden: typeof document !== "undefined" && document.hidden === true,
|
|
656
|
+
edges: edgesRef.current,
|
|
657
|
+
turnEndPulse: pe.pulse
|
|
658
|
+
});
|
|
659
|
+
};
|
|
615
660
|
const duePull = () => {
|
|
616
661
|
const c = sessionId !== void 0 ? stateCache.get(sessionId) : void 0;
|
|
617
662
|
return mountPullDecision({
|
|
@@ -628,7 +673,8 @@ window.__ModuleLoader__.load({
|
|
|
628
673
|
if (!(opts?.mount === true && duePull())) return;
|
|
629
674
|
}
|
|
630
675
|
const spacing = spacingNow();
|
|
631
|
-
|
|
676
|
+
const mountDiscovery = opts?.mount === true && duePull();
|
|
677
|
+
if (opts?.force === true || mountDiscovery || spacing !== null && opts?.mount !== true) await refresh();
|
|
632
678
|
if (replyStatus.current === "off") return;
|
|
633
679
|
const next = spacingNow();
|
|
634
680
|
if (next !== null) timer = setTimeout(() => {
|
|
@@ -763,7 +809,10 @@ window.__ModuleLoader__.load({
|
|
|
763
809
|
className: "dsh-chapters_promptText",
|
|
764
810
|
"aria-label": state.expanded ? "Put this suggested prompt in the input box" : "Show the full suggested prompt",
|
|
765
811
|
onClick: populate
|
|
766
|
-
}, state.expanded ? (0, react.createElement)("span", { className: "dsh-chapters_promptFull" }, state.text ?? "") : (0, react.createElement)("span", {
|
|
812
|
+
}, state.expanded ? (0, react.createElement)("span", { className: "dsh-chapters_promptFull" }, state.text ?? "") : (0, react.createElement)("span", {
|
|
813
|
+
className: "dsh-chapters_promptEllipsis",
|
|
814
|
+
title: state.text ?? void 0
|
|
815
|
+
}, lineFor(state))), ...state.note === "" ? [] : [(0, react.createElement)("span", {
|
|
767
816
|
key: "note",
|
|
768
817
|
className: "dsh-chapters_promptNote",
|
|
769
818
|
role: "status"
|
|
@@ -997,13 +1046,14 @@ window.__ModuleLoader__.load({
|
|
|
997
1046
|
return tryClick();
|
|
998
1047
|
}
|
|
999
1048
|
function makeDoorRunner(remote, sessions, door, verifySwitched, opts) {
|
|
1000
|
-
return async function run(sessionId) {
|
|
1049
|
+
return async function run(sessionId, anchorSeq) {
|
|
1001
1050
|
try {
|
|
1002
1051
|
if (remote.commands?.execute === void 0) return {
|
|
1003
1052
|
ok: false,
|
|
1004
1053
|
message: "commands remote unavailable"
|
|
1005
1054
|
};
|
|
1006
|
-
const
|
|
1055
|
+
const line = typeof anchorSeq === "number" && Number.isFinite(anchorSeq) ? `${door.command} ${String(Math.trunc(anchorSeq))}` : door.command;
|
|
1056
|
+
const res = await remote.commands.execute(sessionId, line, []);
|
|
1007
1057
|
if (res.ok !== true) return {
|
|
1008
1058
|
ok: false,
|
|
1009
1059
|
message: (res.error?.message ?? `the ${door.noun} call failed`).slice(0, 90)
|
|
@@ -1246,7 +1296,16 @@ window.__ModuleLoader__.load({
|
|
|
1246
1296
|
name: "conversation.chat.assistant-actions",
|
|
1247
1297
|
id: door.id,
|
|
1248
1298
|
label: door.label,
|
|
1249
|
-
inject: (sessionId) => ({ run: () =>
|
|
1299
|
+
inject: (sessionId, owner) => ({ run: async () => {
|
|
1300
|
+
let anchor = null;
|
|
1301
|
+
const mid = typeof owner?.messageId === "string" ? owner.messageId : "";
|
|
1302
|
+
if (mid !== "") try {
|
|
1303
|
+
const out = await runCommandText(remote, sessionId, `/chapters-anchor ${mid}`);
|
|
1304
|
+
const n = Number.parseInt(out.trim(), 10);
|
|
1305
|
+
if (Number.isFinite(n) && n > 0) anchor = n;
|
|
1306
|
+
} catch {}
|
|
1307
|
+
return run(sessionId, anchor);
|
|
1308
|
+
} })
|
|
1250
1309
|
}, component));
|
|
1251
1310
|
console.info(`[client:chapters] door factory ran: ${String(effects.length)} register effect(s), types: ${effects.map((e) => typeof e).join(",")}`);
|
|
1252
1311
|
return effects;
|