@plurnk/plurnk-service 0.4.0 → 0.5.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 +548 -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 +169 -73
  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 +16 -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":"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"}
@@ -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":"AACA,OAAO,KAAK,EAAE,eAAe,EAA4D,MAAM,wBAAwB,CAAC;AACxH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAiB,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAG9C,OAAO,KAAK,EAAmD,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAkD1E,KAAK,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEvD,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAI9E,OAAO,KAAK,EAAE,QAAQ,EAAsD,MAAM,uBAAuB,CAAC;AAyC1G,KAAK,eAAe,GAAG;IACnB,SAAS,EAAE,eAAe,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,CAAC;AAEF,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAOjF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9D,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAK3B,IAAI,CAAC,EAAE,MAAM,CAAC;IAKd,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAYD,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;CACpB;AAyGD,eAAO,MAAM,eAAe,GAAI,KAAK,aAAa,CAAC,eAAe,CAAC,KAAG,MAErE,CAAC;AAMF,eAAO,MAAM,WAAW,GACpB,SAAS,aAAa,CAAC,MAAM,CAAC,EAC9B,WAAW,MAAM,EACjB,gBAAgB,MAAM,KACvB;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAexE,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,MAAM;;gBAqCX,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,EAAE;QACtE,EAAE,EAAE,EAAE,CAAC;QACP,OAAO,EAAE,cAAc,CAAC;QACxB,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;QACtC,aAAa,CAAC,EAAE,aAAa,CAAC;KACjC;IA4BK,OAAO,CAAC,EACV,QAAQ,EAAE,QAAQ,EAAE,OAAY,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAC1D,QAAa,EAAE,UAA6B,EAC5C,SAAoE,EACpE,cAAqF,EACrF,MAAgB,EAAE,MAAM,EAAE,UAAU,GACvC,EAAE;QACC,QAAQ,EAAE,QAAQ,CAAC;QACnB,QAAQ,EAAE,WAAW,EAAE,CAAC;QAIxB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;KAC7C,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB,GAAG,UAAU,GAAG,IAAI,CAAA;KAAE,CAAC;IAoHrI,OAAO,CAAC,EACV,QAAQ,EAAE,QAAQ,EAAE,OAAY,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAgB,EAAE,MAAM,EAAE,UAAU,EAChG,UAAc,EAAE,QAAa,GAChC,EAAE;QACC,QAAQ,EAAE,QAAQ,CAAC;QACnB,QAAQ,EAAE,WAAW,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QACjD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;QAK1C,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAyclF,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAyJjE,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAYzE,kBAAkB,IAAI,MAAM,EAAE;IAQxB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBpD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAChD;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAC7C;IA8BD,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI;CA4V3E"}
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,7 +301,7 @@ 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);
@@ -273,11 +313,10 @@ export default class Engine {
273
313
  const cycle = detectCycle(state.history, minCycles, maxCyclePeriod);
274
314
  if (cycle.detected) {
275
315
  state.turnErrors++;
276
- this.#pushTelemetry(loopId, {
316
+ this.#pushTelemetry(sessionId, loopId, {
277
317
  kind: "cycle",
278
318
  period: cycle.period,
279
319
  cycles: cycle.cycles,
280
- message: "Loop detected",
281
320
  });
282
321
  }
283
322
  this.#strikeState.set(loopId, state);
@@ -293,12 +332,11 @@ export default class Engine {
293
332
  if (struck) {
294
333
  state.streak++;
295
334
  const reason = noOps ? "no_ops" : recordedFailed ? "recorded_failure" : "rail";
296
- this.#pushTelemetry(loopId, {
335
+ this.#pushTelemetry(sessionId, loopId, {
297
336
  kind: "strike",
298
337
  streak: state.streak,
299
338
  maxStrikes,
300
339
  reason,
301
- message: `strike ${state.streak}/${maxStrikes} (${reason}); ${maxStrikes - state.streak} before abandonment`,
302
340
  });
303
341
  if (state.streak >= maxStrikes) {
304
342
  await this.#db.engine_loop_cancel.run({ loop_id: loopId });
@@ -316,15 +354,14 @@ export default class Engine {
316
354
  // each turn so the model can wrap up before the hard cancel.
317
355
  // Soft: no strike, no loop-status change. SPEC §15.1.
318
356
  if (turnIds.length >= suddenDeathThreshold && turnIds.length < maxTurns) {
319
- this.#pushTelemetry(loopId, {
357
+ this.#pushTelemetry(sessionId, loopId, {
320
358
  kind: "sudden_death",
321
- message: `${turnIds.length} of ${maxTurns}`,
322
359
  remaining: maxTurns - turnIds.length,
323
360
  });
324
361
  }
325
362
  }
326
363
  }
327
- async runTurn({ provider, messages, persona = "", sessionId, runId, loopId, origin = "model", signal, onDispatch, turnNumber = 1, maxTurns = 50, }) {
364
+ async runTurn({ provider, messages, persona = "", requirements = "", sessionId, runId, loopId, origin = "model", signal, onDispatch, turnNumber = 1, maxTurns = 50, }) {
328
365
  // === Turn-as-container model ===
329
366
  //
330
367
  // Turn rows are created at runTurn OPEN (status=102, placeholder
@@ -380,8 +417,8 @@ export default class Engine {
380
417
  // queries log_entries scoped to the run — the prompt entry just
381
418
  // written (if turn 1) is part of that query result.
382
419
  const requestPacket = await this.#buildRequestPacket({
383
- initialMessages: messages, persona, runId, loopId,
384
- turnNumber, currentTurnSeq: seq, maxTurns, provider,
420
+ initialMessages: messages, persona, requirements, runId, loopId,
421
+ currentTurnSeq: seq, provider,
385
422
  });
386
423
  const modelMessages = this.#packetToWireMessages(requestPacket);
387
424
  const response = await provider.generate({ messages: modelMessages, signal });
@@ -395,8 +432,23 @@ export default class Engine {
395
432
  // body starting with `//` being interpreted as xpath) silently
396
433
  // drop, the model sees zero ops dispatched, strike-rail fires,
397
434
  // 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 });
435
+ //
436
+ // Envelope per @plurnk/plurnk-grammar 0.17.0 TelemetryEvent:
437
+ // { source, kind, message, position: { type: "content-offset", line, column } }
438
+ // Plus a `snippet` field (additionalProperties) carrying ±N lines
439
+ // of the assistant's own content around the error line. Without
440
+ // the snippet, the model sees "invalid xpath at 1:0" but can't
441
+ // connect that to what IT wrote — and tends to regenerate the
442
+ // same broken emission. See edit-todo demo for the canonical case.
443
+ for (const { message, line, column, source } of parseErrors ?? []) {
444
+ this.#pushTelemetry(sessionId, loopId, {
445
+ source: "grammar",
446
+ kind: "parse_error",
447
+ message,
448
+ position: { type: "content-offset", line, column },
449
+ snippet: this.#extractSnippet(packetAssistant.content, line, 2),
450
+ parserSource: source,
451
+ });
400
452
  }
401
453
  const opsCount = packetAssistant.ops.length;
402
454
  const sendOp = packetAssistant.ops.findLast((op) => op.op === "SEND" && typeof op.signal === "number");
@@ -422,8 +474,19 @@ export default class Engine {
422
474
  });
423
475
  // Dispatch model ops starting at nextActionIndex (continues the
424
476
  // turn's running counter after any pre-model writes).
477
+ //
478
+ // Max-commands cap: a single emission with more than `maxCommands`
479
+ // ops is the runaway-loop fingerprint observed in pathological cases
480
+ // (html-attrs demo: 635 ops in one turn). Cap dispatches at the
481
+ // configured limit; overflow ops are dropped without per-op log
482
+ // entries (avoids bloating forensics with hundreds of identical refusals)
483
+ // and the model gets a single telemetry signal next packet so it knows
484
+ // its emission was truncated.
485
+ const maxCommands = readMaxCommands();
486
+ const opsToDispatch = packetAssistant.ops.slice(0, maxCommands);
487
+ const droppedCount = opsCount - opsToDispatch.length;
425
488
  const statuses = [];
426
- for (const [i, statement] of packetAssistant.ops.entries()) {
489
+ for (const [i, statement] of opsToDispatch.entries()) {
427
490
  const result = await this.dispatch({
428
491
  statement, sessionId, runId, loopId, turnId,
429
492
  sequence: nextActionIndex + i,
@@ -431,14 +494,21 @@ export default class Engine {
431
494
  });
432
495
  statuses.push(result.status);
433
496
  }
497
+ if (droppedCount > 0) {
498
+ this.#pushTelemetry(sessionId, loopId, {
499
+ kind: "max_commands_exceeded",
500
+ emitted: opsCount,
501
+ cap: maxCommands,
502
+ dropped: droppedCount,
503
+ });
504
+ }
434
505
  if (opsCount === 0) {
435
506
  // Rail #41 (revised): per-turn requirement is "emit at least one
436
507
  // op." Zero ops = actionless failure. SEND specifically is not
437
508
  // required — any of the 9 grammar ops satisfies. Pushed AFTER
438
509
  // #buildPacket so this turn's drain doesn't consume it.
439
- this.#pushTelemetry(loopId, {
510
+ this.#pushTelemetry(sessionId, loopId, {
440
511
  kind: "no_ops",
441
- message: "turn ended without emitting any op; emit at least one operation per turn",
442
512
  });
443
513
  }
444
514
  return { turnId, status: turnStatus, statuses, fingerprint: fingerprintTurn(packetAssistant.ops) };
@@ -446,8 +516,9 @@ export default class Engine {
446
516
  // Split the wire-level ProviderResponse into the two destinations:
447
517
  // packet.assistant gets the model's emission (content, ops, reasoning);
448
518
  // 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.
519
+ // SPEC §2.1 / plurnk-providers#1: text-fragment scraping policy lives
520
+ // here — engine owns the parse and the scraping rule, providers stay
521
+ // grammar-unaware.
451
522
  //
452
523
  // Test-fixture escape hatch: the Mock provider may pre-supply `ops` on
453
524
  // its assistant payload to skip the parse roundtrip. The wire Provider
@@ -458,6 +529,10 @@ export default class Engine {
458
529
  const preParsedOps = assistant.ops;
459
530
  const ops = [];
460
531
  const textFragments = [];
532
+ // Full PlurnkParseError context (line/column/source) is preserved
533
+ // here so runTurn can build TelemetryEvent envelopes per the
534
+ // grammar 0.17.0 protocol — model needs position info to locate
535
+ // its own offending content on the next turn.
461
536
  const parseErrors = [];
462
537
  if (preParsedOps !== undefined) {
463
538
  ops.push(...preParsedOps);
@@ -474,8 +549,13 @@ export default class Engine {
474
549
  }
475
550
  else if (item.kind === "error") {
476
551
  const err = item.error;
477
- const msg = (err && typeof err.message === "string") ? err.message : "parse error";
478
- parseErrors.push(msg);
552
+ if (err instanceof PlurnkParseError) {
553
+ parseErrors.push({ message: err.message, line: err.line, column: err.column, source: err.source });
554
+ }
555
+ else {
556
+ const msg = err?.message ?? "parse error";
557
+ parseErrors.push({ message: msg, line: 0, column: 0, source: "parser" });
558
+ }
479
559
  }
480
560
  }
481
561
  }
@@ -493,10 +573,7 @@ export default class Engine {
493
573
  // and §user) BEFORE the provider call. The same packet object is then
494
574
  // completed with assistant + assistantRaw after the model responds, so
495
575
  // 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, }) {
576
+ async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, }) {
500
577
  const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
501
578
  const system_definition = byRole("system");
502
579
  // user.prompt sources from the loop's most recent prompt entry first
@@ -516,13 +593,6 @@ export default class Engine {
516
593
  const index = await this.#buildIndex(runId, loopId);
517
594
  const log = await this.#buildLog(runId);
518
595
  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
596
  // Per-section render-cost subtotals via provider's tokenizer.
527
597
  // Engine approximates each section by tokenizing its serialized
528
598
  // form — wire-payload tokens may differ slightly because chat-
@@ -533,13 +603,13 @@ export default class Engine {
533
603
  provider.countTokens(JSON.stringify(index)) +
534
604
  provider.countTokens(JSON.stringify(log));
535
605
  // 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);
606
+ // Real per-scheme breakdown is the tokenomics chapter (SPEC.md §14.2);
607
+ // depends on provider.getContextSize and write-time per-row tokens.
608
+ const budget = this.#renderBudgetShim(systemTokens, provider, prompt, telemetryErrors);
539
609
  const userTokens = provider.countTokens(prompt) +
540
- provider.countTokens(system_requirements) +
541
610
  provider.countTokens(JSON.stringify(telemetryErrors)) +
542
- provider.countTokens(budget);
611
+ provider.countTokens(budget) +
612
+ provider.countTokens(requirements);
543
613
  return {
544
614
  system: {
545
615
  tokens: systemTokens,
@@ -552,20 +622,19 @@ export default class Engine {
552
622
  tokens: userTokens,
553
623
  prompt,
554
624
  telemetry: { budget, errors: telemetryErrors },
555
- system_requirements,
625
+ system_requirements: requirements,
556
626
  },
557
627
  };
558
628
  }
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) {
629
+ // user.telemetry.budget — SHIM. Real per-scheme breakdown +
630
+ // context-window "free/percent-of-total" is SPEC.md §14.2; depends on
631
+ // provider.getContextSize and write-time per-row tokens. Until then,
632
+ // render the section-aggregate counts we already compute so the wire's
633
+ // `# Plurnk System Budget` section is non-empty for picking-apart purposes.
634
+ #renderBudgetShim(systemTokens, provider, prompt, telemetryErrors) {
565
635
  const userPromptTokens = provider.countTokens(prompt);
566
- const userReqTokens = provider.countTokens(system_requirements);
567
636
  const userErrTokens = provider.countTokens(JSON.stringify(telemetryErrors));
568
- const userTokens = userPromptTokens + userReqTokens + userErrTokens;
637
+ const userTokens = userPromptTokens + userErrTokens;
569
638
  const total = systemTokens + userTokens;
570
639
  const pct = (n) => total === 0 ? "0.0%" : `${((n / total) * 100).toFixed(1)}%`;
571
640
  return [
@@ -618,7 +687,7 @@ export default class Engine {
618
687
  op: r.op,
619
688
  target,
620
689
  status: r.status_rx,
621
- message: typeof parsedRx === "object" && parsedRx !== null && "error" in parsedRx
690
+ error: typeof parsedRx === "object" && parsedRx !== null && "error" in parsedRx
622
691
  ? parsedRx.error
623
692
  : typeof parsedRx === "string" ? parsedRx : "",
624
693
  };
@@ -738,7 +807,7 @@ export default class Engine {
738
807
  result = denial;
739
808
  }
740
809
  else {
741
- // SCHEMES.md §7.1 / §8: action-entry-as-outcome. Scheme-handler
810
+ // SPEC §3.6 + plurnk-schemes#1: action-entry-as-outcome. Scheme-handler
742
811
  // exceptions become the action-entry's outcome (status 500), not a
743
812
  // thrown bubble. The log_entry is the durable record; engine never
744
813
  // skips it. Logging failures (#writeLog throws) are NOT caught —
@@ -773,13 +842,13 @@ export default class Engine {
773
842
  }
774
843
  const logEntryId = await this.#writeLog({ statement, result, runId, loopId, turnId, sequence, origin });
775
844
  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';
845
+ // Proposal lifecycle (SPEC.md §0.5 + §13.5 loop.resolve). When a
846
+ // scheme returns status 202, the entry is written as state='proposed';
778
847
  // dispatch then PAUSES on a per-entry waiter until resolution
779
848
  // 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.
849
+ // YOLO listener, or timeout). The post-resolution status replaces
850
+ // 202 in the result the caller sees, so runTurn never branches on
851
+ // a pending state.
783
852
  if (result.status === 202) {
784
853
  // Register the resolution waiter SYNCHRONOUSLY before any await
785
854
  // yields. A same-tick resolveProposal() (e.g. from a test that
@@ -1007,8 +1076,8 @@ export default class Engine {
1007
1076
  });
1008
1077
  return { status, outcome, body: resolution.body };
1009
1078
  }
1010
- // SCHEMES.md §8 {§8-writable-by-enforcement}: engine rejects writes whose
1011
- // origin is outside the target scheme's manifest.writableBy.
1079
+ // SPEC §3.6: engine rejects writes whose origin is outside the target
1080
+ // scheme's manifest.writableBy.
1012
1081
  // - Read-side ops (READ, FIND, SHOW, HIDE) are not gated.
1013
1082
  // - SEND broadcast (path=null) has no target scheme; not gated.
1014
1083
  // - COPY: dst scheme writableBy applies.
@@ -1154,8 +1223,8 @@ export default class Engine {
1154
1223
  return { status: 415, error: `mimetype mismatch on channel '${channelName}': ${channelData.mimetype} vs ${expectedMimetype}` };
1155
1224
  }
1156
1225
  }
1157
- // `<L>` source range slicing per AGENTS.md "Resolved ambiguities" §4
1158
- // (symmetric with READ `<L>` — source range, no line-number prefix).
1226
+ // `<L>` source range slicing per SPEC.md §16.9 (symmetric with READ
1227
+ // `<L>` — source range, no line-number prefix).
1159
1228
  // Applied to every channel of the source entry. Binary channels return
1160
1229
  // 415 since line semantics don't apply.
1161
1230
  const lineMarker = statement.lineMarker ?? null;
@@ -1225,9 +1294,36 @@ export default class Engine {
1225
1294
  // consumes on accept. All other statuses are terminal — state =
1226
1295
  // 'resolved' for the common case.
1227
1296
  const isProposed = result.status === 202;
1228
- const attrs = (result.attrs !== undefined && result.attrs !== null)
1229
- ? JSON.stringify(result.attrs)
1230
- : "{}";
1297
+ let attrsObj = (result.attrs !== undefined && result.attrs !== null)
1298
+ ? { ...result.attrs }
1299
+ : {};
1300
+ // EXEC pathname is coordinate-based — the stream entry lives at
1301
+ // exec://<loop_seq>/<turn_seq>/<sequence>/EXEC, mirroring the log
1302
+ // row's own URI at log://<...>/EXEC. The model can correlate the
1303
+ // log meta to its stream output by structural parallelism — no
1304
+ // random slug injection. Coordinate is resolved here because both
1305
+ // log row write and applyResolution need it.
1306
+ if (statement.op === "EXEC") {
1307
+ const seqs = await this.#db.engine_loop_turn_seqs.get({
1308
+ loop_id: loopId, turn_id: turnId,
1309
+ });
1310
+ if (seqs === undefined)
1311
+ throw new Error(`Engine.#writeLog: loop_turn_seqs returned no row for loop=${loopId} turn=${turnId}`);
1312
+ const coordPathname = `${seqs.loop_seq}/${seqs.turn_seq}/${sequence}/EXEC`;
1313
+ target.scheme = "exec";
1314
+ target.pathname = coordPathname;
1315
+ attrsObj.pathname = coordPathname;
1316
+ // Mutate the in-memory result.attrs too: the dispatch path
1317
+ // hands originalResult.attrs to handler.applyResolution after
1318
+ // proposal accept (see #acceptResolution). Both views — the
1319
+ // stored row AND the in-memory proposal — need the same
1320
+ // coordinate-based pathname so applyResolution writes the
1321
+ // entry at exec://<coord>/EXEC.
1322
+ if (result.attrs !== undefined && result.attrs !== null) {
1323
+ result.attrs.pathname = coordPathname;
1324
+ }
1325
+ }
1326
+ const attrs = JSON.stringify(attrsObj);
1231
1327
  const row = await this.#db.engine_insert_log_entry.get({
1232
1328
  run_id: runId,
1233
1329
  loop_id: loopId,