@tinoy/pi-canon 0.6.0 → 0.6.2
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/README.md +1 -1
- package/index.ts +40 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ pi install npm:@tinoy/pi-canon
|
|
|
24
24
|
- **Commands** — `/canon` (list, add, remove, edit, category management) and `/canon-dump`.
|
|
25
25
|
- **Injection** — the block is appended at `before_agent_start` and re-normalized on every provider request, so a run started by an injected message carries the same bytes as an interactive prompt.
|
|
26
26
|
- **Category headings** — a category sub-heading inside a scope group prints the store id after the word `category` (`#### Behavioural Preferences [category 1cg5lr]`), which is the same id a `canon_add` refusal lists; the word keeps it from reading as an entry handle, which the block renders as `[1i15c2]`. Uncategorized entries carry no id, and a scope group holding a single category stays flat with no sub-heading.
|
|
27
|
-
- **Peer notices** — entry changes are published over the `ipc` transport's bus (namespace `canon`); receivers match the entry scope against their own model and audience.
|
|
27
|
+
- **Peer notices** — entry changes are published over the `ipc` transport's bus (namespace `canon`); receivers match the entry scope against their own model and audience. A matching notice steers into a run that is already in flight, landing at the next tool boundary, and queues for the next turn when the session is idle, so it never wakes anything.
|
|
28
28
|
- **Tail sections** — another extension contributes prompt text through the `canon:section` event; its ids are published on `canon:sections`.
|
|
29
29
|
|
|
30
30
|
## Exports
|
package/index.ts
CHANGED
|
@@ -610,6 +610,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
610
610
|
pi.events.emit("canon:sections", { ids: registeredSectionIds() });
|
|
611
611
|
});
|
|
612
612
|
|
|
613
|
+
// A notice steered into a live run lands at the next tool boundary, before the next
|
|
614
|
+
// LLM call; the passive queue drains only when a new turn starts, so a notice emitted
|
|
615
|
+
// mid-run would otherwise wait for the operator's next prompt. The passive route keeps
|
|
616
|
+
// the no-wake guarantee where it is needed — an idle session — and steer is used only
|
|
617
|
+
// while a run is in flight, where it cannot wake anything. A steer queued as the run
|
|
618
|
+
// settles can carry it one more LLM call; that is the price of a notice landing with the
|
|
619
|
+
// trajectory rather than after it.
|
|
620
|
+
let runInFlight = false;
|
|
621
|
+
|
|
622
|
+
pi.on("agent_start", () => {
|
|
623
|
+
runInFlight = true;
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
pi.on("agent_settled", () => {
|
|
627
|
+
runInFlight = false;
|
|
628
|
+
});
|
|
629
|
+
|
|
613
630
|
// keep the cached model fresh for notification filtering
|
|
614
631
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
615
632
|
hookFiredSinceLastRequest = true;
|
|
@@ -764,7 +781,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
764
781
|
const payload = event.payload as Partial<CanonNotice> | null;
|
|
765
782
|
if (!payload || payload.type !== "canon" || !payload.scope || !payload.id)
|
|
766
783
|
return;
|
|
767
|
-
if (!matches(payload.scope, currentModel(), audiences))
|
|
784
|
+
if (!matches(payload.scope, currentModel(), audiences)) {
|
|
785
|
+
// Withholding is otherwise unattributable: nothing is delivered and nothing is
|
|
786
|
+
// sent back, so a scope mismatch leaves no trace on either side. The decision is
|
|
787
|
+
// unchanged here — only the record of it is added.
|
|
788
|
+
hookLog("canon", "notice-withheld", {
|
|
789
|
+
namespace: NAMESPACE,
|
|
790
|
+
id: payload.id,
|
|
791
|
+
op: payload.op,
|
|
792
|
+
entryModel: payload.scope.model,
|
|
793
|
+
entryAudience: payload.scope.audience,
|
|
794
|
+
sessionModel: currentModel(),
|
|
795
|
+
sessionAudience: sessionLabel(audiences),
|
|
796
|
+
mismatch:
|
|
797
|
+
payload.scope.audience !== "all" && !audiences.has(payload.scope.audience)
|
|
798
|
+
? "audience"
|
|
799
|
+
: "model",
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
768
803
|
const sender = payload.sender?.trim() || event.fromSessionId.slice(0, 8);
|
|
769
804
|
const verb =
|
|
770
805
|
payload.op === "remove" || payload.op === "category_remove"
|
|
@@ -779,9 +814,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
779
814
|
display: true,
|
|
780
815
|
details: { fromSessionId: event.fromSessionId, canonId: payload.id },
|
|
781
816
|
},
|
|
782
|
-
//
|
|
783
|
-
// prompt;
|
|
784
|
-
|
|
817
|
+
// An idle session takes the passive queue, because steer would wake it like a
|
|
818
|
+
// prompt; a run already in flight takes steer, which lands at the next tool
|
|
819
|
+
// boundary instead of waiting for a turn that has not started.
|
|
820
|
+
{ deliverAs: runInFlight ? "steer" : "nextTurn" },
|
|
785
821
|
);
|
|
786
822
|
},
|
|
787
823
|
onReady(readyChannel: CanonChannel): void {
|