@plurnk/plurnk-service 0.44.0 → 0.45.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.
- package/.env.example +27 -23
- package/README.md +37 -18
- package/SPEC.md +31 -7
- package/dist/core/ChannelWrite.d.ts +4 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +9 -0
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +69 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +108 -71
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +291 -0
- package/dist/core/ExecutorRegistry.d.ts +4 -2
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +14 -4
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +1 -0
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +6 -0
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +8 -1
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +50 -0
- package/dist/core/plugin-attribution.d.ts +5 -0
- package/dist/core/plugin-attribution.d.ts.map +1 -0
- package/dist/core/plugin-attribution.js +39 -0
- package/dist/core/plugin-attribution.js.map +1 -0
- package/dist/core/run-ops.sql +16 -0
- package/dist/core/session-settings.d.ts +1 -0
- package/dist/core/session-settings.d.ts.map +1 -1
- package/dist/core/session-settings.js +2 -1
- package/dist/core/session-settings.js.map +1 -1
- package/dist/schemes/Exec.d.ts +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +17 -5
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +27 -3
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.sql +37 -0
- package/dist/schemes/_entry-crud.sql +88 -0
- package/dist/schemes/_entry-find.sql +31 -0
- package/dist/schemes/_entry-graph.sql +60 -0
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +4 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +20 -0
- package/dist/schemes/_entry-semantic.sql +64 -0
- package/dist/schemes/exec-env.js +1 -1
- package/dist/schemes/exec-env.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +103 -37
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.sql +10 -0
- package/dist/server/drain.sql +82 -0
- package/dist/server/dsl.d.ts.map +1 -1
- package/dist/server/dsl.js +11 -4
- package/dist/server/dsl.js.map +1 -1
- package/dist/server/envelope.sql +75 -0
- package/dist/server/logEntry.sql +10 -0
- package/dist/server/methods/_dispatchAsClient.d.ts.map +1 -1
- package/dist/server/methods/_dispatchAsClient.js +11 -6
- package/dist/server/methods/_dispatchAsClient.js.map +1 -1
- package/dist/server/methods/entry_read.sql +21 -0
- package/dist/server/methods/log_read.sql +11 -0
- package/dist/server/methods/loop_run.sql +9 -0
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +10 -3
- package/dist/server/methods/session_create.js.map +1 -1
- package/dist/service.d.ts +6 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +221 -0
- package/dist/service.js.map +1 -0
- package/package.json +28 -14
- package/bin/plurnk-service.ts +0 -176
- package/dist/core/ProviderRegistry.d.ts +0 -42
- package/dist/core/ProviderRegistry.d.ts.map +0 -1
- package/dist/core/ProviderRegistry.js +0 -72
- package/dist/core/ProviderRegistry.js.map +0 -1
- package/dist/core/line-marker.d.ts +0 -23
- package/dist/core/line-marker.d.ts.map +0 -1
- package/dist/core/line-marker.js +0 -321
- package/dist/core/line-marker.js.map +0 -1
- package/dist/core/matcher.d.ts +0 -12
- package/dist/core/matcher.d.ts.map +0 -1
- package/dist/core/matcher.js +0 -72
- package/dist/core/matcher.js.map +0 -1
- package/dist/core/mimetype-binary.d.ts +0 -6
- package/dist/core/mimetype-binary.d.ts.map +0 -1
- package/dist/core/mimetype-binary.js +0 -82
- package/dist/core/mimetype-binary.js.map +0 -1
- package/dist/core/path-mimetype.d.ts +0 -3
- package/dist/core/path-mimetype.d.ts.map +0 -1
- package/dist/core/path-mimetype.js +0 -47
- package/dist/core/path-mimetype.js.map +0 -1
- package/dist/core/plugin-trust.d.ts +0 -4
- package/dist/core/plugin-trust.d.ts.map +0 -1
- package/dist/core/plugin-trust.js +0 -23
- package/dist/core/plugin-trust.js.map +0 -1
- package/dist/providers/Mock.d.ts +0 -43
- package/dist/providers/Mock.d.ts.map +0 -1
- package/dist/providers/Mock.js +0 -36
- package/dist/providers/Mock.js.map +0 -1
- package/dist/server/methods/op_hide.d.ts +0 -5
- package/dist/server/methods/op_hide.d.ts.map +0 -1
- package/dist/server/methods/op_hide.js +0 -24
- package/dist/server/methods/op_hide.js.map +0 -1
- package/dist/server/methods/op_show.d.ts +0 -5
- package/dist/server/methods/op_show.d.ts.map +0 -1
- package/dist/server/methods/op_show.js +0 -24
- package/dist/server/methods/op_show.js.map +0 -1
- package/dist/server/methods/session_set_persona.d.ts +0 -5
- package/dist/server/methods/session_set_persona.d.ts.map +0 -1
- package/dist/server/methods/session_set_persona.js +0 -29
- package/dist/server/methods/session_set_persona.js.map +0 -1
package/dist/core/Engine.js
CHANGED
|
@@ -71,6 +71,7 @@ const readManifestItems = () => {
|
|
|
71
71
|
return null;
|
|
72
72
|
return normalizeManifestItems(Number.parseInt(raw, 10));
|
|
73
73
|
};
|
|
74
|
+
import { ProviderError } from "@plurnk/plurnk-providers";
|
|
74
75
|
// Resolution timeout — proposed entries auto-cancel if nothing arrives
|
|
75
76
|
// within this window. SPEC.md §engine-rails (proposal lifecycle) + §methods (loop.resolve).
|
|
76
77
|
const PROPOSAL_TIMEOUT_DEFAULT_MS = 300000;
|
|
@@ -599,7 +600,7 @@ class Engine {
|
|
|
599
600
|
// it, not a 404; same plurnk-origin foist as the operator docs.
|
|
600
601
|
if (seq === 1) {
|
|
601
602
|
// #231 — a session's client-chosen manifestItems REPLACES the env default outright.
|
|
602
|
-
const { manifestItems: sessionMI } = await SessionSettings.read(this.#db, sessionId);
|
|
603
|
+
const { manifestItems: sessionMI, autoReadAgents } = await SessionSettings.read(this.#db, sessionId);
|
|
603
604
|
const manifestItems = sessionMI !== null ? normalizeManifestItems(sessionMI) : readManifestItems();
|
|
604
605
|
if (manifestItems !== null) {
|
|
605
606
|
const manifestRead = {
|
|
@@ -618,6 +619,30 @@ class Engine {
|
|
|
618
619
|
});
|
|
619
620
|
nextActionIndex++;
|
|
620
621
|
}
|
|
622
|
+
// #250 — auto-READ the project's AGENTS.md scratchpad into THIS first model turn
|
|
623
|
+
// when the session opted in AND it's a member. The client picks it (gitignored by
|
|
624
|
+
// convention → not a git member); the engine READs it here so its body is part of
|
|
625
|
+
// turn-1's log — a normal file:/// member READ (the model sees only the READ; it
|
|
626
|
+
// stays read-write, so the model edits the scratchpad back as it evolves).
|
|
627
|
+
if (autoReadAgents === true) {
|
|
628
|
+
const agentsMember = await this.#db.crud_get_member_sig.get({ session_id: sessionId, scheme: null, pathname: "/AGENTS.md" });
|
|
629
|
+
if (agentsMember !== undefined) {
|
|
630
|
+
const agentsRead = {
|
|
631
|
+
op: "READ", suffix: "", signal: null, lineMarker: null,
|
|
632
|
+
target: {
|
|
633
|
+
kind: "url", raw: "file:///AGENTS.md", scheme: "file",
|
|
634
|
+
username: null, password: null, hostname: null, port: null,
|
|
635
|
+
pathname: "/AGENTS.md", params: {}, fragment: null,
|
|
636
|
+
},
|
|
637
|
+
body: null, position: { line: 1, column: 1 },
|
|
638
|
+
};
|
|
639
|
+
await this.dispatch({
|
|
640
|
+
statement: agentsRead, sessionId, runId, loopId, turnId,
|
|
641
|
+
sequence: nextActionIndex, origin: "plurnk", onDispatch,
|
|
642
|
+
});
|
|
643
|
+
nextActionIndex++;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
621
646
|
}
|
|
622
647
|
// §env-delta — pre-seed environment deltas (changes since this run last
|
|
623
648
|
// reconciled) as system EDIT rows, before the packet composes; advance
|
|
@@ -661,7 +686,31 @@ class Engine {
|
|
|
661
686
|
// decode at the free window so a runaway can't reach the context wall.
|
|
662
687
|
const genCeiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling); // provider.contextSize, the immutable identity, read by the budget — §provider-surface-identity
|
|
663
688
|
const maxTokens = genCeiling === null ? undefined : Math.max(1, genCeiling - requestPacket.tokens);
|
|
664
|
-
|
|
689
|
+
let response;
|
|
690
|
+
// #249 — plugin attribution tags onto the per-turn generate() wire. Value is the
|
|
691
|
+
// active-plugin set (placeholder); real per-turn grounding is deferred.
|
|
692
|
+
const attributions = [...new Set([...this.#schemes.attributions(), ...(this.#executors?.attributions() ?? [])])].toSorted();
|
|
693
|
+
try {
|
|
694
|
+
response = await provider.generate({ messages: modelMessages, runId: String(runId), signal, grammar: await this.#grammarConstraint(), maxTokens, attributions: attributions.length > 0 ? attributions : undefined }); // §provider-surface-generate §provider-guarantees-single-call §provider-guarantees-signal-wired §attribution-plurnk-namespace-reserved
|
|
695
|
+
}
|
|
696
|
+
catch (err) {
|
|
697
|
+
// #256 — grammar_unenforced is the one provider error the MODEL can recover from:
|
|
698
|
+
// the backend didn't constrain the GBNF, so this turn's output was rejected, but a
|
|
699
|
+
// conforming emission next turn is accepted. Surface it as telemetry (the model's
|
|
700
|
+
// next packet shows it) and fall through as an empty no-op turn, so the strike rail
|
|
701
|
+
// gives it maxStrikes chances before terminating — unlike infra errors (rate_limit,
|
|
702
|
+
// network_failure, unauthorized), which propagate and end the loop.
|
|
703
|
+
if (err instanceof ProviderError && err.kind === "grammar_unenforced") {
|
|
704
|
+
this.#pushTelemetry(sessionId, loopId, { source: "provider", kind: "grammar_unenforced", message: err.message });
|
|
705
|
+
response = {
|
|
706
|
+
assistant: { content: "", reasoning: null, usage: { prompt: requestPacket.tokens, completion: 0, reasoning: 0, cached: 0, total: requestPacket.tokens }, finishReason: null, model: provider.model },
|
|
707
|
+
assistantRaw: null,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
throw err;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
665
714
|
// Engine splits wire-level response: emission (content, reasoning,
|
|
666
715
|
// parsed ops) → packet.assistant per Packet.json §assistant;
|
|
667
716
|
// call-metadata (usage, finishReason, model) → Turn columns per
|
|
@@ -691,10 +740,10 @@ class Engine {
|
|
|
691
740
|
});
|
|
692
741
|
}
|
|
693
742
|
const opsCount = packetAssistant.ops.length;
|
|
694
|
-
//
|
|
695
|
-
//
|
|
743
|
+
// PLAN (reasoning) and informational SEND[103] are no-ops, not actions: both are
|
|
744
|
+
// excluded from the real-op count so a PLAN-only or prose-only turn still strikes
|
|
696
745
|
// as no-ops, and the terminal scan ignores 1xx so they never set turnStatus.
|
|
697
|
-
const realOpsCount = packetAssistant.ops.filter((op) => !(op.op === "SEND" && op.signal === 103 && op.target === null)).length;
|
|
746
|
+
const realOpsCount = packetAssistant.ops.filter((op) => op.op !== "PLAN" && !(op.op === "SEND" && op.signal === 103 && op.target === null)).length;
|
|
698
747
|
const sendOp = packetAssistant.ops.findLast((op) => op.op === "SEND" && typeof op.signal === "number" && op.signal >= 200);
|
|
699
748
|
// Rail #41 (revised): the per-turn requirement is "emit at least one
|
|
700
749
|
// op," not "emit a terminal SEND." SEND is purely a signal verb; many
|
|
@@ -728,7 +777,14 @@ class Engine {
|
|
|
728
777
|
// its emission was truncated.
|
|
729
778
|
// #232 — a session's maxCommands is a tighten-only ceiling: min() the env ceiling.
|
|
730
779
|
const maxCommands = Math.min(readMaxCommands(), (await SessionSettings.read(this.#db, sessionId)).maxCommands ?? Number.POSITIVE_INFINITY);
|
|
731
|
-
|
|
780
|
+
// PLAN (reasoning) and a terminal SEND (signal ≥ 200, the conclusion) are not
|
|
781
|
+
// actions — they always dispatch and never count against the cap. maxCommands
|
|
782
|
+
// bounds real actions only; maxCommands:0 still admits a plan and a conclusion
|
|
783
|
+
// (the PLAN/SEND ops, zero actions), which is its only coherent meaning.
|
|
784
|
+
let realCommands = 0;
|
|
785
|
+
const opsToDispatch = packetAssistant.ops.filter((op) => op.op === "PLAN"
|
|
786
|
+
|| (op.op === "SEND" && typeof op.signal === "number" && op.signal >= 200)
|
|
787
|
+
|| realCommands++ < maxCommands);
|
|
732
788
|
const droppedCount = opsCount - opsToDispatch.length;
|
|
733
789
|
const statuses = [];
|
|
734
790
|
for (const [i, statement] of opsToDispatch.entries()) {
|
|
@@ -772,46 +828,26 @@ class Engine {
|
|
|
772
828
|
const { assistant } = response;
|
|
773
829
|
const preParsedOps = assistant.ops;
|
|
774
830
|
const ops = [];
|
|
775
|
-
//
|
|
776
|
-
//
|
|
777
|
-
//
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (op.op !== "PLAN")
|
|
781
|
-
return false;
|
|
782
|
-
const raw = (op.body ?? "").trim();
|
|
783
|
-
if (raw.length > 0)
|
|
784
|
-
planFragments.push(raw);
|
|
785
|
-
return true;
|
|
786
|
-
};
|
|
831
|
+
// PLAN is an ordinary op — emitted by the model, dispatched, and passed to the
|
|
832
|
+
// client as a log entry. No special hoisting into the reasoning field (that
|
|
833
|
+
// legacy paradigm is abandoned). Interstitial free text is DROPPED — the prior
|
|
834
|
+
// #free-text-capture synthesis of SEND[103] log ops was retired as tech debt
|
|
835
|
+
// (grammar 0.70 forbids free text between ops, so a prose-only turn strikes 422).
|
|
787
836
|
// Full PlurnkParseError context (line/column/source) is preserved
|
|
788
837
|
// here so runTurn can build TelemetryEvent envelopes per the
|
|
789
838
|
// grammar 0.17.0 protocol — model needs position info to locate
|
|
790
839
|
// its own offending content on the next turn.
|
|
791
840
|
const parseErrors = [];
|
|
792
841
|
if (preParsedOps !== undefined) {
|
|
793
|
-
|
|
794
|
-
if (!hoistPlan(op))
|
|
795
|
-
ops.push(op);
|
|
842
|
+
ops.push(...preParsedOps);
|
|
796
843
|
}
|
|
797
844
|
else {
|
|
798
845
|
const parsed = PlurnkParser.parse(assistant.content);
|
|
799
846
|
for (const item of parsed.items) {
|
|
800
847
|
if (item.kind === "statement") {
|
|
801
|
-
|
|
802
|
-
ops.push(item.statement);
|
|
803
|
-
}
|
|
804
|
-
else if (item.kind === "text") {
|
|
805
|
-
// #free-text-capture: interstitial prose → an informational
|
|
806
|
-
// SEND[103] broadcast log op, interleaved in emission order.
|
|
807
|
-
const trimmed = item.text.trim();
|
|
808
|
-
if (trimmed.length > 0)
|
|
809
|
-
ops.push({
|
|
810
|
-
op: "SEND", suffix: "", signal: 103, target: null,
|
|
811
|
-
lineMarker: null, body: { raw: trimmed, json: null },
|
|
812
|
-
position: item.position ?? { line: 1, column: 1 },
|
|
813
|
-
});
|
|
848
|
+
ops.push(item.statement);
|
|
814
849
|
}
|
|
850
|
+
// Free text (kind "text") is dropped — #free-text-capture retired (above).
|
|
815
851
|
else if (item.kind === "error") {
|
|
816
852
|
const err = item.error;
|
|
817
853
|
if (err instanceof PlurnkParseError) {
|
|
@@ -834,10 +870,7 @@ class Engine {
|
|
|
834
870
|
parseErrors.push({ message: tail.reason, line: tail.from.line, column: tail.from.column, source: "grammar" });
|
|
835
871
|
}
|
|
836
872
|
}
|
|
837
|
-
const
|
|
838
|
-
const planReasoning = planFragments.join("\n\n");
|
|
839
|
-
const reasoningParts = [wireReasoning, planReasoning].filter((s) => s.length > 0);
|
|
840
|
-
const reasoning = reasoningParts.length > 0 ? reasoningParts.join("\n\n") : null;
|
|
873
|
+
const reasoning = assistant.reasoning ?? null;
|
|
841
874
|
return {
|
|
842
875
|
packetAssistant: { content: assistant.content, ops, reasoning },
|
|
843
876
|
callMetadata: { usage: assistant.usage, finishReason: assistant.finishReason, model: assistant.model },
|
|
@@ -850,9 +883,10 @@ class Engine {
|
|
|
850
883
|
// the stored packet and the wire payload share one source of truth.
|
|
851
884
|
async #buildRequestPacket({ initialMessages, requirements, runId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
852
885
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
853
|
-
// plurnk.md (grammar/dialects
|
|
854
|
-
// scheme catalogue is
|
|
855
|
-
//
|
|
886
|
+
// plurnk.md (grammar/dialects) ONLY — the definition is the hot-path grammar.
|
|
887
|
+
// The scheme catalogue is its own `schemes` section below tools (§schemes-directory),
|
|
888
|
+
// NOT appended here: grammar 0.49+ is scheme-agnostic, so the service advertises
|
|
889
|
+
// the scheme set at packet-time (grammar#239 item 7) via SchemeRegistry.teach().
|
|
856
890
|
const system_definition = byRole("system");
|
|
857
891
|
// the prompt section sources from the loop's most recent prompt entry first
|
|
858
892
|
// (plurnk:///prompt/<loop_id>/<N> for the highest N written to date).
|
|
@@ -869,11 +903,11 @@ class Engine {
|
|
|
869
903
|
// requirements). Read Paths.defaultRequirements (PLURNK_REQUIREMENTS env →
|
|
870
904
|
// requirements.md) fresh each build so edits take effect; a non-empty param wins.
|
|
871
905
|
const baseRequirements = requirements.length > 0 ? requirements : await readFile(Paths.defaultRequirements, "utf8");
|
|
872
|
-
// The op syntax leads the requirements
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
|
|
876
|
-
const requirementsText = `Syntax: <<OPsuffix[signal]?(target)?<Line/Result>?:body?:OPsuffix\n\n${
|
|
906
|
+
// The op syntax leads the requirements. PLAN is mandated unconditionally by
|
|
907
|
+
// plurnk.md §Imperatives (grammar 0.70 requires every turn to lead with PLAN),
|
|
908
|
+
// so the service injects no separate plan directive here — the former PLURNK_PLAN
|
|
909
|
+
// gating is retired (PLURNK_PLAN is no longer a flag).
|
|
910
|
+
const requirementsText = `Syntax: <<OPsuffix[signal]?(target)?<Line/Result>?:body?:OPsuffix\n\n${baseRequirements}`;
|
|
877
911
|
const log = await this.#buildLog(runId);
|
|
878
912
|
const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
|
|
879
913
|
const countTokens = (t) => provider.countTokens(t); // §provider-surface-counttokens
|
|
@@ -889,8 +923,8 @@ class Engine {
|
|
|
889
923
|
const budgetReadout = this.#renderBudget(PacketWire.measureLogBudget(log, countTokens), ceiling);
|
|
890
924
|
// The default packet: an ordered list of sections, each addressable state
|
|
891
925
|
// (§packet-construction). `slot` is the prompt-cache boundary; the STATIC
|
|
892
|
-
// sections (definition, tools
|
|
893
|
-
//
|
|
926
|
+
// sections (definition, tools) lead the system slot so they form the cached
|
|
927
|
+
// prefix, with the dynamic log after. In the user slot, requirements renders
|
|
894
928
|
// last (the contract closest to the assistant turn); budget/errors/git are
|
|
895
929
|
// peer sections (unbundled). The budget section carries its {{tokensFree}}
|
|
896
930
|
// placeholders here; they resolve below once the assembled total is known.
|
|
@@ -961,9 +995,7 @@ class Engine {
|
|
|
961
995
|
// The # Plurnk System Tools capability sheet (SPEC §tools). A hook: each enabled
|
|
962
996
|
// capability contributes one line, rendered above Requirements so the model sees what
|
|
963
997
|
// it can do before the rules. Each available executor tag contributes its self-documenting
|
|
964
|
-
// example (plurnk-execs#7), retiring the blind EXEC.
|
|
965
|
-
// is a hard requirement gated by PLURNK_PLAN (§requirements-plan-gated), so it joins the
|
|
966
|
-
// rules list, not this optional sheet.
|
|
998
|
+
// example (plurnk-execs#7), retiring the blind EXEC.
|
|
967
999
|
// The capability sheet — the live tool surface (wired executor tags). §tools-capability-sheet
|
|
968
1000
|
#collectTools() {
|
|
969
1001
|
const tools = [];
|
|
@@ -1269,13 +1301,13 @@ class Engine {
|
|
|
1269
1301
|
const logEntryId = await this.#writeLog({ statement, result, runId, loopId, turnId, sequence, origin });
|
|
1270
1302
|
onDispatch?.(logEntryId);
|
|
1271
1303
|
// Proposal lifecycle (SPEC.md §engine-rails + §methods loop.resolve; §proposal-202-pauses). When a
|
|
1272
|
-
//
|
|
1273
|
-
//
|
|
1274
|
-
//
|
|
1275
|
-
//
|
|
1276
|
-
//
|
|
1277
|
-
// a pending state.
|
|
1278
|
-
if (result
|
|
1304
|
+
// side-effecting op returns status 202 (a broadcast SEND[202] park is model
|
|
1305
|
+
// speech, not a proposal — #isProposal, #255), the entry is written
|
|
1306
|
+
// state='proposed'; dispatch then PAUSES on a per-entry waiter until
|
|
1307
|
+
// resolution arrives via Engine.resolveProposal (from the loop/resolve RPC,
|
|
1308
|
+
// YOLO listener, or timeout). The post-resolution status replaces 202 in the
|
|
1309
|
+
// result the caller sees, so runTurn never branches on a pending state.
|
|
1310
|
+
if (_a.#isProposal(statement, result)) {
|
|
1279
1311
|
// Effect-gated auto-run (read/pure runtimes, plurnk-service#182):
|
|
1280
1312
|
// no human gate, no loop/proposal notification. Accept + apply
|
|
1281
1313
|
// in-process; the model sees the outcome directly, never a review.
|
|
@@ -1749,12 +1781,9 @@ class Engine {
|
|
|
1749
1781
|
const delResult = await handler.deleteEntry(pathnameFromPath(path), ctx);
|
|
1750
1782
|
return { status: delResult.status };
|
|
1751
1783
|
}
|
|
1752
|
-
// PLAN — the model's
|
|
1753
|
-
//
|
|
1754
|
-
// (
|
|
1755
|
-
// handler is the op's direct-dispatch semantics (exercised by the dispatch unit
|
|
1756
|
-
// test): a pure no-op (PLAN ∉ MUTATING_OPS) whose body would serialize into the log
|
|
1757
|
-
// row's tx, no runtime effect.
|
|
1784
|
+
// PLAN — the model's reasoning op (the 11th op). An ordinary op: dispatched like any
|
|
1785
|
+
// other, logged, and broadcast to the client as a log entry — but a pure no-op for
|
|
1786
|
+
// state (PLAN ∉ MUTATING_OPS); its body serializes into the log row's tx, no effect.
|
|
1758
1787
|
#handlePlan(statement) {
|
|
1759
1788
|
if (statement.op !== "PLAN")
|
|
1760
1789
|
throw new Error("unreachable");
|
|
@@ -1882,18 +1911,26 @@ class Engine {
|
|
|
1882
1911
|
return path.scheme === "https" ? "http" : path.scheme;
|
|
1883
1912
|
return "file"; // local (bare) → file
|
|
1884
1913
|
}
|
|
1914
|
+
// A status-202 result is a reviewable PROPOSAL (a side-effecting op — EDIT/EXEC/
|
|
1915
|
+
// directed write — paused for client resolution) UNLESS it is a broadcast SEND.
|
|
1916
|
+
// A broadcast SEND[202] is the model PARKING the loop (a terminal disposition,
|
|
1917
|
+
// plurnk.md), never a side-effect — #255: gating the propose/await path on the
|
|
1918
|
+
// bare 202 surfaced model speech as a loop/proposal and froze clients. The 202
|
|
1919
|
+
// is overloaded (proposal-pause vs parked-terminal); the op disambiguates it.
|
|
1920
|
+
static #isProposal(statement, result) {
|
|
1921
|
+
return result.status === 202 && !(statement.op === "SEND" && statement.target === null);
|
|
1922
|
+
}
|
|
1885
1923
|
async #writeLog({ statement, result, runId, loopId, turnId, sequence, origin, }) {
|
|
1886
1924
|
const target = this.#extractTarget(statement.target);
|
|
1887
1925
|
const lineMarkerJson = "lineMarker" in statement && statement.lineMarker !== null
|
|
1888
1926
|
? JSON.stringify(statement.lineMarker)
|
|
1889
1927
|
: null;
|
|
1890
|
-
//
|
|
1891
|
-
//
|
|
1892
|
-
//
|
|
1893
|
-
//
|
|
1894
|
-
//
|
|
1895
|
-
|
|
1896
|
-
const isProposed = result.status === 202;
|
|
1928
|
+
// A proposal (status 202 from a side-effecting op) is written to the log in
|
|
1929
|
+
// state='proposed' until the proposal lifecycle resolves it; attrs holds the
|
|
1930
|
+
// scheme-supplied payload (file diff, exec command, etc.) the client renders
|
|
1931
|
+
// for review and the scheme consumes on accept. A broadcast SEND[202] is a
|
|
1932
|
+
// parked-terminal, NOT a proposal (#isProposal / #255) → state='resolved'.
|
|
1933
|
+
const isProposed = _a.#isProposal(statement, result);
|
|
1897
1934
|
let attrsObj = (result.attrs !== undefined && result.attrs !== null)
|
|
1898
1935
|
? { ...result.attrs }
|
|
1899
1936
|
: {};
|