@north-light/crouter 0.3.179 → 0.3.181

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.
Files changed (137) hide show
  1. package/dist/api/client.d.ts +27 -2
  2. package/dist/api/client.js +41 -1
  3. package/dist/api/dto/broker.d.ts +32 -0
  4. package/dist/api/dto/crons.d.ts +17 -0
  5. package/dist/api/dto/human.d.ts +1 -7
  6. package/dist/api/dto/inbox.d.ts +71 -1
  7. package/dist/api/dto/inbox.js +9 -1
  8. package/dist/api/dto/memory.d.ts +17 -0
  9. package/dist/api/dto/memory.js +6 -0
  10. package/dist/api/dto/messages.d.ts +5 -0
  11. package/dist/api/dto/reviews.d.ts +8 -4
  12. package/dist/api/index.d.ts +1 -0
  13. package/dist/api/index.js +1 -0
  14. package/dist/api/routes.d.ts +5 -0
  15. package/dist/api/routes.js +8 -1
  16. package/dist/build-root.d.ts +7 -0
  17. package/dist/build-root.js +21 -0
  18. package/dist/builtin-memory/insights/init.md +48 -3
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/__tests__/insights-active-init.test.ts +98 -0
  20. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +7 -50
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +16 -1
  22. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +34 -0
  23. package/dist/cli.js +1 -2
  24. package/dist/clients/attach/__tests__/context-message.test.js +5 -2
  25. package/dist/clients/attach/assets/README.md +7 -0
  26. package/dist/clients/attach/assets/whip-06.mp3 +0 -0
  27. package/dist/clients/attach/assets/whip-crack.mp3 +0 -0
  28. package/dist/clients/attach/assets/whip-snap.mp3 +0 -0
  29. package/dist/clients/attach/chrome/canvas-panels.d.ts +7 -1
  30. package/dist/clients/attach/chrome/canvas-panels.js +20 -3
  31. package/dist/clients/attach/chrome/review-wait.d.ts +6 -0
  32. package/dist/clients/attach/chrome/review-wait.js +22 -0
  33. package/dist/clients/attach/chrome/roster.js +23 -2
  34. package/dist/clients/attach/chrome/widgets.js +1 -1
  35. package/dist/clients/attach/input/controller.js +4 -3
  36. package/dist/clients/attach/overlays/mcp.js +3 -1
  37. package/dist/clients/attach/render/chat-view.js +1 -1
  38. package/dist/clients/attach/session/whip.d.ts +1 -0
  39. package/dist/clients/attach/session/whip.js +26 -0
  40. package/dist/clients/attach/slash/dispatch.js +2 -0
  41. package/dist/clients/attach/viewer.js +578 -573
  42. package/dist/clients/inbox/review/document-surface.d.ts +1 -1
  43. package/dist/clients/inbox/review/document-surface.js +4 -4
  44. package/dist/clients/inbox/review/launch.js +16 -4
  45. package/dist/clients/inbox/review/review-client.d.ts +9 -4
  46. package/dist/clients/inbox/review/review-client.js +3 -0
  47. package/dist/commands/cron.js +30 -8
  48. package/dist/commands/human/prompts.d.ts +7 -2
  49. package/dist/commands/human/prompts.js +15 -10
  50. package/dist/commands/human.js +1 -2
  51. package/dist/commands/memory/find.js +11 -8
  52. package/dist/commands/memory/read.js +111 -11
  53. package/dist/commands/memory/write.js +1 -1
  54. package/dist/commands/memory.js +1 -1
  55. package/dist/commands/pkg/market-manage.d.ts +13 -0
  56. package/dist/commands/pkg/market-manage.js +39 -33
  57. package/dist/commands/pkg/plugin-inspect.js +4 -3
  58. package/dist/commands/pkg/plugin-manage.js +12 -11
  59. package/dist/commands/surface/node/focus.js +1 -2
  60. package/dist/commands/sys/doctor.js +4 -4
  61. package/dist/commands/sys/setup-core.d.ts +14 -7
  62. package/dist/commands/sys/setup-core.js +66 -11
  63. package/dist/commands/sys/setup-wizard.js +2 -2
  64. package/dist/commands/sys/setup.js +1 -1
  65. package/dist/core/__tests__/cron-held-settlement.test.d.ts +1 -0
  66. package/dist/core/__tests__/cron-held-settlement.test.js +222 -0
  67. package/dist/core/__tests__/helpers/harness.js +1 -2
  68. package/dist/core/__tests__/phase4-review-store.test.js +1 -0
  69. package/dist/core/__tests__/serial/command-plugins.test.js +88 -1
  70. package/dist/core/__tests__/session-model.test.js +5 -3
  71. package/dist/core/bootstrap.d.ts +0 -4
  72. package/dist/core/bootstrap.js +1 -55
  73. package/dist/core/canvas/crons.d.ts +54 -2
  74. package/dist/core/canvas/crons.js +48 -4
  75. package/dist/core/canvas/db.js +23 -0
  76. package/dist/core/command-manifests/manifest.d.ts +11 -0
  77. package/dist/core/command-manifests/manifest.js +45 -4
  78. package/dist/core/command-manifests/schema.d.ts +1 -1
  79. package/dist/core/command-plugins/bundle.d.ts +1 -0
  80. package/dist/core/command-plugins/bundle.js +3 -3
  81. package/dist/core/command-plugins/discovery.d.ts +5 -2
  82. package/dist/core/command-plugins/discovery.js +5 -5
  83. package/dist/core/command-plugins/help-addenda.d.ts +12 -0
  84. package/dist/core/command-plugins/help-addenda.js +30 -0
  85. package/dist/core/command.js +25 -2
  86. package/dist/core/config.js +0 -1
  87. package/dist/core/human/convention.d.ts +0 -1
  88. package/dist/core/human/convention.js +0 -6
  89. package/dist/core/keybindings/inbox.d.ts +6 -8
  90. package/dist/core/keybindings/inbox.js +6 -15
  91. package/dist/core/keybindings/index.d.ts +1 -1
  92. package/dist/core/keybindings/index.js +1 -1
  93. package/dist/core/memory/doc-link-grammar.js +4 -1
  94. package/dist/core/memory-resolver.d.ts +28 -4
  95. package/dist/core/memory-resolver.js +51 -39
  96. package/dist/core/review/stage.js +1 -0
  97. package/dist/core/review/store.d.ts +5 -0
  98. package/dist/core/review/store.js +10 -0
  99. package/dist/core/review/types.d.ts +4 -0
  100. package/dist/core/runtime/broker/event-projection.d.ts +8 -1
  101. package/dist/core/runtime/broker/event-projection.js +25 -1
  102. package/dist/core/runtime/broker/frame-dispatch.d.ts +2 -0
  103. package/dist/core/runtime/broker/frame-dispatch.js +50 -8
  104. package/dist/core/runtime/broker/message-ledger.d.ts +53 -0
  105. package/dist/core/runtime/broker/message-ledger.js +143 -0
  106. package/dist/core/runtime/broker/rebind.js +14 -0
  107. package/dist/core/runtime/broker-protocol.d.ts +46 -1
  108. package/dist/core/runtime/broker.js +11 -2
  109. package/dist/core/runtime/interactive-deliver.d.ts +5 -2
  110. package/dist/core/runtime/interactive-deliver.js +6 -3
  111. package/dist/core/runtime/shell-expansion.d.ts +32 -0
  112. package/dist/core/runtime/shell-expansion.js +102 -0
  113. package/dist/core/session-model/session-state.d.ts +9 -4
  114. package/dist/core/session-model/session-state.js +5 -1
  115. package/dist/daemon/api/handlers/broker-ops.js +8 -0
  116. package/dist/daemon/api/handlers/crons.js +14 -1
  117. package/dist/daemon/api/handlers/inbox.js +346 -2
  118. package/dist/daemon/api/handlers/memory.d.ts +2 -0
  119. package/dist/daemon/api/handlers/memory.js +48 -0
  120. package/dist/daemon/api/handlers/messages.js +7 -1
  121. package/dist/daemon/api/handlers/reviews.js +7 -5
  122. package/dist/daemon/api/map.js +3 -0
  123. package/dist/daemon/api/server.js +2 -0
  124. package/dist/daemon/cron-run.js +71 -3
  125. package/dist/daemon/crtrd.js +3 -0
  126. package/dist/daemon/reconcilers/pending-review-submit.d.ts +7 -0
  127. package/dist/daemon/reconcilers/pending-review-submit.js +35 -0
  128. package/dist/daemon/review/companion.d.ts +8 -0
  129. package/dist/daemon/review/companion.js +35 -0
  130. package/dist/daemon/review/deliver.js +2 -1
  131. package/dist/daemon/review/finish.d.ts +29 -2
  132. package/dist/daemon/review/finish.js +75 -2
  133. package/dist/shared/generated-context.d.ts +3 -4
  134. package/dist/shared/generated-context.js +24 -6
  135. package/dist/types.d.ts +0 -1
  136. package/package.json +1 -1
  137. package/runtime.lock.json +2 -2
@@ -0,0 +1,35 @@
1
+ // The safety net under a deferred review submit.
2
+ //
3
+ // The companion's turn end is what normally completes a submit the human left
4
+ // waiting (`daemon/api/handlers/broker-ops.ts`). A companion that crashed, was
5
+ // torn down, or ended its turn while this daemon was starting never delivers
6
+ // that settlement, so this pass completes any review whose companion is no
7
+ // longer working. Without it a review could wait forever on a turn end that is
8
+ // never coming.
9
+ import { emitEvent } from '../../core/events/emit.js';
10
+ import { operationIdContext } from '../../core/events/operation-id.js';
11
+ import { listSubmitRequestedReviews } from '../../core/review/store.js';
12
+ import { completeRequestedSubmit } from '../review/finish.js';
13
+ export class PendingReviewSubmitReconciler {
14
+ run(ctx) {
15
+ if (!ctx.lifecycle.acceptsDetachedWork())
16
+ return;
17
+ let pending;
18
+ try {
19
+ pending = listSubmitRequestedReviews();
20
+ }
21
+ catch (error) {
22
+ operationIdContext.fresh(() => {
23
+ emitEvent({ level: 'error', event: 'review.deferred_submit.scan_failed', error });
24
+ });
25
+ return;
26
+ }
27
+ for (const review of pending) {
28
+ ctx.lifecycle.registerDetached(completeRequestedSubmit(review.review_id, { atCompanionTurnEnd: false }).then(() => { }, (error) => {
29
+ operationIdContext.fresh(() => {
30
+ emitEvent({ level: 'error', event: 'review.deferred_submit.failed', error });
31
+ });
32
+ }));
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,8 @@
1
+ /** Is the companion inside a turn right now? */
2
+ export declare function companionMidTurn(nodeId: string): boolean;
3
+ /** Does the companion hold work it has not yet consumed? A deferred entry is
4
+ * excluded for the same reason the settlement policy excludes it: it never
5
+ * starts a turn of its own. */
6
+ export declare function companionHasQueuedWork(nodeId: string): boolean;
7
+ /** Would submitting right now step on the companion's work? */
8
+ export declare function companionStillWorking(nodeId: string): boolean;
@@ -0,0 +1,35 @@
1
+ // Is a review companion still working? This is what a deferred submit waits on.
2
+ //
3
+ // Two facts, both already maintained by the runtime, and neither one enough
4
+ // alone:
5
+ //
6
+ // - MID-TURN — the `<jobDir>/busy` marker AND-ed with a live broker pid, the
7
+ // canonical "pi is inside a turn" signal (`core/runtime/busy.ts`). A stale
8
+ // marker from a crashed broker reads false through the AND.
9
+ // - QUEUED — an undelivered non-deferred inbox entry. Comments are pushed to
10
+ // the companion as inbox messages, so the ordinary rhythm is: the human
11
+ // comments, the entry lands, the companion's turn starts a moment later.
12
+ // Between those two moments the busy marker is cold while the companion is
13
+ // unmistakably about to work, and submitting into that gap kills the edit
14
+ // the wait exists to protect.
15
+ import { getNode } from '../../core/canvas/canvas.js';
16
+ import { isPidAlive } from '../../core/canvas/pid.js';
17
+ import { readCursor, readInboxSince } from '../../core/feed/inbox.js';
18
+ import { isBusy } from '../../core/runtime/busy.js';
19
+ /** Is the companion inside a turn right now? */
20
+ export function companionMidTurn(nodeId) {
21
+ const node = getNode(nodeId);
22
+ if (node === null || !isPidAlive(node.pi_pid))
23
+ return false;
24
+ return isBusy(nodeId);
25
+ }
26
+ /** Does the companion hold work it has not yet consumed? A deferred entry is
27
+ * excluded for the same reason the settlement policy excludes it: it never
28
+ * starts a turn of its own. */
29
+ export function companionHasQueuedWork(nodeId) {
30
+ return readInboxSince(nodeId, readCursor(nodeId)).some((entry) => entry.tier !== 'deferred');
31
+ }
32
+ /** Would submitting right now step on the companion's work? */
33
+ export function companionStillWorking(nodeId) {
34
+ return companionMidTurn(nodeId) || companionHasQueuedWork(nodeId);
35
+ }
@@ -1,4 +1,5 @@
1
1
  import { FinalizationError, pushFinal, redeliverFinalReport } from '../../core/feed/feed.js';
2
+ import { formatReviewApproval } from '../../shared/generated-context.js';
2
3
  import { deliverNodeMessage } from '../messaging/node-message.js';
3
4
  function approvalBody(review, resultPath) {
4
5
  const summary = review.changed
@@ -8,7 +9,7 @@ function approvalBody(review, resultPath) {
8
9
  }
9
10
  /** Render the immutable approval fact without replaying comment text. */
10
11
  export function renderApprovalMessage(review, resultPath) {
11
- return approvalBody(review, resultPath);
12
+ return formatReviewApproval(approvalBody(review, resultPath));
12
13
  }
13
14
  function recordedFailedFanout(review, reportBasename) {
14
15
  const error = review.delivery_error;
@@ -5,6 +5,13 @@ export interface ReviewApprovalFinishOutcome {
5
5
  record: ReviewRecord;
6
6
  result: ReviewResult;
7
7
  }
8
+ /** The human submitted while the companion was still working. The review stays
9
+ * open and the daemon completes the approval once the companion goes quiet. */
10
+ export interface ReviewSubmitDeferredOutcome {
11
+ outcome: 'awaiting_companion';
12
+ record: ReviewRecord;
13
+ }
14
+ export type ReviewSubmitOutcome = ReviewApprovalFinishOutcome | ReviewSubmitDeferredOutcome;
8
15
  export interface ReviewCancellationFinishOutcome {
9
16
  outcome: 'settled' | 'already_settled';
10
17
  record: ReviewRecord;
@@ -13,8 +20,28 @@ export interface ReviewCancellationFinishOutcome {
13
20
  export type ReviewFinishOutcome = ReviewApprovalFinishOutcome | ReviewCancellationFinishOutcome;
14
21
  /** Retire a companion only through a lifecycle transition legal for its current status. */
15
22
  export declare function retireCompanion(nodeId: string, outcome: 'approved' | 'canceled'): 'retired' | 'already' | 'missing';
16
- /** Commit approval once, then independently finish its projection, delivery, and companion retirement ledgers. */
17
- export declare function submitReviewApproval(reviewId: string): Promise<ReviewApprovalFinishOutcome>;
23
+ /** Approve on the human's behalf, waiting first when the companion is still
24
+ * working. Waiting is the only new outcome: an idle companion approves in the
25
+ * same pass it always did, and the wait never freezes the review — comments
26
+ * added during it are in the snapshot, because the snapshot happens at
27
+ * completion and the review is still open. */
28
+ export declare function submitReviewApproval(reviewId: string): Promise<ReviewSubmitOutcome>;
29
+ /** Complete a submit the human already pressed, once its companion is quiet.
30
+ * Returns null when there is nothing to complete — no pending intent, the
31
+ * review moved on, or the companion is still working. Idempotent: concurrent
32
+ * callers race on the same conditional `state = 'open'` approval write, so
33
+ * only one can win and the loser reports what the winner committed.
34
+ *
35
+ * `atCompanionTurnEnd` marks the one caller that IS the companion's turn end.
36
+ * There the mid-turn marker may not be cleared yet, so it is not consulted;
37
+ * queued work still defers, because an inbox entry means another turn is
38
+ * coming and the wait should outlast it. */
39
+ export declare function completeRequestedSubmit(reviewId: string, opts: {
40
+ atCompanionTurnEnd: boolean;
41
+ }): Promise<ReviewApprovalFinishOutcome | null>;
42
+ /** The companion's turn ended: complete any submit the human left waiting on
43
+ * it. Never throws — a settlement directive must not depend on delivery. */
44
+ export declare function completeCompanionRequestedSubmit(companionNodeId: string): Promise<void>;
18
45
  /** Cancel an open review once; ticket cancellation remains owned by the retained human-ticket finisher. */
19
46
  export declare function cancelReview(reviewId: string, args: {
20
47
  reason?: string;
@@ -8,11 +8,14 @@ import { reviewResultPath } from '../../core/canvas/paths.js';
8
8
  import { snapshotCommentsLocked } from '../../core/review/comments.js';
9
9
  import { readReviewSource } from '../../core/review/document.js';
10
10
  import { signalReviewActivity } from '../../core/review/signal.js';
11
- import { approveReviewLocked, cancelReviewRow, markDelivered, markDeliveryError, markProjected, markProjectionError, requireVisibleReview, } from '../../core/review/store.js';
11
+ import { approveReviewLocked, cancelReviewRow, getReview, getReviewByCompanion, markDelivered, markDeliveryError, markProjected, markProjectionError, requestReviewSubmit, requireVisibleReview, } from '../../core/review/store.js';
12
12
  import { ReviewOperationError } from '../../core/review/types.js';
13
13
  import { transition } from '../../core/runtime/lifecycle.js';
14
14
  import { headlessBrokerHost } from '../../core/runtime/host.js';
15
+ import { emitEvent } from '../../core/events/emit.js';
16
+ import { deliverNodeMessage } from '../messaging/node-message.js';
15
17
  import { cancelHumanTicket, deliverTerminalResult } from '../human/finish.js';
18
+ import { companionHasQueuedWork, companionMidTurn, companionStillWorking } from './companion.js';
16
19
  import { deliverApproval } from './deliver.js';
17
20
  /** One daemon serializes terminal side effects for each immutable review row. */
18
21
  const finishChains = new Map();
@@ -178,9 +181,46 @@ async function finishCanceled(review, winningWrite, args) {
178
181
  retireCompanion(current.companion_node_id, 'canceled');
179
182
  return { record: requireVisibleReview(review.review_id), ...(canceledTicket === undefined ? {} : { ticket_result: canceledTicket }) };
180
183
  }
181
- /** Commit approval once, then independently finish its projection, delivery, and companion retirement ledgers. */
184
+ /** Tell the origin its review was submitted but is finishing behind the
185
+ * companion. Deliberately deferred: the origin is told nothing is needed of
186
+ * it, so waking it to say so would be noise. The approval itself wakes it. */
187
+ async function noteSubmitQueued(review) {
188
+ try {
189
+ await deliverNodeMessage({
190
+ node_id: review.origin_node_id,
191
+ from: 'crtrd',
192
+ mode: 'quiet',
193
+ label: 'human review submitted — result to follow',
194
+ data: { review_id: review.review_id },
195
+ body: `The human submitted their review of \`${review.file}\`; the document is still being edited. `
196
+ + "You'll be notified with the result once the review completes. Nothing is needed from you now.",
197
+ });
198
+ }
199
+ catch (error) {
200
+ emitEvent({ level: 'warn', event: 'review.submit_queued_note.failed', error });
201
+ }
202
+ }
203
+ /** Approve on the human's behalf, waiting first when the companion is still
204
+ * working. Waiting is the only new outcome: an idle companion approves in the
205
+ * same pass it always did, and the wait never freezes the review — comments
206
+ * added during it are in the snapshot, because the snapshot happens at
207
+ * completion and the review is still open. */
182
208
  export async function submitReviewApproval(reviewId) {
183
209
  const initial = requireVisibleReview(reviewId);
210
+ if (initial.state === 'canceled')
211
+ return terminalConflict(initial, 'submit');
212
+ if (initial.state === 'open' && companionStillWorking(initial.companion_node_id)) {
213
+ if (requestReviewSubmit(reviewId, now())) {
214
+ signalReviewActivity();
215
+ await noteSubmitQueued(initial);
216
+ }
217
+ return { outcome: 'awaiting_companion', record: requireVisibleReview(reviewId) };
218
+ }
219
+ return await approveReview(initial);
220
+ }
221
+ /** Commit approval once, then independently finish its projection, delivery, and companion retirement ledgers. */
222
+ async function approveReview(initial) {
223
+ const reviewId = initial.review_id;
184
224
  if (initial.state === 'canceled')
185
225
  return terminalConflict(initial, 'submit');
186
226
  if (initial.state === 'approved') {
@@ -219,6 +259,39 @@ export async function submitReviewApproval(reviewId) {
219
259
  const record = await serializedFinish(reviewId, () => finishApproved(committed.review, committed.result));
220
260
  return { outcome: committed.outcome, record, result: committed.result };
221
261
  }
262
+ /** Complete a submit the human already pressed, once its companion is quiet.
263
+ * Returns null when there is nothing to complete — no pending intent, the
264
+ * review moved on, or the companion is still working. Idempotent: concurrent
265
+ * callers race on the same conditional `state = 'open'` approval write, so
266
+ * only one can win and the loser reports what the winner committed.
267
+ *
268
+ * `atCompanionTurnEnd` marks the one caller that IS the companion's turn end.
269
+ * There the mid-turn marker may not be cleared yet, so it is not consulted;
270
+ * queued work still defers, because an inbox entry means another turn is
271
+ * coming and the wait should outlast it. */
272
+ export async function completeRequestedSubmit(reviewId, opts) {
273
+ const review = getReview(reviewId);
274
+ if (review === null || review.state !== 'open' || review.submit_requested_at === null)
275
+ return null;
276
+ if (companionHasQueuedWork(review.companion_node_id))
277
+ return null;
278
+ if (!opts.atCompanionTurnEnd && companionMidTurn(review.companion_node_id))
279
+ return null;
280
+ return await approveReview(review);
281
+ }
282
+ /** The companion's turn ended: complete any submit the human left waiting on
283
+ * it. Never throws — a settlement directive must not depend on delivery. */
284
+ export async function completeCompanionRequestedSubmit(companionNodeId) {
285
+ try {
286
+ const review = getReviewByCompanion(companionNodeId);
287
+ if (review === null || review.state !== 'open' || review.submit_requested_at === null)
288
+ return;
289
+ await completeRequestedSubmit(review.review_id, { atCompanionTurnEnd: true });
290
+ }
291
+ catch (error) {
292
+ emitEvent({ level: 'error', event: 'review.deferred_submit.failed', error });
293
+ }
294
+ }
222
295
  /** Cancel an open review once; ticket cancellation remains owned by the retained human-ticket finisher. */
223
296
  export async function cancelReview(reviewId, args) {
224
297
  const initial = requireVisibleReview(reviewId);
@@ -20,6 +20,8 @@ export declare const CONNECTION_FAULT_RECOVERY_BODY = "The network connection is
20
20
  export declare const PROVIDER_FAULT_RECOVERY_BODY = "Your previous turn stopped on a provider fault. Continue from where you left off and retry the work that failed.";
21
21
  /** Mark an active persona transition without changing the guidance inside it. */
22
22
  export declare function formatPersonaTransition(guidance: string): string;
23
+ /** Mark a daemon-delivered review approval for compact viewer presentation. */
24
+ export declare function formatReviewApproval(body: string): string;
23
25
  /** Format the stop guard's dynamic structured-output mandate. */
24
26
  export declare function formatStructuredOutputReprompt(schema: string): string;
25
27
  export type ModelFallbackRecoveryReason = 'credential' | 'not-found';
@@ -31,15 +33,12 @@ export interface GeneratedContextMessageLike {
31
33
  content?: unknown;
32
34
  }
33
35
  export interface GeneratedContextPresentation {
34
- kind: 'bearings' | 'situational' | 'revive' | 'context-nudge' | 'inbox' | 'persona-transition' | 'stop-guard' | 'recovery';
36
+ kind: 'bearings' | 'situational' | 'revive' | 'context-nudge' | 'inbox' | 'review-approval' | 'persona-transition' | 'stop-guard' | 'recovery';
35
37
  label: string;
36
38
  summary: string;
37
39
  /** The unmodified, model-visible message content. */
38
40
  body: string;
39
41
  }
40
- /** The display-only disclosure body for a generated context card. The full
41
- * message remains in `body` for the model and transcript bookkeeping; the two
42
- * prompt-sized cards expose the one piece a person needs to inspect. */
43
42
  export declare function generatedContextExpandedBody(presentation: GeneratedContextPresentation): string;
44
43
  /** Plain text from a generated message's string or text-block content. */
45
44
  export declare function generatedContextText(message: GeneratedContextMessageLike): string;
@@ -31,6 +31,8 @@ export const CONNECTION_FAULT_RECOVERY_BODY = 'The network connection is back on
31
31
  export const PROVIDER_FAULT_RECOVERY_BODY = 'Your previous turn stopped on a provider fault. Continue from where you left off and retry the work that failed.';
32
32
  const PERSONA_TRANSITION_OPEN = '<persona-transition>';
33
33
  const PERSONA_TRANSITION_CLOSE = '</persona-transition>';
34
+ const REVIEW_APPROVAL_OPEN = '<crtr-review-approval>';
35
+ const REVIEW_APPROVAL_CLOSE = '</crtr-review-approval>';
34
36
  const MODEL_FALLBACK_RECOVERY_OPEN = '<model-fallback-recovery>';
35
37
  const MODEL_FALLBACK_RECOVERY_CLOSE = '</model-fallback-recovery>';
36
38
  const STRUCTURED_OUTPUT_REPROMPT_PREFIX = 'You must call the `submit` tool with a result matching the required schema before you can stop. You cannot finish or go dormant any other way while this request is pending.\n\nRequired schema:\n\n```json\n';
@@ -39,6 +41,10 @@ const STRUCTURED_OUTPUT_REPROMPT_SUFFIX = '\n```';
39
41
  export function formatPersonaTransition(guidance) {
40
42
  return `${PERSONA_TRANSITION_OPEN}\n${guidance}\n${PERSONA_TRANSITION_CLOSE}`;
41
43
  }
44
+ /** Mark a daemon-delivered review approval for compact viewer presentation. */
45
+ export function formatReviewApproval(body) {
46
+ return `${REVIEW_APPROVAL_OPEN}\n${body}\n${REVIEW_APPROVAL_CLOSE}`;
47
+ }
42
48
  /** Format the stop guard's dynamic structured-output mandate. */
43
49
  export function formatStructuredOutputReprompt(schema) {
44
50
  return `${STRUCTURED_OUTPUT_REPROMPT_PREFIX}${schema}${STRUCTURED_OUTPUT_REPROMPT_SUFFIX}`;
@@ -55,14 +61,18 @@ const YIELD_MESSAGE_BLOCK = /<yield-message>([\s\S]*?)<\/yield-message>/;
55
61
  /** The display-only disclosure body for a generated context card. The full
56
62
  * message remains in `body` for the model and transcript bookkeeping; the two
57
63
  * prompt-sized cards expose the one piece a person needs to inspect. */
64
+ function unmarkReviewApprovals(body) {
65
+ return body.replaceAll(`${REVIEW_APPROVAL_OPEN}\n`, '').replaceAll(`\n${REVIEW_APPROVAL_CLOSE}`, '');
66
+ }
58
67
  export function generatedContextExpandedBody(presentation) {
68
+ let body = presentation.body;
59
69
  if (presentation.label === 'crtr context') {
60
- return AUTO_LOADED_CONTEXT_BLOCK.exec(presentation.body)?.[1]?.trim() ?? '';
70
+ body = AUTO_LOADED_CONTEXT_BLOCK.exec(body)?.[1]?.trim() ?? '';
61
71
  }
62
- if (presentation.kind === 'revive') {
63
- return YIELD_MESSAGE_BLOCK.exec(presentation.body)?.[1]?.trim() ?? '';
72
+ else if (presentation.kind === 'revive') {
73
+ body = YIELD_MESSAGE_BLOCK.exec(body)?.[1]?.trim() ?? '';
64
74
  }
65
- return presentation.body;
75
+ return unmarkReviewApprovals(body);
66
76
  }
67
77
  /** Plain text from a generated message's string or text-block content. */
68
78
  export function generatedContextText(message) {
@@ -88,7 +98,9 @@ function nudgeSummary(body) {
88
98
  }
89
99
  // Stable header emitted by core/feed/inbox.ts coalesce(). Human-authored inbox
90
100
  // messages are deliberately delivered verbatim and never carry this header.
91
- const INBOX_HEADER_RE = /^From ([a-z0-9]+(?:-[a-z0-9]+)+|system|human) — (\d+) update/m;
101
+ // Senders are node ids, the two reserved words, or the daemon itself (crtrd —
102
+ // review notes and other daemon-originated node messages).
103
+ const INBOX_HEADER_RE = /^From ([a-z0-9]+(?:-[a-z0-9]+)+|system|human|crtrd) — (\d+) update/m;
92
104
  export function isInboxDigest(text) {
93
105
  return INBOX_HEADER_RE.test(text);
94
106
  }
@@ -96,7 +108,7 @@ export function extractInboxSender(text) {
96
108
  return INBOX_HEADER_RE.exec(text)?.[1] ?? null;
97
109
  }
98
110
  function inboxSummary(body) {
99
- const headers = [...body.matchAll(/^From ([a-z0-9]+(?:-[a-z0-9]+)+|system|human) — (\d+) update/gm)];
111
+ const headers = [...body.matchAll(/^From ([a-z0-9]+(?:-[a-z0-9]+)+|system|human|crtrd) — (\d+) update/gm)];
100
112
  if (headers.length === 0)
101
113
  return 'node update';
102
114
  const updates = headers.reduce((total, match) => total + Number(match[2] ?? 0), 0);
@@ -109,6 +121,9 @@ function inboxSummary(body) {
109
121
  function isPersonaTransition(body) {
110
122
  return body.startsWith(`${PERSONA_TRANSITION_OPEN}\n`) && body.endsWith(`\n${PERSONA_TRANSITION_CLOSE}`);
111
123
  }
124
+ function isReviewApproval(body) {
125
+ return body.startsWith(`${REVIEW_APPROVAL_OPEN}\n`) && body.endsWith(`\n${REVIEW_APPROVAL_CLOSE}`);
126
+ }
112
127
  function isStructuredOutputReprompt(body) {
113
128
  if (!body.startsWith(STRUCTURED_OUTPUT_REPROMPT_PREFIX) || !body.endsWith(STRUCTURED_OUTPUT_REPROMPT_SUFFIX)) {
114
129
  return false;
@@ -189,6 +204,9 @@ export function generatedContextPresentation(message) {
189
204
  if (isInboxDigest(body)) {
190
205
  return { kind: 'inbox', label: 'crtr inbox', summary: inboxSummary(body), body };
191
206
  }
207
+ if (isReviewApproval(body)) {
208
+ return { kind: 'review-approval', label: 'crtr review', summary: 'human review approved', body };
209
+ }
192
210
  if (isPersonaTransition(body)) {
193
211
  return {
194
212
  kind: 'persona-transition',
package/dist/types.d.ts CHANGED
@@ -263,7 +263,6 @@ export interface ScopeState {
263
263
  last_updated?: string;
264
264
  }>;
265
265
  last_self_check?: string;
266
- bootstrap_done?: boolean;
267
266
  /** The name of the remote canvas target `crtr canvas use` last selected
268
267
  * (see `RemoteCanvasConfig`), or `null`/omitted for local. Read by
269
268
  * `resolveCanvasSource` (`core/canvas/source.ts`) as the durable fallback
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.179",
3
+ "version": "0.3.181",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.179",
3
+ "version": "0.3.181",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.179",
9
+ "version": "0.3.181",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {