@plurnk/plurnk-service 0.4.0 → 0.6.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.
Files changed (71) hide show
  1. package/README.md +13 -44
  2. package/SPEC.md +546 -445
  3. package/bin/plurnk-service.js +2 -1
  4. package/dist/core/ChannelWrite.d.ts +5 -0
  5. package/dist/core/ChannelWrite.d.ts.map +1 -1
  6. package/dist/core/ChannelWrite.js.map +1 -1
  7. package/dist/core/Engine.d.ts +8 -5
  8. package/dist/core/Engine.d.ts.map +1 -1
  9. package/dist/core/Engine.js +190 -99
  10. package/dist/core/Engine.js.map +1 -1
  11. package/dist/core/PluginLoader.js +6 -6
  12. package/dist/core/PluginLoader.js.map +1 -1
  13. package/dist/core/ProviderInstantiate.d.ts +4 -0
  14. package/dist/core/ProviderInstantiate.d.ts.map +1 -0
  15. package/dist/core/ProviderInstantiate.js +41 -0
  16. package/dist/core/ProviderInstantiate.js.map +1 -0
  17. package/dist/core/ProviderRegistry.d.ts.map +1 -1
  18. package/dist/core/ProviderRegistry.js +1 -1
  19. package/dist/core/ProviderRegistry.js.map +1 -1
  20. package/dist/core/SchemeRegistry.d.ts +1 -1
  21. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  22. package/dist/core/SchemeRegistry.js +4 -20
  23. package/dist/core/SchemeRegistry.js.map +1 -1
  24. package/dist/core/line-marker.js +3 -3
  25. package/dist/core/line-marker.js.map +1 -1
  26. package/dist/core/matcher.d.ts +4 -6
  27. package/dist/core/matcher.d.ts.map +1 -1
  28. package/dist/core/matcher.js +61 -184
  29. package/dist/core/matcher.js.map +1 -1
  30. package/dist/core/mimetype-binary.js +1 -1
  31. package/dist/core/mimetype-binary.js.map +1 -1
  32. package/dist/core/packet-wire.d.ts.map +1 -1
  33. package/dist/core/packet-wire.js +130 -30
  34. package/dist/core/packet-wire.js.map +1 -1
  35. package/dist/core/scheme-types.d.ts +3 -26
  36. package/dist/core/scheme-types.d.ts.map +1 -1
  37. package/dist/core/scheme-types.js +5 -8
  38. package/dist/core/scheme-types.js.map +1 -1
  39. package/dist/index.d.ts +3 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +14 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/schemes/Exec.d.ts.map +1 -1
  44. package/dist/schemes/Exec.js +12 -24
  45. package/dist/schemes/Exec.js.map +1 -1
  46. package/dist/schemes/File.d.ts +1 -0
  47. package/dist/schemes/File.d.ts.map +1 -1
  48. package/dist/schemes/File.js +11 -5
  49. package/dist/schemes/File.js.map +1 -1
  50. package/dist/schemes/Log.d.ts +1 -0
  51. package/dist/schemes/Log.d.ts.map +1 -1
  52. package/dist/schemes/Log.js +10 -4
  53. package/dist/schemes/Log.js.map +1 -1
  54. package/dist/schemes/_entry-ops.d.ts +1 -1
  55. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  56. package/dist/schemes/_entry-ops.js +15 -20
  57. package/dist/schemes/_entry-ops.js.map +1 -1
  58. package/dist/server/Daemon.d.ts +11 -0
  59. package/dist/server/Daemon.d.ts.map +1 -1
  60. package/dist/server/Daemon.js +18 -0
  61. package/dist/server/Daemon.js.map +1 -1
  62. package/dist/server/MethodRegistry.d.ts +2 -2
  63. package/dist/server/MethodRegistry.d.ts.map +1 -1
  64. package/dist/server/methods/loop_resolve.js +2 -2
  65. package/dist/server/methods/loop_resolve.js.map +1 -1
  66. package/dist/server/methods/loop_run.d.ts.map +1 -1
  67. package/dist/server/methods/loop_run.js +2 -1
  68. package/dist/server/methods/loop_run.js.map +1 -1
  69. package/dist/server/methods/providers_list.js +1 -1
  70. package/dist/server/methods/providers_list.js.map +1 -1
  71. package/package.json +17 -12
@@ -7,7 +7,8 @@ import { fileURLToPath } from "node:url";
7
7
  import { dirname, resolve } from "node:path";
8
8
  import Daemon from "../src/server/Daemon.ts";
9
9
  import { parseEnvExample, formatFlagsHelp } from "../src/core/EnvFlags.ts";
10
- import { loadActiveProvider, resolveActiveAlias } from "../src/core/ProviderRegistry.ts";
10
+ import { loadActiveProvider } from "../src/core/ProviderInstantiate.ts";
11
+ import { resolveActiveAlias } from "@plurnk/plurnk-providers";
11
12
 
12
13
  const die = (code, message) => {
13
14
  process.stderr.write(`${message}\n`);
@@ -17,6 +17,11 @@ export interface WakeRunPayload {
17
17
  summary: string;
18
18
  }
19
19
  export type WakeRunNotify = (payload: WakeRunPayload) => void;
20
+ export interface TelemetryEventPayload {
21
+ loopId: number;
22
+ event: object;
23
+ }
24
+ export type TelemetryEventNotify = (sessionId: number, payload: TelemetryEventPayload) => void;
20
25
  export declare const appendToChannel: (db: Db, { entryId, channel, chunk, notify }: {
21
26
  entryId: number;
22
27
  channel: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAQvF,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAe9D,eAAO,MAAM,eAAe,GACxB,IAAI,EAAE,EACN,qCAAqC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,KACrH,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,eAAe,GACxB,IAAI,EAAE,EACN,qCAAqC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,KAC3H,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,gBAAgB,GACzB,IAAI,EAAE,EACN,oCAAoC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvG,OAAO,CAAC,MAAM,CAIhB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC1B,IAAI,EAAE,EACN,4BAA4B;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvE,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAC/B,IAAI,EAAE,EACN,oBAAoB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACvD,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG/D,CAAC"}
1
+ {"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAQvF,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAQ9D,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAe/F,eAAO,MAAM,eAAe,GACxB,IAAI,EAAE,EACN,qCAAqC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,KACrH,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,eAAe,GACxB,IAAI,EAAE,EACN,qCAAqC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,KAC3H,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,gBAAgB,GACzB,IAAI,EAAE,EACN,oCAAoC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvG,OAAO,CAAC,MAAM,CAIhB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC1B,IAAI,EAAE,EACN,4BAA4B;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvE,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAC/B,IAAI,EAAE,EACN,oBAAoB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACvD,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG/D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,mEAAmE;AACnE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,2EAA2E;AAC3E,gCAAgC;AAoDhC,MAAM,WAAW,GAAG,CAAC,EAAM,EAAE,EAAE,CAAC,EAAE,CAAC,YAA0B,CAAC;AAC9D,MAAM,UAAU,GAAG,CAAC,EAAM,EAAE,EAAE,CAAC,EAAE,CAAC,iBAA+B,CAAC;AAClE,MAAM,SAAS,GAAG,CAAC,EAAM,EAAE,EAAE,CAAC,EAAE,CAAC,iBAA+B,CAAC;AACjE,MAAM,WAAW,GAAG,CAAC,EAAM,EAAE,EAAE,CAAC,EAAE,CAAC,iBAA+B,CAAC;AACnE,MAAM,YAAY,GAAG,CAAC,EAAM,EAAE,EAAE,CAAC,EAAE,CAAC,kBAAgC,CAAC;AACrE,MAAM,cAAc,GAAG,CAAC,EAAM,EAAE,EAAE,CAAC,EAAE,CAAC,wBAAsC,CAAC;AAE7E,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAChC,EAAM,EACN,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAmF,EACvG,EAAE;IACf,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/E,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO;IACjC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACvF,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;AACxG,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAChC,EAAM,EACN,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAyF,EAC7G,EAAE;IACf,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO;IACjC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACvF,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;AACxG,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EACjC,EAAM,EACN,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAsE,EACvF,EAAE;IACjB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5G,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACjG,OAAO,GAAG,CAAC,EAAE,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAClC,EAAM,EACN,EAAE,cAAc,EAAE,MAAM,EAA8C,EACzD,EAAE;IACf,MAAM,YAAY,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC;AAC5E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EACvC,EAAM,EACN,EAAE,KAAK,EAAE,OAAO,EAAsC,EACQ,EAAE;IAChE,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiD,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/H,OAAO,GAAG,IAAI,IAAI,CAAC;AACvB,CAAC,CAAC"}
@@ -3,13 +3,13 @@ import type SchemeRegistry from "./SchemeRegistry.ts";
3
3
  import { Mimetypes } from "@plurnk/plurnk-mimetypes";
4
4
  import type { Db } from "./Db.ts";
5
5
  import type { LoopFlags } from "./scheme-types.ts";
6
- import type { StreamEventNotify, WakeRunNotify } from "./ChannelWrite.ts";
6
+ import type { StreamEventNotify, TelemetryEventNotify, WakeRunNotify } from "./ChannelWrite.ts";
7
7
  type Origin = "model" | "client" | "system" | "plugin";
8
8
  type ChatMessage = {
9
9
  role: "system" | "user" | "assistant";
10
10
  content: string;
11
11
  };
12
- import type { Provider } from "./ProviderRegistry.ts";
12
+ import type { Provider } from "@plurnk/plurnk-providers";
13
13
  type DispatchContext = {
14
14
  statement: PlurnkStatement;
15
15
  sessionId: number;
@@ -56,17 +56,19 @@ export declare const detectCycle: (history: ReadonlyArray<string>, minCycles: nu
56
56
  };
57
57
  export default class Engine {
58
58
  #private;
59
- constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify }: {
59
+ constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify }: {
60
60
  db: Db;
61
61
  schemes: SchemeRegistry;
62
62
  mimetypes?: Mimetypes;
63
63
  streamEventNotify?: StreamEventNotify;
64
64
  wakeRunNotify?: WakeRunNotify;
65
+ telemetryEventNotify?: TelemetryEventNotify;
65
66
  });
66
- runLoop({ provider, messages, persona, sessionId, runId, loopId, maxTurns, maxStrikes, minCycles, maxCyclePeriod, origin, signal, onDispatch, }: {
67
+ runLoop({ provider, messages, persona, requirements, sessionId, runId, loopId, maxTurns, maxStrikes, minCycles, maxCyclePeriod, origin, signal, onDispatch, }: {
67
68
  provider: Provider;
68
69
  messages: ChatMessage[];
69
70
  persona?: string;
71
+ requirements?: string;
70
72
  sessionId: number;
71
73
  runId: number;
72
74
  loopId: number;
@@ -83,10 +85,11 @@ export default class Engine {
83
85
  hitMaxTurns: boolean;
84
86
  reason: "max_turns" | "strike_threshold" | "external" | null;
85
87
  }>;
86
- runTurn({ provider, messages, persona, sessionId, runId, loopId, origin, signal, onDispatch, turnNumber, maxTurns, }: {
88
+ runTurn({ provider, messages, persona, requirements, sessionId, runId, loopId, origin, signal, onDispatch, turnNumber, maxTurns, }: {
87
89
  provider: Provider;
88
90
  messages: ChatMessage[];
89
91
  persona?: string;
92
+ requirements?: string;
90
93
  sessionId: number;
91
94
  runId: number;
92
95
  loopId: number;
@@ -1 +1 @@
1
- {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,18 @@
1
- import { PlurnkParser } from "@plurnk/plurnk-grammar";
1
+ import { PlurnkParser, PlurnkParseError } from "@plurnk/plurnk-grammar";
2
2
  import { Mimetypes, emptyRegistry } from "@plurnk/plurnk-mimetypes";
3
3
  import { writeEntry } from "../schemes/_entry-crud.js";
4
4
  import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
5
- import { sliceLinesRaw } from "./line-marker.js";
6
- import { isBinaryMimetype } from "./mimetype-binary.js";
5
+ import { sliceLinesRaw, isBinaryMimetype } from "@plurnk/plurnk-schemes";
7
6
  // Plain JS module shared with bin/digest.js so wire projection and
8
7
  // digest projection are structurally one function. tsconfig.build.json
9
8
  // has allowJs:true so this gets copied through to dist/.
10
9
  import { packetToWireMessages } from "./packet-wire.js";
11
- // SCHEMES.md §8: writer must be in target scheme's manifest.writableBy.
10
+ // SPEC §3.6: writer must be in target scheme's manifest.writableBy.
12
11
  // SHOW/HIDE/READ/FIND are not gated — they touch visibility metadata or read.
13
12
  const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC"]);
14
13
  const DEFAULT_PREVIEW_BUDGET = 256;
15
14
  const DEFAULT_MAX_STRIKES = 3;
15
+ const DEFAULT_MAX_COMMANDS = 99;
16
16
  const readBudget = () => {
17
17
  const raw = process.env.PLURNK_ENTRY_SIZE_DEFAULT_TOKENS;
18
18
  if (raw === undefined || raw.length === 0)
@@ -31,8 +31,17 @@ const readMaxStrikes = () => {
31
31
  return DEFAULT_MAX_STRIKES;
32
32
  return n;
33
33
  };
34
+ const readMaxCommands = () => {
35
+ const raw = process.env.PLURNK_MAX_COMMANDS;
36
+ if (raw === undefined || raw.length === 0)
37
+ return DEFAULT_MAX_COMMANDS;
38
+ const n = Number.parseInt(raw, 10);
39
+ if (!Number.isFinite(n) || n < 1)
40
+ return DEFAULT_MAX_COMMANDS;
41
+ return n;
42
+ };
34
43
  // Resolution timeout — proposed entries auto-cancel if nothing arrives
35
- // within this window. Per AGENTS.md §Phase E.2.
44
+ // within this window. SPEC.md §0.5 (proposal lifecycle) + §13.5 (loop.resolve).
36
45
  const PROPOSAL_TIMEOUT_DEFAULT_MS = 300000;
37
46
  const readProposalTimeoutMs = () => {
38
47
  const raw = process.env.PLURNK_PROPOSAL_TIMEOUT_MS;
@@ -166,11 +175,10 @@ export default class Engine {
166
175
  // rails (cycle detection #39, etc.) — read and reset at end of each turn.
167
176
  // `history` holds per-turn fingerprints for rail #39 cycle detection.
168
177
  #strikeState = new Map();
169
- // Proposal lifecycle (task #42): pending dispatch pauses waiting for
170
- // resolution. Engine.runTurn awaits the promise when a scheme returns
171
- // status 202; Engine.resolveProposal feeds the resolution back in. Map
172
- // is per-log-entry-id; entries clear on resolution. See AGENTS.md
173
- // §Phase E for the broader lifecycle plan.
178
+ // Proposal lifecycle: pending dispatch pauses waiting for resolution.
179
+ // Engine.runTurn awaits the promise when a scheme returns status 202;
180
+ // Engine.resolveProposal feeds the resolution back in. Map is per-log-
181
+ // entry-id; entries clear on resolution. SPEC.md §0.5 + §13.5 (loop.resolve).
174
182
  #pendingProposals = new Map();
175
183
  // External observers of proposal lifecycle events. Daemon subscribes
176
184
  // here to push `loop/proposal` notifications when an entry enters
@@ -186,11 +194,18 @@ export default class Engine {
186
194
  #loopAborts = new Map();
187
195
  #streamEventNotify;
188
196
  #wakeRunNotify;
189
- constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify }) {
197
+ // Telemetry event fan-out: every TelemetryEvent pushed to the loop's
198
+ // buffer is also broadcast live to the connected client(s) on the
199
+ // session. Without this, the client sees `loop/terminated` with a
200
+ // status code but has no way to surface why the loop degraded.
201
+ // Per-grammar 0.17.0 protocol — see SPEC §15.1.
202
+ #telemetryEventNotify;
203
+ constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify }) {
190
204
  this.#db = db;
191
205
  this.#schemes = schemes;
192
206
  this.#streamEventNotify = streamEventNotify;
193
207
  this.#wakeRunNotify = wakeRunNotify;
208
+ this.#telemetryEventNotify = telemetryEventNotify;
194
209
  // Default to empty discovery — standalone Engine construction (in
195
210
  // tests) gets no handlers, and content flows through the framework's
196
211
  // raw-content fitContent fallback. Daemon-managed Engine receives a
@@ -200,12 +215,16 @@ export default class Engine {
200
215
  });
201
216
  this.#previewBudget = readBudget();
202
217
  }
203
- #pushTelemetry(loopId, error) {
218
+ #pushTelemetry(sessionId, loopId, event) {
204
219
  const existing = this.#telemetryBuffer.get(loopId);
205
220
  if (existing === undefined)
206
- this.#telemetryBuffer.set(loopId, [error]);
221
+ this.#telemetryBuffer.set(loopId, [event]);
207
222
  else
208
- existing.push(error);
223
+ existing.push(event);
224
+ // Live fan-out: client sees the event the moment it lands in the
225
+ // model's buffer (not at the next packet build). Same envelope on
226
+ // both sides per the grammar 0.17.0 TelemetryEvent protocol.
227
+ this.#telemetryEventNotify?.(sessionId, { loopId, event });
209
228
  }
210
229
  #drainTelemetry(loopId) {
211
230
  const buf = this.#telemetryBuffer.get(loopId);
@@ -214,7 +233,28 @@ export default class Engine {
214
233
  this.#telemetryBuffer.delete(loopId);
215
234
  return buf;
216
235
  }
217
- async runLoop({ provider, messages, persona = "", sessionId, runId, loopId, maxTurns = 50, maxStrikes = readMaxStrikes(), minCycles = readPositiveInt("PLURNK_MIN_CYCLES", DEFAULT_MIN_CYCLES), maxCyclePeriod = readPositiveInt("PLURNK_MAX_CYCLE_PERIOD", DEFAULT_MAX_CYCLE_PERIOD), origin = "model", signal, onDispatch, }) {
236
+ // Pull a ±windowLines context block around `targetLine` (1-based) from
237
+ // `content`, formatted with N:\t prefixes — same shape the model
238
+ // already knows from READ output and numbered Index entries. Used to
239
+ // give parse_error telemetry concrete locality: the model sees what
240
+ // it wrote on the offending line, not just an abstract error message.
241
+ //
242
+ // Lenient: targetLine ≤ 0 clamps to 1; targetLine beyond content
243
+ // returns whatever overlap exists; empty content returns "".
244
+ #extractSnippet(content, targetLine, windowLines) {
245
+ if (content.length === 0)
246
+ return "";
247
+ const lines = content.split("\n");
248
+ const target = Math.max(1, targetLine);
249
+ const start = Math.max(1, target - windowLines);
250
+ const end = Math.min(lines.length, target + windowLines);
251
+ const slice = [];
252
+ for (let i = start; i <= end; i++) {
253
+ slice.push(`${i}:\t${lines[i - 1] ?? ""}`);
254
+ }
255
+ return slice.join("\n");
256
+ }
257
+ async runLoop({ provider, messages, persona = "", requirements = "", sessionId, runId, loopId, maxTurns = 50, maxStrikes = readMaxStrikes(), minCycles = readPositiveInt("PLURNK_MIN_CYCLES", DEFAULT_MIN_CYCLES), maxCyclePeriod = readPositiveInt("PLURNK_MAX_CYCLE_PERIOD", DEFAULT_MAX_CYCLE_PERIOD), origin = "model", signal, onDispatch, }) {
218
258
  const turnIds = [];
219
259
  const suddenDeathThreshold = maxTurns - maxStrikes;
220
260
  // Per-loop AbortController for scheme-side cancellation propagation.
@@ -261,25 +301,23 @@ export default class Engine {
261
301
  return { turnIds, finalStatus: 499, hitMaxTurns: true, reason: "max_turns" };
262
302
  }
263
303
  const turn = await this.runTurn({
264
- provider, messages, persona, sessionId, runId, loopId, origin, signal, onDispatch,
304
+ provider, messages, persona, requirements, sessionId, runId, loopId, origin, signal, onDispatch,
265
305
  turnNumber: turnIds.length + 1, maxTurns,
266
306
  });
267
307
  turnIds.push(turn.turnId);
268
308
  // Rail #39: cycle detection. Push this turn's fingerprint to
269
309
  // history, scan for repetition patterns. Detection bumps
270
- // turnErrors so the strike system handles abandonment naturally.
310
+ // turnErrors so the strike system handles abandonment
311
+ // naturally — same internal-only role rummy gave it
312
+ // (plugins/error/error.js#verdict). Intentionally NOT a
313
+ // model-facing telemetry kind: model sees the strike pile-up
314
+ // (which IS the actionable signal); cycle is the engine's
315
+ // reason for treating the turn as a failure, not its own alert.
271
316
  const state = this.#strikeState.get(loopId) ?? { streak: 0, turnErrors: 0, history: [] };
272
317
  state.history.push(turn.fingerprint);
273
318
  const cycle = detectCycle(state.history, minCycles, maxCyclePeriod);
274
- if (cycle.detected) {
319
+ if (cycle.detected)
275
320
  state.turnErrors++;
276
- this.#pushTelemetry(loopId, {
277
- kind: "cycle",
278
- period: cycle.period,
279
- cycles: cycle.cycles,
280
- message: "Loop detected",
281
- });
282
- }
283
321
  this.#strikeState.set(loopId, state);
284
322
  // Rail #38: strike accounting. Three sources strike a turn:
285
323
  // 1. recordedFailed — any action-entry at hard failure status
@@ -287,19 +325,19 @@ export default class Engine {
287
325
  // 2. noOps — turn.status === TURN_STATUS_NO_OPS (per #41).
288
326
  // 3. turnErrors — externally bumped by per-turn rails (#39 cycle).
289
327
  // Struck → streak++; clean → streak = 0. Threshold → abandon.
328
+ // Strike accounting is engine-internal bookkeeping. Per rummy
329
+ // precedent (plugins/error/error.js#verdict) and SPEC §15.1
330
+ // policy: model sees errors that happened (parse_error,
331
+ // action_failure), never the engine's accounting about them
332
+ // (strike counts, cycle detection, sudden-death threshold).
333
+ // Surfacing internal state to the model creates a gamification
334
+ // surface — model optimizes for engine metrics rather than
335
+ // task progress.
290
336
  const recordedFailed = turn.statuses.some((s) => s >= 400 && !SOFT_FAILURE_STATUSES.has(s));
291
337
  const noOps = turn.status === TURN_STATUS_NO_OPS;
292
338
  const struck = noOps || recordedFailed || state.turnErrors > 0;
293
339
  if (struck) {
294
340
  state.streak++;
295
- const reason = noOps ? "no_ops" : recordedFailed ? "recorded_failure" : "rail";
296
- this.#pushTelemetry(loopId, {
297
- kind: "strike",
298
- streak: state.streak,
299
- maxStrikes,
300
- reason,
301
- message: `strike ${state.streak}/${maxStrikes} (${reason}); ${maxStrikes - state.streak} before abandonment`,
302
- });
303
341
  if (state.streak >= maxStrikes) {
304
342
  await this.#db.engine_loop_cancel.run({ loop_id: loopId });
305
343
  cleanup("forceful", "strike_threshold");
@@ -311,20 +349,15 @@ export default class Engine {
311
349
  }
312
350
  state.turnErrors = 0;
313
351
  this.#strikeState.set(loopId, state);
314
- // Rail #40: sudden-death soft warning. When the loop enters the
315
- // last maxStrikes-sized window before maxTurns, push a warning
316
- // each turn so the model can wrap up before the hard cancel.
317
- // Soft: no strike, no loop-status change. SPEC §15.1.
352
+ // Sudden-death threshold is engine-internal abandonment
353
+ // happens at maxTurns regardless. Per gamification policy:
354
+ // we don't warn the model that it's nearing our limit.
318
355
  if (turnIds.length >= suddenDeathThreshold && turnIds.length < maxTurns) {
319
- this.#pushTelemetry(loopId, {
320
- kind: "sudden_death",
321
- message: `${turnIds.length} of ${maxTurns}`,
322
- remaining: maxTurns - turnIds.length,
323
- });
356
+ // Threshold tripped; engine bookkeeping only.
324
357
  }
325
358
  }
326
359
  }
327
- async runTurn({ provider, messages, persona = "", sessionId, runId, loopId, origin = "model", signal, onDispatch, turnNumber = 1, maxTurns = 50, }) {
360
+ async runTurn({ provider, messages, persona = "", requirements = "", sessionId, runId, loopId, origin = "model", signal, onDispatch, turnNumber = 1, maxTurns = 50, }) {
328
361
  // === Turn-as-container model ===
329
362
  //
330
363
  // Turn rows are created at runTurn OPEN (status=102, placeholder
@@ -380,8 +413,8 @@ export default class Engine {
380
413
  // queries log_entries scoped to the run — the prompt entry just
381
414
  // written (if turn 1) is part of that query result.
382
415
  const requestPacket = await this.#buildRequestPacket({
383
- initialMessages: messages, persona, runId, loopId,
384
- turnNumber, currentTurnSeq: seq, maxTurns, provider,
416
+ initialMessages: messages, persona, requirements, runId, loopId,
417
+ currentTurnSeq: seq, provider,
385
418
  });
386
419
  const modelMessages = this.#packetToWireMessages(requestPacket);
387
420
  const response = await provider.generate({ messages: modelMessages, signal });
@@ -395,8 +428,23 @@ export default class Engine {
395
428
  // body starting with `//` being interpreted as xpath) silently
396
429
  // drop, the model sees zero ops dispatched, strike-rail fires,
397
430
  // model has no feedback on WHY its emission didn't take effect.
398
- for (const message of parseErrors ?? []) {
399
- this.#pushTelemetry(loopId, { kind: "parse_error", message });
431
+ //
432
+ // Envelope per @plurnk/plurnk-grammar 0.17.0 TelemetryEvent:
433
+ // { source, kind, message, position: { type: "content-offset", line, column } }
434
+ // Plus a `snippet` field (additionalProperties) carrying ±N lines
435
+ // of the assistant's own content around the error line. Without
436
+ // the snippet, the model sees "invalid xpath at 1:0" but can't
437
+ // connect that to what IT wrote — and tends to regenerate the
438
+ // same broken emission. See edit-todo demo for the canonical case.
439
+ for (const { message, line, column, source } of parseErrors ?? []) {
440
+ this.#pushTelemetry(sessionId, loopId, {
441
+ source: "grammar",
442
+ kind: "parse_error",
443
+ message,
444
+ position: { type: "content-offset", line, column },
445
+ snippet: this.#extractSnippet(packetAssistant.content, line, 2),
446
+ parserSource: source,
447
+ });
400
448
  }
401
449
  const opsCount = packetAssistant.ops.length;
402
450
  const sendOp = packetAssistant.ops.findLast((op) => op.op === "SEND" && typeof op.signal === "number");
@@ -422,8 +470,19 @@ export default class Engine {
422
470
  });
423
471
  // Dispatch model ops starting at nextActionIndex (continues the
424
472
  // turn's running counter after any pre-model writes).
473
+ //
474
+ // Max-commands cap: a single emission with more than `maxCommands`
475
+ // ops is the runaway-loop fingerprint observed in pathological cases
476
+ // (html-attrs demo: 635 ops in one turn). Cap dispatches at the
477
+ // configured limit; overflow ops are dropped without per-op log
478
+ // entries (avoids bloating forensics with hundreds of identical refusals)
479
+ // and the model gets a single telemetry signal next packet so it knows
480
+ // its emission was truncated.
481
+ const maxCommands = readMaxCommands();
482
+ const opsToDispatch = packetAssistant.ops.slice(0, maxCommands);
483
+ const droppedCount = opsCount - opsToDispatch.length;
425
484
  const statuses = [];
426
- for (const [i, statement] of packetAssistant.ops.entries()) {
485
+ for (const [i, statement] of opsToDispatch.entries()) {
427
486
  const result = await this.dispatch({
428
487
  statement, sessionId, runId, loopId, turnId,
429
488
  sequence: nextActionIndex + i,
@@ -431,23 +490,30 @@ export default class Engine {
431
490
  });
432
491
  statuses.push(result.status);
433
492
  }
434
- if (opsCount === 0) {
435
- // Rail #41 (revised): per-turn requirement is "emit at least one
436
- // op." Zero ops = actionless failure. SEND specifically is not
437
- // required any of the 9 grammar ops satisfies. Pushed AFTER
438
- // #buildPacket so this turn's drain doesn't consume it.
439
- this.#pushTelemetry(loopId, {
440
- kind: "no_ops",
441
- message: "turn ended without emitting any op; emit at least one operation per turn",
493
+ // max_commands_exceeded IS model-facing: dropped ops are things
494
+ // the model emitted that didn't run it needs to know. Engine
495
+ // bookkeeping (the cap value, our threshold reasoning) stays
496
+ // internal; only the facts of what happened are reported.
497
+ if (droppedCount > 0) {
498
+ this.#pushTelemetry(sessionId, loopId, {
499
+ source: "engine:rail",
500
+ kind: "max_commands_exceeded",
501
+ emitted: opsCount,
502
+ dropped: droppedCount,
442
503
  });
443
504
  }
505
+ // Zero ops is NOT an error to report — the model knows it emitted
506
+ // nothing. Strike accounting (engine-internal) treats it as a
507
+ // struck turn; the model just sees an empty packet next turn.
508
+ // Per SPEC §15.1 gamification policy.
444
509
  return { turnId, status: turnStatus, statuses, fingerprint: fingerprintTurn(packetAssistant.ops) };
445
510
  }
446
511
  // Split the wire-level ProviderResponse into the two destinations:
447
512
  // packet.assistant gets the model's emission (content, ops, reasoning);
448
513
  // Turn columns get the call-metadata (usage, finishReason, model).
449
- // PROVIDERS.md §3.3 text-fragment scraping policy lives here — engine
450
- // owns the parse and the scraping rule, providers stay grammar-unaware.
514
+ // SPEC §2.1 / plurnk-providers#1: text-fragment scraping policy lives
515
+ // here — engine owns the parse and the scraping rule, providers stay
516
+ // grammar-unaware.
451
517
  //
452
518
  // Test-fixture escape hatch: the Mock provider may pre-supply `ops` on
453
519
  // its assistant payload to skip the parse roundtrip. The wire Provider
@@ -458,6 +524,10 @@ export default class Engine {
458
524
  const preParsedOps = assistant.ops;
459
525
  const ops = [];
460
526
  const textFragments = [];
527
+ // Full PlurnkParseError context (line/column/source) is preserved
528
+ // here so runTurn can build TelemetryEvent envelopes per the
529
+ // grammar 0.17.0 protocol — model needs position info to locate
530
+ // its own offending content on the next turn.
461
531
  const parseErrors = [];
462
532
  if (preParsedOps !== undefined) {
463
533
  ops.push(...preParsedOps);
@@ -474,8 +544,13 @@ export default class Engine {
474
544
  }
475
545
  else if (item.kind === "error") {
476
546
  const err = item.error;
477
- const msg = (err && typeof err.message === "string") ? err.message : "parse error";
478
- parseErrors.push(msg);
547
+ if (err instanceof PlurnkParseError) {
548
+ parseErrors.push({ message: err.message, line: err.line, column: err.column, source: err.source });
549
+ }
550
+ else {
551
+ const msg = err?.message ?? "parse error";
552
+ parseErrors.push({ message: msg, line: 0, column: 0, source: "parser" });
553
+ }
479
554
  }
480
555
  }
481
556
  }
@@ -493,10 +568,7 @@ export default class Engine {
493
568
  // and §user) BEFORE the provider call. The same packet object is then
494
569
  // completed with assistant + assistantRaw after the model responds, so
495
570
  // the stored packet and the wire payload share one source of truth.
496
- // Per Packet.json: user.prompt is "Copy of loop.prompt never null on
497
- // a continuation turn"; the turn-N-of-M continuation marker rides on
498
- // user.system_requirements (per-turn rules), NOT a mutated prompt.
499
- async #buildRequestPacket({ initialMessages, persona: defaultPersona, runId, loopId, turnNumber, currentTurnSeq, maxTurns, provider, }) {
571
+ async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, }) {
500
572
  const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
501
573
  const system_definition = byRole("system");
502
574
  // user.prompt sources from the loop's most recent prompt entry first
@@ -516,13 +588,6 @@ export default class Engine {
516
588
  const index = await this.#buildIndex(runId, loopId);
517
589
  const log = await this.#buildLog(runId);
518
590
  const telemetryErrors = await this.#buildTelemetryErrors(loopId, currentTurnSeq);
519
- // Rummy AgentLoop.js #buildContinuationPrompt: literally
520
- // `Turn ${turn}/${maxTurns}`. That's the whole string. The model
521
- // can read the action log to see what it already did; it does
522
- // not need editorial instructions from us about not repeating.
523
- const system_requirements = turnNumber > 1
524
- ? `Turn ${turnNumber}/${maxTurns}`
525
- : "";
526
591
  // Per-section render-cost subtotals via provider's tokenizer.
527
592
  // Engine approximates each section by tokenizing its serialized
528
593
  // form — wire-payload tokens may differ slightly because chat-
@@ -533,13 +598,13 @@ export default class Engine {
533
598
  provider.countTokens(JSON.stringify(index)) +
534
599
  provider.countTokens(JSON.stringify(log));
535
600
  // user.telemetry.budget — shimmed with section-aggregate table.
536
- // Real per-scheme breakdown is on AGENTS.md TODO (provider doesn't
537
- // yet expose getContextSize; user-side token count is below).
538
- const budget = this.#renderBudgetShim(systemTokens, provider, prompt, system_requirements, telemetryErrors);
601
+ // Real per-scheme breakdown is the tokenomics chapter (SPEC.md §14.2);
602
+ // depends on provider.getContextSize and write-time per-row tokens.
603
+ const budget = this.#renderBudgetShim(systemTokens, provider, prompt, telemetryErrors);
539
604
  const userTokens = provider.countTokens(prompt) +
540
- provider.countTokens(system_requirements) +
541
605
  provider.countTokens(JSON.stringify(telemetryErrors)) +
542
- provider.countTokens(budget);
606
+ provider.countTokens(budget) +
607
+ provider.countTokens(requirements);
543
608
  return {
544
609
  system: {
545
610
  tokens: systemTokens,
@@ -552,20 +617,19 @@ export default class Engine {
552
617
  tokens: userTokens,
553
618
  prompt,
554
619
  telemetry: { budget, errors: telemetryErrors },
555
- system_requirements,
620
+ system_requirements: requirements,
556
621
  },
557
622
  };
558
623
  }
559
- // user.telemetry.budget — SHIM. Per AGENTS.md §Open: real per-scheme
560
- // breakdown + context-window "free/percent-of-total" awaits provider
561
- // contract additions (getContextSize). Until then, render the section-
562
- // aggregate counts we already compute so the wire's `# Plurnk System
563
- // Budget` section is non-empty for picking-apart purposes.
564
- #renderBudgetShim(systemTokens, provider, prompt, system_requirements, telemetryErrors) {
624
+ // user.telemetry.budget — SHIM. Real per-scheme breakdown +
625
+ // context-window "free/percent-of-total" is SPEC.md §14.2; depends on
626
+ // provider.getContextSize and write-time per-row tokens. Until then,
627
+ // render the section-aggregate counts we already compute so the wire's
628
+ // `# Plurnk System Budget` section is non-empty for picking-apart purposes.
629
+ #renderBudgetShim(systemTokens, provider, prompt, telemetryErrors) {
565
630
  const userPromptTokens = provider.countTokens(prompt);
566
- const userReqTokens = provider.countTokens(system_requirements);
567
631
  const userErrTokens = provider.countTokens(JSON.stringify(telemetryErrors));
568
- const userTokens = userPromptTokens + userReqTokens + userErrTokens;
632
+ const userTokens = userPromptTokens + userErrTokens;
569
633
  const total = systemTokens + userTokens;
570
634
  const pct = (n) => total === 0 ? "0.0%" : `${((n / total) * 100).toFixed(1)}%`;
571
635
  return [
@@ -618,7 +682,7 @@ export default class Engine {
618
682
  op: r.op,
619
683
  target,
620
684
  status: r.status_rx,
621
- message: typeof parsedRx === "object" && parsedRx !== null && "error" in parsedRx
685
+ error: typeof parsedRx === "object" && parsedRx !== null && "error" in parsedRx
622
686
  ? parsedRx.error
623
687
  : typeof parsedRx === "string" ? parsedRx : "",
624
688
  };
@@ -738,7 +802,7 @@ export default class Engine {
738
802
  result = denial;
739
803
  }
740
804
  else {
741
- // SCHEMES.md §7.1 / §8: action-entry-as-outcome. Scheme-handler
805
+ // SPEC §3.6 + plurnk-schemes#1: action-entry-as-outcome. Scheme-handler
742
806
  // exceptions become the action-entry's outcome (status 500), not a
743
807
  // thrown bubble. The log_entry is the durable record; engine never
744
808
  // skips it. Logging failures (#writeLog throws) are NOT caught —
@@ -773,13 +837,13 @@ export default class Engine {
773
837
  }
774
838
  const logEntryId = await this.#writeLog({ statement, result, runId, loopId, turnId, sequence, origin });
775
839
  onDispatch?.(logEntryId);
776
- // Proposal lifecycle (task #42, AGENTS.md §Phase E). When a scheme
777
- // returns status 202, the entry is written as state='proposed';
840
+ // Proposal lifecycle (SPEC.md §0.5 + §13.5 loop.resolve). When a
841
+ // scheme returns status 202, the entry is written as state='proposed';
778
842
  // dispatch then PAUSES on a per-entry waiter until resolution
779
843
  // arrives via Engine.resolveProposal (from the loop/resolve RPC,
780
- // YOLO listener, or timeout — Phase E.2/E.3 work). The post-
781
- // resolution status replaces 202 in the result the caller sees,
782
- // so runTurn never branches on a pending state.
844
+ // YOLO listener, or timeout). The post-resolution status replaces
845
+ // 202 in the result the caller sees, so runTurn never branches on
846
+ // a pending state.
783
847
  if (result.status === 202) {
784
848
  // Register the resolution waiter SYNCHRONOUSLY before any await
785
849
  // yields. A same-tick resolveProposal() (e.g. from a test that
@@ -1007,8 +1071,8 @@ export default class Engine {
1007
1071
  });
1008
1072
  return { status, outcome, body: resolution.body };
1009
1073
  }
1010
- // SCHEMES.md §8 {§8-writable-by-enforcement}: engine rejects writes whose
1011
- // origin is outside the target scheme's manifest.writableBy.
1074
+ // SPEC §3.6: engine rejects writes whose origin is outside the target
1075
+ // scheme's manifest.writableBy.
1012
1076
  // - Read-side ops (READ, FIND, SHOW, HIDE) are not gated.
1013
1077
  // - SEND broadcast (path=null) has no target scheme; not gated.
1014
1078
  // - COPY: dst scheme writableBy applies.
@@ -1154,8 +1218,8 @@ export default class Engine {
1154
1218
  return { status: 415, error: `mimetype mismatch on channel '${channelName}': ${channelData.mimetype} vs ${expectedMimetype}` };
1155
1219
  }
1156
1220
  }
1157
- // `<L>` source range slicing per AGENTS.md "Resolved ambiguities" §4
1158
- // (symmetric with READ `<L>` — source range, no line-number prefix).
1221
+ // `<L>` source range slicing per SPEC.md §16.9 (symmetric with READ
1222
+ // `<L>` — source range, no line-number prefix).
1159
1223
  // Applied to every channel of the source entry. Binary channels return
1160
1224
  // 415 since line semantics don't apply.
1161
1225
  const lineMarker = statement.lineMarker ?? null;
@@ -1225,9 +1289,36 @@ export default class Engine {
1225
1289
  // consumes on accept. All other statuses are terminal — state =
1226
1290
  // 'resolved' for the common case.
1227
1291
  const isProposed = result.status === 202;
1228
- const attrs = (result.attrs !== undefined && result.attrs !== null)
1229
- ? JSON.stringify(result.attrs)
1230
- : "{}";
1292
+ let attrsObj = (result.attrs !== undefined && result.attrs !== null)
1293
+ ? { ...result.attrs }
1294
+ : {};
1295
+ // EXEC pathname is coordinate-based — the stream entry lives at
1296
+ // exec://<loop_seq>/<turn_seq>/<sequence>/EXEC, mirroring the log
1297
+ // row's own URI at log://<...>/EXEC. The model can correlate the
1298
+ // log meta to its stream output by structural parallelism — no
1299
+ // random slug injection. Coordinate is resolved here because both
1300
+ // log row write and applyResolution need it.
1301
+ if (statement.op === "EXEC") {
1302
+ const seqs = await this.#db.engine_loop_turn_seqs.get({
1303
+ loop_id: loopId, turn_id: turnId,
1304
+ });
1305
+ if (seqs === undefined)
1306
+ throw new Error(`Engine.#writeLog: loop_turn_seqs returned no row for loop=${loopId} turn=${turnId}`);
1307
+ const coordPathname = `${seqs.loop_seq}/${seqs.turn_seq}/${sequence}/EXEC`;
1308
+ target.scheme = "exec";
1309
+ target.pathname = coordPathname;
1310
+ attrsObj.pathname = coordPathname;
1311
+ // Mutate the in-memory result.attrs too: the dispatch path
1312
+ // hands originalResult.attrs to handler.applyResolution after
1313
+ // proposal accept (see #acceptResolution). Both views — the
1314
+ // stored row AND the in-memory proposal — need the same
1315
+ // coordinate-based pathname so applyResolution writes the
1316
+ // entry at exec://<coord>/EXEC.
1317
+ if (result.attrs !== undefined && result.attrs !== null) {
1318
+ result.attrs.pathname = coordPathname;
1319
+ }
1320
+ }
1321
+ const attrs = JSON.stringify(attrsObj);
1231
1322
  const row = await this.#db.engine_insert_log_entry.get({
1232
1323
  run_id: runId,
1233
1324
  loop_id: loopId,