@plurnk/plurnk-service 3.0.0 → 3.1.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.defaults +16 -4
- package/INSTALL.md +18 -0
- package/README.md +3 -0
- package/SPEC.md +355 -187
- package/dist/build-info.json +1 -1
- package/dist/completions/_plurnk-service +13 -0
- package/dist/completions/plurnk-service.bash +19 -0
- package/dist/completions/plurnk-service.fish +93 -0
- package/dist/core/AdmittedTurnExecutor.d.ts +2 -2
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +16 -24
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/BudgetReadout.d.ts +7 -2
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +36 -5
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +2 -2
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +43 -39
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +1 -4
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/EnvFlags.js +1 -1
- package/dist/core/EnvFlags.js.map +1 -1
- package/dist/core/FabricatedLog.d.ts +1 -0
- package/dist/core/FabricatedLog.d.ts.map +1 -1
- package/dist/core/FabricatedLog.js +8 -0
- package/dist/core/FabricatedLog.js.map +1 -1
- package/dist/core/KillHandler.d.ts.map +1 -1
- package/dist/core/KillHandler.js +2 -0
- package/dist/core/KillHandler.js.map +1 -1
- package/dist/core/LiveSubscription.d.ts.map +1 -1
- package/dist/core/LiveSubscription.js +14 -2
- package/dist/core/LiveSubscription.js.map +1 -1
- package/dist/core/LiveSubscriptions.d.ts +3 -1
- package/dist/core/LiveSubscriptions.d.ts.map +1 -1
- package/dist/core/LiveSubscriptions.js +16 -1
- package/dist/core/LiveSubscriptions.js.map +1 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +3 -1
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LoopDriver.d.ts.map +1 -1
- package/dist/core/LoopDriver.js +3 -0
- package/dist/core/LoopDriver.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +1 -0
- package/dist/core/MessageResources.sql +31 -0
- package/dist/core/PacketBuilder.d.ts +2 -2
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +123 -51
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/PacketBuilder.sql +13 -14
- package/dist/core/ReplyMetadata.d.ts +10 -0
- package/dist/core/ReplyMetadata.d.ts.map +1 -0
- package/dist/core/ReplyMetadata.js +20 -0
- package/dist/core/ReplyMetadata.js.map +1 -0
- package/dist/core/ServiceHelp.d.ts +6 -0
- package/dist/core/ServiceHelp.d.ts.map +1 -0
- package/dist/core/ServiceHelp.js +35 -0
- package/dist/core/ServiceHelp.js.map +1 -0
- package/dist/core/StoredPacket.d.ts +1 -0
- package/dist/core/StoredPacket.d.ts.map +1 -1
- package/dist/core/StoredPacket.js +4 -1
- package/dist/core/StoredPacket.js.map +1 -1
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +2 -2
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TokenCalibration.d.ts +2 -2
- package/dist/core/TokenCalibration.d.ts.map +1 -1
- package/dist/core/TokenCalibration.js +12 -16
- package/dist/core/TokenCalibration.js.map +1 -1
- package/dist/core/TokenCalibration.sql +29 -6
- package/dist/core/TurnDispositionHandler.d.ts +1 -2
- package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
- package/dist/core/TurnDispositionHandler.js +30 -26
- package/dist/core/TurnDispositionHandler.js.map +1 -1
- package/dist/core/TurnMaterialization.d.ts.map +1 -1
- package/dist/core/TurnMaterialization.js +11 -1
- package/dist/core/TurnMaterialization.js.map +1 -1
- package/dist/core/TurnOps.d.ts +5 -1
- package/dist/core/TurnOps.d.ts.map +1 -1
- package/dist/core/TurnOps.js +29 -2
- package/dist/core/TurnOps.js.map +1 -1
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +119 -55
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/TurnSources.sql +2 -2
- package/dist/core/packet-wire.d.ts +7 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +87 -27
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/recap-lines.d.ts +4 -0
- package/dist/core/recap-lines.d.ts.map +1 -0
- package/dist/core/recap-lines.js +11 -0
- package/dist/core/recap-lines.js.map +1 -0
- package/dist/core/sql-functions.d.ts +2 -0
- package/dist/core/sql-functions.d.ts.map +1 -0
- package/dist/core/sql-functions.js +5 -0
- package/dist/core/sql-functions.js.map +1 -0
- package/dist/core/turn-scheduler.d.ts.map +1 -1
- package/dist/core/turn-scheduler.js +1 -1
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/evidence/EvidenceReader.d.ts.map +1 -1
- package/dist/evidence/EvidenceReader.js +15 -2
- package/dist/evidence/EvidenceReader.js.map +1 -1
- package/dist/evidence/digest.sql +1 -0
- package/dist/man/plurnk-service.1 +488 -0
- package/dist/schemes/Env.d.ts +26 -0
- package/dist/schemes/Env.d.ts.map +1 -0
- package/dist/schemes/Env.js +133 -0
- package/dist/schemes/Env.js.map +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +15 -15
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +3 -2
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +9 -0
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +33 -10
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +9 -0
- package/dist/schemes/_entry-find.d.ts +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +16 -9
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.js +3 -3
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/exec-abort.js +2 -2
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts +1 -0
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +6 -1
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +1 -0
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +4 -1
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts +4 -2
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +4 -1
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +12 -3
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/drain.sql +10 -9
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +5 -31
- package/dist/service.js.map +1 -1
- package/docs/env.md +22 -0
- package/migrations/018_worker_owners.sql +1 -0
- package/migrations/019_interactive_owners.sql +1 -0
- package/migrations/020_log_projection.sql +1 -0
- package/migrations/021_packet_roles.sql +86 -0
- package/migrations/022_message_completion.sql +24 -0
- package/package.json +16 -14
package/dist/core/TurnRunner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import NativeContent from "./NativeContent.js";
|
|
3
|
-
import { PlurnkParser } from "@plurnk/plurnk-parser";
|
|
4
|
-
import { PathSyntax, PlurnkParseError,
|
|
3
|
+
import { PlurnkParser, parsePath } from "@plurnk/plurnk-parser";
|
|
4
|
+
import { PathSyntax, PlurnkParseError, UNKNOWN_POSITION } from "@plurnk/plurnk-contracts";
|
|
5
5
|
import WorkerOwners from "./WorkerOwners.js";
|
|
6
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
7
7
|
import { aggregateProviderAccounting } from "@plurnk/plurnk-providers";
|
|
@@ -12,20 +12,16 @@ import GitMembership from "./git-membership.js";
|
|
|
12
12
|
import { acceptedKinds } from "./attachments.js";
|
|
13
13
|
import GitState from "./git-state.js";
|
|
14
14
|
import WorkspaceSettings from "./workspace-settings.js";
|
|
15
|
-
import { contentWeight } from "./content-weight.js";
|
|
16
15
|
import { generatedPathname } from "./plurnk-uri.js";
|
|
16
|
+
import { contentWeight } from "./content-weight.js";
|
|
17
17
|
import { readFile } from "node:fs/promises";
|
|
18
18
|
import { resolve as resolvePath } from "node:path";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
|
-
// {§operator-grammar}
|
|
21
|
-
// relative to the daemon's working directory. The service ships no grammar profile, so a bare
|
|
22
|
-
// name (no separator) is refused by name rather than resolved against anything.
|
|
20
|
+
// {§operator-grammar}
|
|
23
21
|
export const resolveOperatorGrammarPath = (value) => {
|
|
24
22
|
if (value === "~" || value.startsWith("~/"))
|
|
25
23
|
return resolvePath(homedir(), value.slice(2));
|
|
26
|
-
|
|
27
|
-
return resolvePath(value);
|
|
28
|
-
throw new Error(`PLURNK_PROVIDERS_GBNF=${value} names a bundled grammar profile; the service ships none (#588). Give the path of a grammar file you wrote.`);
|
|
24
|
+
return resolvePath(value);
|
|
29
25
|
};
|
|
30
26
|
// Shared module imported by both Engine and the digest, so wire
|
|
31
27
|
// projection and digest projection are structurally one function — no
|
|
@@ -101,6 +97,24 @@ const windowOverflowFailure = (overflow) => Results.failure("engine:context", "w
|
|
|
101
97
|
// {§context-over-budget-row} — the row is the mandate; the gauge is the one home for the numbers
|
|
102
98
|
// ({§context-gauge}), which the row's own charge would otherwise put out of agreement.
|
|
103
99
|
const OVER_BUDGET_DETAIL = "Context exceeds budget. YOU MUST ONLY KILL, MOVE or NOTE this turn.";
|
|
100
|
+
// {§worker-initialization-entry} — the one NOTE that leads the initialization surveys.
|
|
101
|
+
const INITIALIZATION_NOTE = {
|
|
102
|
+
op: "NOTE", aside: null, metadata: null, target: null, lineMarker: null,
|
|
103
|
+
body: "Surveyed tooling, documentation, extended context, and project root.", position: UNKNOWN_POSITION,
|
|
104
|
+
};
|
|
105
|
+
// {§continued-without-note} — said once, in the next packet, of a continuing turn that carried no NOTE.
|
|
106
|
+
const CONTINUED_WITHOUT_NOTE = "The turn continued without a NOTE.";
|
|
107
|
+
// Trial (#1073): a continuing turn that carried NOTEs and no other operation is told so under the same
|
|
108
|
+
// per-turn rule; it is a notice, never a strike.
|
|
109
|
+
const NOTED_WITHOUT_OPERATION = "The turn carried NOTEs and no other operation.";
|
|
110
|
+
// {§notice-directive} — what each notice of the model's own output asks of it: MUST where the card says MUST
|
|
111
|
+
// or the output was refused, SHOULD where a near-miss was accepted.
|
|
112
|
+
const DIRECTIVE = Object.freeze({
|
|
113
|
+
note: "YOU MUST emit at least one NOTE operation with a complete status summary and at least one other operation per turn.",
|
|
114
|
+
emission: "YOU MUST ONLY emit fenced operations; with the parameters, pattern, and terse aside on the fenced Operation line.",
|
|
115
|
+
advisory: "YOU SHOULD write the canonical form.",
|
|
116
|
+
output: "YOU SHOULD emit fewer or shorter operations per turn.",
|
|
117
|
+
});
|
|
104
118
|
const overBudgetFailure = () => Results.failure("engine:context", "packet-exceeds-budget", 413, OVER_BUDGET_DETAIL);
|
|
105
119
|
const INVALID_EMISSION_RECOVERY_MESSAGE = "Response rejected before dispatch; no operations were performed.";
|
|
106
120
|
// {§output-allowance-notice} — THE single derivation of a ceiling cut: a
|
|
@@ -109,7 +123,7 @@ const INVALID_EMISSION_RECOVERY_MESSAGE = "Response rejected before dispatch; no
|
|
|
109
123
|
// path. Null means the emission was not cut by the allowance.
|
|
110
124
|
const allowanceCutMessage = (finishReason, grant) => finishReason !== "length"
|
|
111
125
|
? null
|
|
112
|
-
: `
|
|
126
|
+
: `Emission truncated at the output allowance${grant === null ? "" : ` (${grant} tokens)`}`;
|
|
113
127
|
const readEmissionAttempts = () => Knob.integer("PLURNK_SERVICE_EMISSION_ATTEMPTS", 1);
|
|
114
128
|
// The wall's abort reason — runLoop branches a mid-turn teardown to the 504 terminal on it.
|
|
115
129
|
export const LOOP_TIMEOUT_REASON = "loop_timeout";
|
|
@@ -488,9 +502,14 @@ class TurnRunner {
|
|
|
488
502
|
await Turn.complete(this.#db, initializationTurn.id, 200);
|
|
489
503
|
return;
|
|
490
504
|
}
|
|
491
|
-
|
|
505
|
+
// {§worker-initialization-entry} — the example turn keeps the NOTE contract the card teaches:
|
|
506
|
+
// when the surveys run, one NOTE leads them.
|
|
507
|
+
const programStatements = admittedInitializationStatements.some((statement) => statement.op === "FIND")
|
|
508
|
+
? [INITIALIZATION_NOTE, ...admittedInitializationStatements]
|
|
509
|
+
: admittedInitializationStatements;
|
|
510
|
+
const program = TurnOps.renderInternal(programStatements);
|
|
492
511
|
const admitted = PlurnkParser.parseStatements(program).items.flatMap((item) => item.kind === "statement" ? [item.statement] : []);
|
|
493
|
-
if (admitted.length !==
|
|
512
|
+
if (admitted.length !== programStatements.length) {
|
|
494
513
|
throw new Error("initialization program did not preserve every authored operation");
|
|
495
514
|
}
|
|
496
515
|
await Turn.recordSource(this.#db, initializationTurn.id, "ops", program);
|
|
@@ -505,7 +524,7 @@ class TurnRunner {
|
|
|
505
524
|
turnId: initializationTurn.id,
|
|
506
525
|
fromSequence: 1,
|
|
507
526
|
failOnOperationError: true,
|
|
508
|
-
fit: this.#fitFor(args, { turnId: initializationTurn.id, seq: initializationTurn.sequence, gitStatus, notices: [], transientOpenLogEntryId: container.transientOpenLogEntryId, projection: { bodiless: new Set(),
|
|
527
|
+
fit: this.#fitFor(args, { turnId: initializationTurn.id, seq: initializationTurn.sequence, gitStatus, notices: [], transientOpenLogEntryId: container.transientOpenLogEntryId, projection: { bodiless: new Set(), omitEmissionHistory: false } }),
|
|
509
528
|
signal: this.#loopSignal(loopId),
|
|
510
529
|
onDispatch,
|
|
511
530
|
onSettled,
|
|
@@ -517,11 +536,18 @@ class TurnRunner {
|
|
|
517
536
|
// {§actor-boundary-catalog-preview} — the opening surveys, each admitted only when
|
|
518
537
|
// its scheme is registered for the workspace. A positive filesItems caps the
|
|
519
538
|
// project rows.
|
|
520
|
-
async #catalogSurveys({ workspaceId }, { workerName, initializationPolicies }, filesItems) {
|
|
539
|
+
async #catalogSurveys({ workspaceId }, { workerName, initializationPolicies, loopSequence }, filesItems) {
|
|
521
540
|
const catalogSchemes = await this.#db.engine_scheme_catalog_summary.all({ workspace_id: workspaceId });
|
|
522
541
|
const fileItems = catalogSchemes.find(({ scheme }) => scheme === "file")?.shallow_items ?? 0;
|
|
523
542
|
const fileCap = filesItems > 0 && fileItems > 0 ? Math.min(filesItems, fileItems) : null;
|
|
524
543
|
const surveys = [
|
|
544
|
+
{
|
|
545
|
+
// Trial (#1073): the survey reads its own program, so the log carries one program verbatim
|
|
546
|
+
// and shows that every ops:// turn is readable.
|
|
547
|
+
op: "READ", aside: "this program",
|
|
548
|
+
target: { kind: "url", raw: `ops://${workerName}/${loopSequence}/1`, scheme: "ops", username: null, password: null, hostname: workerName, port: null, pathname: `/${loopSequence}/1`, query: null, fragment: null },
|
|
549
|
+
metadata: null, lineMarker: null, matcher: null, body: null, position: UNKNOWN_POSITION,
|
|
550
|
+
},
|
|
525
551
|
{
|
|
526
552
|
op: "FIND", aside: null,
|
|
527
553
|
target: { kind: "url", raw: "skill://*/SKILL.md", scheme: "skill", username: null, password: null, hostname: "*", port: null, pathname: "/SKILL.md", query: null, fragment: null },
|
|
@@ -631,7 +657,7 @@ class TurnRunner {
|
|
|
631
657
|
const systemCtx = this.#schemeContext(args, turnId);
|
|
632
658
|
// {§context-fit} — one measure for everything this turn lands: the budget less the packet as it
|
|
633
659
|
// would render now. The drained notices join the same facts below.
|
|
634
|
-
const facts = { turnId, seq, gitStatus, notices: [], transientOpenLogEntryId: container.transientOpenLogEntryId, projection: { bodiless: new Set(),
|
|
660
|
+
const facts = { turnId, seq, gitStatus, notices: [], transientOpenLogEntryId: container.transientOpenLogEntryId, projection: { bodiless: new Set(), omitEmissionHistory: false } };
|
|
635
661
|
const fit = this.#fitFor(args, facts);
|
|
636
662
|
const messages = await this.#publishMessages(args, turnId, fit);
|
|
637
663
|
let nextActionIndex = await this.#readOpenPaths(args, turnId, messages.openPaths, messages.nextActionIndex, fit);
|
|
@@ -692,20 +718,20 @@ class TurnRunner {
|
|
|
692
718
|
}
|
|
693
719
|
return { nextActionIndex, openPaths };
|
|
694
720
|
}
|
|
695
|
-
// {§methods-loop-run-open-paths}: selected
|
|
721
|
+
// {§methods-loop-run-open-paths}: selected resources belong to the message.
|
|
696
722
|
// Publish it, then dispatch ordinary core READs in that same turn;
|
|
697
723
|
// missing/non-member paths retain their normal 4xx. Returns the next action index.
|
|
698
724
|
async #readOpenPaths({ workspaceId, workerId, loopId, onDispatch, onSettled }, turnId, openPaths, fromSequence, fit) {
|
|
699
725
|
let nextActionIndex = fromSequence;
|
|
700
726
|
for (const [index, raw] of openPaths.entries()) {
|
|
701
|
-
const
|
|
727
|
+
const resourceRead = {
|
|
702
728
|
op: "READ", aside: null, lineMarker: null, matcher: null,
|
|
703
|
-
target:
|
|
729
|
+
target: parsePath(PathSyntax.escapeTarget(raw)),
|
|
704
730
|
metadata: null,
|
|
705
731
|
body: null, position: UNKNOWN_POSITION,
|
|
706
732
|
};
|
|
707
733
|
await this.#dispatch({
|
|
708
|
-
statement:
|
|
734
|
+
statement: resourceRead, workspaceId, workerId, loopId, turnId,
|
|
709
735
|
sequence: nextActionIndex, origin: "_plurnk", onDispatch, onSettled,
|
|
710
736
|
fit: reserved(fit, openPaths.length - index - 1),
|
|
711
737
|
});
|
|
@@ -728,12 +754,12 @@ class TurnRunner {
|
|
|
728
754
|
transientOpenLogEntryId: facts.transientOpenLogEntryId,
|
|
729
755
|
turnId: facts.turnId,
|
|
730
756
|
bodiless: facts.projection.bodiless,
|
|
731
|
-
|
|
757
|
+
omitEmissionHistory: facts.projection.omitEmissionHistory,
|
|
732
758
|
});
|
|
733
|
-
// {§
|
|
759
|
+
// {§emission-history}: all builds, including result-admission probes, shed the optional
|
|
734
760
|
// replay before taking result bodies. Share the decision across this request's rebuilds.
|
|
735
|
-
if (PacketWire.sectionContent(packet, "
|
|
736
|
-
facts.projection.
|
|
761
|
+
if (PacketWire.sectionContent(packet, "emission-history").length > 0 && this.#packets.windowOverflow(packet, provider) !== null) {
|
|
762
|
+
facts.projection.omitEmissionHistory = true;
|
|
737
763
|
return this.#buildPacket(args, facts);
|
|
738
764
|
}
|
|
739
765
|
return packet;
|
|
@@ -796,12 +822,12 @@ class TurnRunner {
|
|
|
796
822
|
}
|
|
797
823
|
}
|
|
798
824
|
// {§context-own-rows-fit} — one shedding step for whichever measure found the request over the wall: the
|
|
799
|
-
// previous program goes whole first ({§
|
|
825
|
+
// previous program goes whole first ({§emission-history}), then the newest rows still carrying a body,
|
|
800
826
|
// enough of them by their rendered tokens to shed the excess; every body stays stored. Null when nothing
|
|
801
827
|
// is left to take.
|
|
802
828
|
async #shed(args, request, overflow) {
|
|
803
|
-
if (!request.projection.
|
|
804
|
-
request.projection.
|
|
829
|
+
if (!request.projection.omitEmissionHistory && PacketWire.sectionContent(request.packet, "emission-history").length > 0) {
|
|
830
|
+
request.projection.omitEmissionHistory = true;
|
|
805
831
|
}
|
|
806
832
|
else {
|
|
807
833
|
let shed = 0;
|
|
@@ -1260,7 +1286,8 @@ class TurnRunner {
|
|
|
1260
1286
|
source: "engine:capacity",
|
|
1261
1287
|
kind: "output_truncated",
|
|
1262
1288
|
level: "error",
|
|
1263
|
-
|
|
1289
|
+
directive: DIRECTIVE.output,
|
|
1290
|
+
message: `${cut}; no operations were performed.`,
|
|
1264
1291
|
});
|
|
1265
1292
|
}
|
|
1266
1293
|
else {
|
|
@@ -1268,6 +1295,7 @@ class TurnRunner {
|
|
|
1268
1295
|
source: "engine:grammar",
|
|
1269
1296
|
kind: "invalid_emission",
|
|
1270
1297
|
level: "error",
|
|
1298
|
+
directive: DIRECTIVE.emission,
|
|
1271
1299
|
message: diagnostic === undefined
|
|
1272
1300
|
? INVALID_EMISSION_RECOVERY_MESSAGE
|
|
1273
1301
|
: `${INVALID_EMISSION_RECOVERY_MESSAGE} Parser: ${diagnostic.message}`,
|
|
@@ -1303,7 +1331,8 @@ class TurnRunner {
|
|
|
1303
1331
|
source: "engine:turn",
|
|
1304
1332
|
kind: "output_truncated",
|
|
1305
1333
|
level: "warn",
|
|
1306
|
-
|
|
1334
|
+
directive: DIRECTIVE.output,
|
|
1335
|
+
message: `${allowanceCut}; no authored operations were performed.`,
|
|
1307
1336
|
});
|
|
1308
1337
|
}
|
|
1309
1338
|
// Non-fatal provider transport notices on an accepted turn. Forward each
|
|
@@ -1317,6 +1346,7 @@ class TurnRunner {
|
|
|
1317
1346
|
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1318
1347
|
source: notice.source,
|
|
1319
1348
|
kind: notice.kind,
|
|
1349
|
+
...(notice.directive === undefined ? {} : { directive: notice.directive }),
|
|
1320
1350
|
message: notice.message ?? "",
|
|
1321
1351
|
level: notice.level,
|
|
1322
1352
|
...(located !== null
|
|
@@ -1328,27 +1358,19 @@ class TurnRunner {
|
|
|
1328
1358
|
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1329
1359
|
source: "engine:capacity",
|
|
1330
1360
|
kind: "output_truncated",
|
|
1331
|
-
|
|
1361
|
+
directive: DIRECTIVE.output,
|
|
1362
|
+
message: `${allowanceCut}.`,
|
|
1332
1363
|
level: "warn",
|
|
1333
1364
|
});
|
|
1334
1365
|
}
|
|
1335
|
-
// {§outside-text} —
|
|
1336
|
-
//
|
|
1366
|
+
// {§outside-text} — a turn that fenced no operation keeps its text as its outside source: the
|
|
1367
|
+
// evidence behind the empty turn's strike and a terminal's citation; no row, notice or NOTE.
|
|
1337
1368
|
if (split.outside !== null) {
|
|
1338
|
-
await Turn.recordSource(this.#db, request.turnId, "outside", split.outside
|
|
1339
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1340
|
-
source: "engine:turn",
|
|
1341
|
-
kind: "outside_text",
|
|
1342
|
-
level: "warn",
|
|
1343
|
-
message: `${split.outside.tokens} tokens emitted outside OPs. Discarded.`,
|
|
1344
|
-
});
|
|
1369
|
+
await Turn.recordSource(this.#db, request.turnId, "outside", split.outside, { modelCallId: emission.modelCallId });
|
|
1345
1370
|
this.#notify.outsideEventNotify?.(workspaceId, {
|
|
1346
|
-
workerId,
|
|
1347
|
-
loopId,
|
|
1348
|
-
turnId: request.turnId,
|
|
1371
|
+
workerId, loopId, turnId: request.turnId,
|
|
1349
1372
|
coordinate: `${request.workerName}-${request.loopSeq}-${request.seq}`,
|
|
1350
|
-
text: split.outside.
|
|
1351
|
-
tokens: split.outside.tokens,
|
|
1373
|
+
text: split.outside, tokens: contentWeight(split.outside),
|
|
1352
1374
|
});
|
|
1353
1375
|
}
|
|
1354
1376
|
// {§operator-grammar} — transport evidence only: the turn records whether the operator's
|
|
@@ -1404,7 +1426,7 @@ class TurnRunner {
|
|
|
1404
1426
|
fit: request.fit,
|
|
1405
1427
|
recoverableParseErrors: split.recoverableParseErrors,
|
|
1406
1428
|
emptyTurn: split.emptyTurn,
|
|
1407
|
-
|
|
1429
|
+
completionAllowed: split.completionAllowed,
|
|
1408
1430
|
bare: {
|
|
1409
1431
|
provider: childProvider,
|
|
1410
1432
|
loopSequence: request.loopSeq,
|
|
@@ -1415,6 +1437,22 @@ class TurnRunner {
|
|
|
1415
1437
|
onDispatch,
|
|
1416
1438
|
onSettled,
|
|
1417
1439
|
});
|
|
1440
|
+
// {§continued-without-note} — a turn the loop continues past or parks on, having carried no NOTE
|
|
1441
|
+
// forward in its program or from its reasoning, is told so in the next packet. An empty turn has
|
|
1442
|
+
// its own no-operation row instead.
|
|
1443
|
+
const ops = split.packetAssistant.ops;
|
|
1444
|
+
if ((executed.status === 102 || executed.status === 202) && ops.length > 0
|
|
1445
|
+
&& !ops.some((statement) => "op" in statement && statement.op === "NOTE")) {
|
|
1446
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1447
|
+
source: "engine:turn", kind: "continued_without_note", level: "warn", directive: DIRECTIVE.note, message: CONTINUED_WITHOUT_NOTE,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
const annotationOnly = ops.length > 0 && ops.every((statement) => "op" in statement && statement.op === "NOTE");
|
|
1451
|
+
if ((executed.status === 102 || executed.status === 202) && annotationOnly) {
|
|
1452
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1453
|
+
source: "engine:turn", kind: "noted_without_operation", level: "warn", directive: DIRECTIVE.note, message: NOTED_WITHOUT_OPERATION,
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1418
1456
|
return turnResult(request, executed.status, {
|
|
1419
1457
|
outcomes: executed.outcomes,
|
|
1420
1458
|
progressed: executed.progressed,
|
|
@@ -1477,10 +1515,9 @@ class TurnRunner {
|
|
|
1477
1515
|
const parseErrors = [];
|
|
1478
1516
|
let contentStatementCount = 0;
|
|
1479
1517
|
let hasUnparsedTail = false;
|
|
1518
|
+
let operationOmitted = false;
|
|
1480
1519
|
const parseNotices = [];
|
|
1481
1520
|
const fabrications = [];
|
|
1482
|
-
// {§outside-text}: every span outside an operation, verbatim and in source order; never an
|
|
1483
|
-
// operation of its own, so a repetitive or length-cut response stays one source.
|
|
1484
1521
|
const outside = [];
|
|
1485
1522
|
if (preParsedOps !== undefined) {
|
|
1486
1523
|
ops.push(...preParsedOps);
|
|
@@ -1496,13 +1533,33 @@ class TurnRunner {
|
|
|
1496
1533
|
span.setAttribute("statements", result.items.filter((item) => item.kind === "statement").length);
|
|
1497
1534
|
return result;
|
|
1498
1535
|
});
|
|
1536
|
+
// {§outside-text}: beside a fenced operation, the text between two operations is one NOTE at its
|
|
1537
|
+
// position, annotation the model wrote without its fence; a turn that fenced no operation keeps
|
|
1538
|
+
// its text as its outside source and stays an empty turn ({§empty-turn}). Echoed emission
|
|
1539
|
+
// headings are announcements, not annotation, and are dropped ({§fabricated-log-entry}).
|
|
1540
|
+
const fenced = parsed.items.filter((item) => item.kind === "statement").length;
|
|
1541
|
+
let span = null;
|
|
1542
|
+
const flushSpan = () => {
|
|
1543
|
+
if (span === null)
|
|
1544
|
+
return;
|
|
1545
|
+
const body = span.body.join("\n\n");
|
|
1546
|
+
if (body.length > 0) {
|
|
1547
|
+
if (fenced > 0) {
|
|
1548
|
+
ops.push({ op: "NOTE", aside: null, metadata: null, target: null, lineMarker: null, body, position: span.position });
|
|
1549
|
+
contentStatementCount += 1;
|
|
1550
|
+
}
|
|
1551
|
+
else
|
|
1552
|
+
outside.push(body);
|
|
1553
|
+
}
|
|
1554
|
+
span = null;
|
|
1555
|
+
};
|
|
1499
1556
|
for (const item of parsed.items) {
|
|
1557
|
+
if (item.kind !== "text")
|
|
1558
|
+
flushSpan();
|
|
1500
1559
|
if (item.kind === "statement") {
|
|
1501
1560
|
ops.push(item.statement);
|
|
1502
1561
|
contentStatementCount += 1;
|
|
1503
1562
|
}
|
|
1504
|
-
// Operation boundaries stay the parser's responsibility; what falls outside them is
|
|
1505
|
-
// stored as the turn's outside source and counted as nothing.
|
|
1506
1563
|
else if (item.kind === "text") {
|
|
1507
1564
|
// {§fabricated-log-entry}: a log-entry heading in outside text rejects the attempt.
|
|
1508
1565
|
const fabricated = FabricatedLog.find(item.content);
|
|
@@ -1510,16 +1567,22 @@ class TurnRunner {
|
|
|
1510
1567
|
fabrications.push({ message: FabricatedLog.message(fabricated.heading), line: item.position.line + fabricated.line, column: 1, source: "harness" });
|
|
1511
1568
|
continue;
|
|
1512
1569
|
}
|
|
1513
|
-
|
|
1570
|
+
const body = FabricatedLog.stripEchoes(item.content);
|
|
1571
|
+
if (body.length === 0)
|
|
1572
|
+
continue;
|
|
1573
|
+
span ??= { body: [], position: item.position };
|
|
1574
|
+
span.body.push(body);
|
|
1514
1575
|
}
|
|
1515
1576
|
else if (item.kind === "error") {
|
|
1516
1577
|
const err = item.error;
|
|
1517
1578
|
if (err instanceof PlurnkParseError) {
|
|
1579
|
+
operationOmitted ||= err.operationOmitted;
|
|
1518
1580
|
if (err.severity === "warning") {
|
|
1519
1581
|
parseNotices.push({
|
|
1520
1582
|
source: "grammar",
|
|
1521
1583
|
kind: "parse_advisory",
|
|
1522
1584
|
level: "warn",
|
|
1585
|
+
directive: DIRECTIVE.advisory,
|
|
1523
1586
|
message: err.message,
|
|
1524
1587
|
position: {
|
|
1525
1588
|
type: "content-offset",
|
|
@@ -1539,6 +1602,7 @@ class TurnRunner {
|
|
|
1539
1602
|
}
|
|
1540
1603
|
}
|
|
1541
1604
|
}
|
|
1605
|
+
flushSpan();
|
|
1542
1606
|
// {§unparsed-tail-boundary} — the lexer's one boundary fact: from `unparsedTail.from` on,
|
|
1543
1607
|
// nothing was read. The statements that closed before it are ordinary facts and run; the
|
|
1544
1608
|
// loss itself is one more hard diagnostic, a failed row the model sees. An emission that
|
|
@@ -1555,6 +1619,7 @@ class TurnRunner {
|
|
|
1555
1619
|
source: "grammar:reasoning",
|
|
1556
1620
|
kind: "parse_advisory",
|
|
1557
1621
|
level: "warn",
|
|
1622
|
+
directive: DIRECTIVE.advisory,
|
|
1558
1623
|
message: warning.message,
|
|
1559
1624
|
parserSource: warning.source,
|
|
1560
1625
|
});
|
|
@@ -1571,9 +1636,9 @@ class TurnRunner {
|
|
|
1571
1636
|
parseErrors.unshift(...fabrications.slice(0, 1));
|
|
1572
1637
|
const emissionStatements = preParsedOps === undefined && contentStatementCount > 0 ? [...ops] : null;
|
|
1573
1638
|
ops.unshift(...reasoningOps);
|
|
1574
|
-
const
|
|
1575
|
-
&&
|
|
1576
|
-
&& assistant.finishReason
|
|
1639
|
+
const completionAllowed = !hasUnparsedTail && !operationOmitted && !assistant.nativeToolCalls
|
|
1640
|
+
&& parseErrors.every((error) => error.message === PlurnkParser.NO_VALID_OPERATION)
|
|
1641
|
+
&& !["length", "content_filter", "tool_calls"].includes(assistant.finishReason ?? "");
|
|
1577
1642
|
const emptyTurn = preParsedOps === undefined && operationCount === 0 && !hasUnparsedTail;
|
|
1578
1643
|
// {§unparsed-tail-boundary}: outside text is not operational work.
|
|
1579
1644
|
const emissionValid = fabrications.length === 0 && (preParsedOps !== undefined
|
|
@@ -1582,7 +1647,6 @@ class TurnRunner {
|
|
|
1582
1647
|
const recoverableParseErrors = parseErrors
|
|
1583
1648
|
.filter((error) => error.message !== PlurnkParser.NO_VALID_OPERATION)
|
|
1584
1649
|
.toSorted(comparePosition);
|
|
1585
|
-
const outsideText = outside.join("\n\n");
|
|
1586
1650
|
return {
|
|
1587
1651
|
packetAssistant: { content: assistant.content, ops, reasoning },
|
|
1588
1652
|
sourceBacked: preParsedOps === undefined,
|
|
@@ -1590,10 +1654,10 @@ class TurnRunner {
|
|
|
1590
1654
|
parseErrors,
|
|
1591
1655
|
recoverableParseErrors: emissionValid ? recoverableParseErrors : [],
|
|
1592
1656
|
emptyTurn,
|
|
1593
|
-
|
|
1657
|
+
completionAllowed,
|
|
1594
1658
|
parseNotices,
|
|
1595
1659
|
emissionValid,
|
|
1596
|
-
outside: outside.length === 0 ? null :
|
|
1660
|
+
outside: outside.length === 0 ? null : outside.join("\n\n"),
|
|
1597
1661
|
emissionStatements,
|
|
1598
1662
|
};
|
|
1599
1663
|
}
|