dsh-ptc-plus 0.4.8 → 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 (139) hide show
  1. package/README.en.md +61 -123
  2. package/README.md +61 -123
  3. package/assets/dsh-ptc-plus-banner-en.webp +0 -0
  4. package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
  5. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  6. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  7. package/assets/ptc-plus-settings-en.png +0 -0
  8. package/assets/ptc-plus-settings-zh.png +0 -0
  9. package/client.cjs +42336 -0
  10. package/cordis.patch.yml +13 -2
  11. package/docs/README.md +7 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -179
  13. package/docs/adr/0008-define-agent-operating-contract.md +5 -1
  14. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
  15. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  16. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
  17. package/docs/adr/0019-plugin-settings-and-kill-switch.md +14 -3
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +3 -1
  21. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
  22. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +101 -35
  23. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
  24. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  25. package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
  26. package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
  27. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  28. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  29. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  30. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  31. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  32. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  33. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  34. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  35. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  36. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  37. package/docs/adr/README.md +6 -0
  38. package/docs/architecture.md +25 -27
  39. package/docs/binding-review-placement-design.md +29 -21
  40. package/docs/capability-planning-components.md +93 -0
  41. package/docs/capability-projection.md +7 -0
  42. package/docs/client-ui.md +88 -29
  43. package/docs/client-visual-contract.md +65 -0
  44. package/docs/context-management.md +22 -0
  45. package/docs/context-optimization-plan.md +353 -0
  46. package/docs/context-public-interface-coverage.md +25 -0
  47. package/docs/durability-design.md +31 -14
  48. package/docs/evaluation.md +41 -6
  49. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  50. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  51. package/docs/installation.md +50 -8
  52. package/docs/program-data-plane.md +5 -3
  53. package/docs/publishing.md +5 -5
  54. package/docs/release-coverage.md +73 -0
  55. package/docs/release-notes.md +1 -1
  56. package/docs/review-procedure.md +56 -0
  57. package/docs/runtime-reference.md +48 -16
  58. package/docs/semantic-validation.md +14 -2
  59. package/docs/stateful-binding-updates-proposal.md +2 -2
  60. package/docs/user-bindings.en.md +2 -2
  61. package/docs/user-bindings.md +3 -3
  62. package/docs/value-wire.md +11 -1
  63. package/docs/verification-optimization-work.md +52 -15
  64. package/docs/verification.md +30 -86
  65. package/index.js +147 -23
  66. package/internal/binding-toggle-receipt.js +24 -0
  67. package/internal/capability-command-receipt.js +29 -0
  68. package/internal/capability-environment.js +1247 -0
  69. package/internal/capability-plan.js +285 -0
  70. package/internal/capability-policy.js +96 -0
  71. package/internal/capability-statistics-store.js +180 -0
  72. package/internal/capability-statistics.js +210 -0
  73. package/internal/compiler-platform.js +5 -3
  74. package/internal/compiler-service.js +2 -9
  75. package/internal/config-spec.js +79 -9
  76. package/internal/cordis-tools-owner.js +22 -19
  77. package/internal/direct-surface-owner.js +29 -29
  78. package/internal/dynamic-environment-runtime.js +9 -9
  79. package/internal/edit-transport-owner.js +183 -139
  80. package/internal/execution-provider-owner.js +160 -0
  81. package/internal/execution-seam-compat.js +76 -120
  82. package/internal/isolated-worker.js +12 -8
  83. package/internal/kernel-worker.js +104 -32
  84. package/internal/native-root-dynamic.js +12 -13
  85. package/internal/program-bindings.js +190 -0
  86. package/internal/recovery-tips.js +53 -23
  87. package/internal/rejected-cell-editor.js +8 -8
  88. package/internal/repl-observation-interest.js +11 -3
  89. package/internal/rpc-contract.js +1 -0
  90. package/internal/runtime-bridge-owner.js +351 -82
  91. package/internal/runtime-config.js +58 -0
  92. package/internal/runtime-contexts.js +85 -16
  93. package/internal/runtime-intrinsics.js +1 -0
  94. package/internal/runtime-messages.js +41 -0
  95. package/internal/sdk-projection.js +10 -82
  96. package/internal/session-cell-executor.js +42 -19
  97. package/internal/session-events.js +74 -4
  98. package/internal/session-journal-recovery.js +904 -184
  99. package/internal/session-journal-schema.js +6 -3
  100. package/internal/session-journal.js +31 -2
  101. package/internal/session-log-view.js +189 -43
  102. package/internal/session-runtime.js +175 -23
  103. package/internal/settings-compat.js +23 -6
  104. package/internal/stateful-module-compiler.js +3 -2
  105. package/internal/stateful-module-runtime.js +7 -3
  106. package/internal/stateful-root-runtime.js +9 -4
  107. package/internal/user-binding-authoring.js +2 -7
  108. package/internal/user-bindings-owner.js +47 -7
  109. package/internal/validated-parse-repair.js +1 -1
  110. package/internal/value-wire.js +68 -21
  111. package/internal/worker-cwd-virtualization.js +20 -18
  112. package/internal/worker-realm-surfaces.js +3 -3
  113. package/locale/en.json +6 -0
  114. package/locale/zh.json +6 -0
  115. package/package.json +55 -38
  116. package/src/client-authoring-view.js +286 -168
  117. package/src/client-binding-review.js +4 -1
  118. package/src/client-binding-toggle.js +18 -0
  119. package/src/client-capabilities-view.js +1341 -0
  120. package/src/client-capability-sort.js +29 -0
  121. package/src/client-code-editor.js +6 -0
  122. package/src/client-copy.js +881 -8
  123. package/src/client-feature-gates.js +15 -5
  124. package/src/client-host-compat.js +23 -2
  125. package/src/client-indicator-view.js +4 -6
  126. package/src/client-primitives.js +280 -0
  127. package/src/client-repl-view.js +23 -16
  128. package/src/client-settings-view.js +412 -69
  129. package/src/client-styles.js +193 -41
  130. package/src/client-tool-hover.js +185 -0
  131. package/src/client-tool-view.js +45 -11
  132. package/src/client-transport.js +44 -9
  133. package/src/client-workbench.js +9 -10
  134. package/src/client.js +148 -68
  135. package/assets/ptc-plus-bindings-en.png +0 -0
  136. package/assets/ptc-plus-bindings-zh.png +0 -0
  137. package/assets/ptc-plus-repair-en.png +0 -0
  138. package/assets/ptc-plus-repair-zh.png +0 -0
  139. package/client.js +0 -31857
@@ -8,13 +8,15 @@ import {
8
8
  export const JOURNAL_KEY = 'dshPtcPlus'
9
9
  export const EDIT_TARGET_KEY = 'dshPtcPlusEdit'
10
10
  export const DERIVED_RUN_KEY = 'dshPtcPlusDerivedRun'
11
+ export const POST_EDIT_KEY = 'dshPtcPlusPostEdit'
11
12
  export const REWRITES_KEY = 'dshPtcPlusRewrites'
12
13
  export const RECOVERY_BOUNDARY_KEY = 'dshPtcPlusRecoveryBoundaries'
13
14
  export const IMPORT_BOUNDARY_JOURNAL_VERSION = 7
14
15
  export const PER_NAME_USER_BINDINGS_JOURNAL_VERSION = 8
15
16
  export const LANGUAGE_SEMANTICS_JOURNAL_VERSION = 9
16
17
  export const MODULE_TRANSFORM_JOURNAL_VERSION = 10
17
- export const JOURNAL_VERSION = MODULE_TRANSFORM_JOURNAL_VERSION
18
+ export const EXECUTION_DIRECTORY_JOURNAL_VERSION = 11
19
+ export const JOURNAL_VERSION = EXECUTION_DIRECTORY_JOURNAL_VERSION
18
20
  export const LIVE_USER_BINDINGS_SHADOW_POLICY = 'per-name'
19
21
  export const LEGACY_USER_BINDINGS_SHADOW_POLICY = 'whole-entry'
20
22
  export const LIVE_USER_BINDINGS_REUSE_POLICY = 'implementation-v1'
@@ -32,7 +34,8 @@ export const BINDING_MODES = new Set(['loose', 'strict'])
32
34
  export const WHOLE_ENTRY_JOURNAL_FIELDS = new Set(['version', 'bindingPolicy', 'rewritePolicy', 'moduleSemantics', 'userBindingsFingerprint', 'userBindingsReusePolicy', 'status', 'calls', 'operations', 'confirms', 'diagnostics', 'completion', 'volatileReason'])
33
35
  export const PER_NAME_JOURNAL_FIELDS = new Set([...WHOLE_ENTRY_JOURNAL_FIELDS, 'userBindingsShadowPolicy', 'userBindingNames'])
34
36
  export const LANGUAGE_SEMANTICS_JOURNAL_FIELDS = new Set([...PER_NAME_JOURNAL_FIELDS, 'languageSemantics'])
35
- export const JOURNAL_FIELDS = new Set([...LANGUAGE_SEMANTICS_JOURNAL_FIELDS, 'moduleTransform'])
37
+ export const MODULE_TRANSFORM_JOURNAL_FIELDS = new Set([...LANGUAGE_SEMANTICS_JOURNAL_FIELDS, 'moduleTransform'])
38
+ export const JOURNAL_FIELDS = new Set([...MODULE_TRANSFORM_JOURNAL_FIELDS, 'executionCwd'])
36
39
  export const FINGERPRINT_REUSE_JOURNAL_FIELDS = new Set([...WHOLE_ENTRY_JOURNAL_FIELDS].filter(field => field !== 'userBindingsReusePolicy'))
37
40
  export const RELATIONLESS_JOURNAL_FIELDS = new Set([...FINGERPRINT_REUSE_JOURNAL_FIELDS].filter(field => field !== 'userBindingsFingerprint'))
38
41
  export const PREDECESSOR_JOURNAL_FIELDS = new Set(['version', 'bindingMode', 'rewritePolicy', 'status', 'calls', 'operations', 'confirms', 'diagnostics', 'completion', 'volatileReason'])
@@ -58,7 +61,7 @@ export const ERROR_FIELDS = new Set(['kind', 'message'])
58
61
  export const EDIT_TARGET_FIELDS = new Set(['targetCallSeq'])
59
62
  export const DERIVED_RUN_FIELDS = new Set(['code', 'description'])
60
63
 
61
- export const JOURNAL_VERSIONS = new Set([LEGACY_JOURNAL_VERSION, INTERMEDIATE_JOURNAL_VERSION, PREVIOUS_JOURNAL_VERSION, USER_BINDING_RELATIONLESS_JOURNAL_VERSION, FINGERPRINT_REUSE_JOURNAL_VERSION, VERSIONED_BINDING_REUSE_JOURNAL_VERSION, IMPORT_BOUNDARY_JOURNAL_VERSION, PER_NAME_USER_BINDINGS_JOURNAL_VERSION, LANGUAGE_SEMANTICS_JOURNAL_VERSION, JOURNAL_VERSION])
64
+ export const JOURNAL_VERSIONS = new Set([LEGACY_JOURNAL_VERSION, INTERMEDIATE_JOURNAL_VERSION, PREVIOUS_JOURNAL_VERSION, USER_BINDING_RELATIONLESS_JOURNAL_VERSION, FINGERPRINT_REUSE_JOURNAL_VERSION, VERSIONED_BINDING_REUSE_JOURNAL_VERSION, IMPORT_BOUNDARY_JOURNAL_VERSION, PER_NAME_USER_BINDINGS_JOURNAL_VERSION, LANGUAGE_SEMANTICS_JOURNAL_VERSION, MODULE_TRANSFORM_JOURNAL_VERSION, JOURNAL_VERSION])
62
65
  export const USER_BINDINGS_REUSE_POLICIES = new Set([LEGACY_USER_BINDINGS_REUSE_POLICY, LIVE_USER_BINDINGS_REUSE_POLICY])
63
66
  export const USER_BINDINGS_SHADOW_POLICIES = new Set([LEGACY_USER_BINDINGS_SHADOW_POLICY, LIVE_USER_BINDINGS_SHADOW_POLICY])
64
67
  const USER_BINDING_NAME_STATES = new Set(['provider', 'local', 'absent', 'unknown'])
@@ -1,4 +1,5 @@
1
1
  import { isDeepStrictEqual } from 'node:util'
2
+ import { posix, win32 } from 'node:path'
2
3
  import { decodeValue, encodeValue, normalizeValueWire } from './value-wire.js'
3
4
  import { normalizeDiagnostic } from './diagnostic.js'
4
5
  import { assertOwnFields, isRecord } from './record-utils.js'
@@ -19,6 +20,8 @@ import {
19
20
  REWRITES_KEY,
20
21
  RECOVERY_BOUNDARY_KEY,
21
22
  JOURNAL_VERSION,
23
+ MODULE_TRANSFORM_JOURNAL_VERSION,
24
+ MODULE_TRANSFORM_JOURNAL_FIELDS,
22
25
  LANGUAGE_SEMANTICS_JOURNAL_VERSION,
23
26
  LANGUAGE_SEMANTICS_JOURNAL_FIELDS,
24
27
  IMPORT_BOUNDARY_JOURNAL_VERSION,
@@ -262,6 +265,10 @@ function normalizeUserBindingsFingerprint(value) {
262
265
 
263
266
  function migrateJournal(value, resolveLegacyConfirm) {
264
267
  if (value.version === JOURNAL_VERSION) return value
268
+ if (value.version === MODULE_TRANSFORM_JOURNAL_VERSION) {
269
+ assertOwnFields(value, MODULE_TRANSFORM_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
270
+ return { ...value, version: JOURNAL_VERSION, executionCwd: null }
271
+ }
265
272
  if (value.version === LANGUAGE_SEMANTICS_JOURNAL_VERSION) {
266
273
  assertOwnFields(value, LANGUAGE_SEMANTICS_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
267
274
  const languageSemantics = normalizeLanguageSemantics(value.languageSemantics)
@@ -332,6 +339,24 @@ function migrateJournal(value, resolveLegacyConfirm) {
332
339
  }
333
340
  }
334
341
 
342
+ /**
343
+ * A recorded execution directory belongs to the execution world that produced
344
+ * the journal, not to the platform reading it. Judge both absolute spellings so
345
+ * a Windows Host journal stays valid evidence inside a POSIX audit checkout
346
+ * (and the reverse), and never rewrite the recorded bytes.
347
+ */
348
+ function isRecordedExecutionDirectory(value) {
349
+ return typeof value === 'string' && value.length > 0
350
+ && (posix.isAbsolute(value) || win32.isAbsolute(value))
351
+ }
352
+
353
+ function normalizeExecutionCwd(value) {
354
+ if (value !== null && !isRecordedExecutionDirectory(value)) {
355
+ throw new Error('invalid dsh-ptc-plus journal execution directory')
356
+ }
357
+ return value
358
+ }
359
+
335
360
  /** Validate and detach one journal emitted by the runtime. */
336
361
  export function normalizeJournal(value, options = {}) {
337
362
  if (!isRecord(value)) throw new Error('invalid dsh-ptc-plus journal')
@@ -339,10 +364,12 @@ export function normalizeJournal(value, options = {}) {
339
364
  || !STATUSES.has(value.status)) {
340
365
  throw new Error('invalid dsh-ptc-plus journal')
341
366
  }
342
- const migrated = migrateJournal(value, options.resolveLegacyConfirm)
367
+ const migrated = { ...migrateJournal(value, options.resolveLegacyConfirm),
368
+ ...(value.version < JOURNAL_VERSION ? { executionCwd: null } : {}) }
343
369
  assertOwnFields(migrated, JOURNAL_FIELDS, 'dsh-ptc-plus journal')
344
370
  const languageSemantics = normalizeLanguageSemantics(migrated.languageSemantics)
345
371
  const moduleTransform = normalizeModuleTransform(migrated.moduleTransform, languageSemantics)
372
+ const executionCwd = normalizeExecutionCwd(migrated.executionCwd)
346
373
  const bindingPolicy = normalizeBindingPolicy(migrated.bindingPolicy)
347
374
  const rewritePolicy = normalizeRewritePolicy(migrated.rewritePolicy)
348
375
  const moduleSemantics = normalizeModuleSemantics(migrated.moduleSemantics)
@@ -374,6 +401,7 @@ export function normalizeJournal(value, options = {}) {
374
401
  version: JOURNAL_VERSION,
375
402
  languageSemantics,
376
403
  moduleTransform,
404
+ executionCwd,
377
405
  bindingPolicy,
378
406
  rewritePolicy,
379
407
  moduleSemantics,
@@ -1022,7 +1050,7 @@ export function liveToolCallSeq(session, callId, toolName) {
1022
1050
 
1023
1051
  /** Start a mutable journal for one live cell. */
1024
1052
  export function createJournal(confirms = [], bindingPolicy, rewritePolicy, languageSemantics = LEGACY_LANGUAGE_SEMANTICS,
1025
- moduleTransform = moduleTransformForLanguage(languageSemantics)) {
1053
+ moduleTransform = moduleTransformForLanguage(languageSemantics), executionCwd = null) {
1026
1054
  if (typeof bindingPolicy === 'string') {
1027
1055
  if (!BINDING_MODES.has(bindingPolicy)) throw new TypeError('invalid dsh-ptc-plus journal binding mode')
1028
1056
  bindingPolicy = {
@@ -1035,6 +1063,7 @@ export function createJournal(confirms = [], bindingPolicy, rewritePolicy, langu
1035
1063
  version: JOURNAL_VERSION,
1036
1064
  languageSemantics,
1037
1065
  moduleTransform: normalizeModuleTransform(moduleTransform, languageSemantics),
1066
+ executionCwd: normalizeExecutionCwd(executionCwd),
1038
1067
  bindingPolicy: normalizeBindingPolicy(bindingPolicy),
1039
1068
  rewritePolicy: normalizeRewritePolicy(rewritePolicy),
1040
1069
  moduleSemantics: normalizeModuleSemantics(LIVE_MODULE_SEMANTICS),
@@ -1,9 +1,12 @@
1
1
  import {
2
- foldSessionTimeline,
2
+ advanceSessionTimeline, createSessionTimelineState, replaySessionTimelineFact, validateSessionTimelineState,
3
3
  } from './session-journal-recovery.js'
4
4
  import { isCanonicalSequence } from './session-journal.js'
5
- import { sessionEvents } from './session-events.js'
5
+ import { advanceSessionSurface, readSessionLog, sessionEvents } from './session-events.js'
6
6
  import { readRuntimeMessage } from './runtime-messages.js'
7
+ import { deepEqualJson, isJsonValue } from '@deepseek-ai/dsh-util-values'
8
+ import { deepFreeze, isRecord } from './record-utils.js'
9
+ import { advanceRuntimeHistory, createRuntimeHistory } from './recovery-tips.js'
7
10
 
8
11
  import { isHostRuntimeContextSource } from './message-sources.js'
9
12
  const SYSTEM_PROMPT_CLEARED = 'Current runtime context: none. Earlier runtime-context snapshots no longer apply.'
@@ -35,30 +38,185 @@ function isContextStep(event) {
35
38
  || (event?.type === 'user/message' && event.data?.source?.kind === 'user')
36
39
  }
37
40
 
38
- function cordisTranscriptFacts(timeline) {
39
- let calls = 0
40
- let inspections = 0
41
- for (const result of timeline.results.values()) {
42
- for (const call of result?.journal?.calls ?? []) {
43
- if (call.global !== 'tools' || typeof call.member !== 'string'
44
- || !call.member.startsWith('cordis_')) continue
45
- calls += 1
46
- if (call.ok === true && call.member.startsWith('cordis_inspect')) inspections += 1
41
+ export const SESSION_LOG_PROJECTION_KEY = 'ptcPlusSessionLog'
42
+
43
+ function emptyLogState() {
44
+ return { timeline: createSessionTimelineState(), contextStep: 0,
45
+ runtimeHistory: createRuntimeHistory(), visibleRuntimeFacts: {},
46
+ systemPromptSnapshots: [], ptcMessages: [], runtimeMessagesBySeq: {}, visibleRuntimeMessages: [], surfaceNodes: [], logFacts: [] }
47
+ }
48
+
49
+ export function runtimeMessageFacts(records, initial = {}) {
50
+ const facts = { ...initial }
51
+ for (const record of records) {
52
+ if (record.producer === 'aggregate') facts.aggregate = record
53
+ else if (record.form === 'snapshot') facts.snapshot = record
54
+ else if (record.form === 'catalog') facts.catalog = record
55
+ }
56
+ return facts
57
+ }
58
+
59
+ function presentationEvent(event) {
60
+ const recognized = event?.type === 'user/message'
61
+ && (systemPromptSnapshotSections(event.data) !== undefined || readRuntimeMessage(event.data) !== undefined)
62
+ const data = recognized ? { source: event.data.source, content: event.data.content }
63
+ : event?.type === 'user/message' && typeof event.data?.source?.kind === 'string'
64
+ ? { source: { kind: event.data.source.kind } } : undefined
65
+ return JSON.parse(JSON.stringify({ seq: event.seq, type: event.type,
66
+ ...(data === undefined ? {} : { data }),
67
+ ...(event.surfaceOp === undefined ? {} : { surfaceOp: event.surfaceOp }),
68
+ }))
69
+ }
70
+
71
+ function applyLogEvent(state, event, index = event?.seq,
72
+ timeline = advanceSessionTimeline(state.timeline, event, index)) {
73
+ const sections = event?.type === 'user/message' ? systemPromptSnapshotSections(event.data) : undefined
74
+ const ptc = event?.type === 'user/message' ? readRuntimeMessage(event.data) : undefined
75
+ const contextStep = state.contextStep + (isContextStep(event) ? 1 : 0)
76
+ const surfaceNodes = advanceSessionSurface(state.surfaceNodes, event)
77
+ let visibleRuntimeMessages = state.visibleRuntimeMessages
78
+ if (timeline === state.timeline && sections === undefined && ptc === undefined && contextStep === state.contextStep
79
+ && surfaceNodes === state.surfaceNodes) return state
80
+ const next = { ...state, timeline, contextStep,
81
+ logFacts: [...state.logFacts, deepFreeze({ index, event: presentationEvent(event) })],
82
+ }
83
+ next.runtimeHistory = advanceRuntimeHistory(state.runtimeHistory, {
84
+ sections, notice: ptc?.form === 'notice' ? ptc.name : undefined,
85
+ snapshot: ptc?.form === 'snapshot', catalog: ptc?.form === 'catalog',
86
+ index, contextStep: state.contextStep, lastSuccessfulRunIndex: timeline.lastSuccessfulRunIndex,
87
+ resetSuccess: timeline.lastSuccessfulRunIndex !== state.timeline.lastSuccessfulRunIndex,
88
+ })
89
+ if (sections !== undefined) {
90
+ const snapshot = Object.freeze({ index, contextStep: state.contextStep, sections })
91
+ next.systemPromptSnapshots = [...state.systemPromptSnapshots, snapshot]
92
+ next.runtimeMessagesBySeq = { ...state.runtimeMessagesBySeq,
93
+ [`k${event.seq}`]: Object.freeze({ ...snapshot, producer: 'aggregate', form: 'snapshot' }) }
94
+ }
95
+ if (ptc !== undefined) {
96
+ const record = Object.freeze({ ...ptc, index, contextStep: state.contextStep, producer: 'ptc-plus' })
97
+ next.ptcMessages = [...state.ptcMessages, record]
98
+ next.runtimeMessagesBySeq = { ...next.runtimeMessagesBySeq, [`k${event.seq}`]: record }
99
+ }
100
+ if (event?.surfaceOp === 'append') {
101
+ const record = next.runtimeMessagesBySeq[`k${event.seq}`]
102
+ if (record !== undefined) {
103
+ visibleRuntimeMessages = [...visibleRuntimeMessages, record]
104
+ next.visibleRuntimeFacts = runtimeMessageFacts([record], state.visibleRuntimeFacts)
47
105
  }
106
+ } else if (surfaceNodes !== state.surfaceNodes) {
107
+ visibleRuntimeMessages = surfaceNodes.flatMap(seq => {
108
+ const record = next.runtimeMessagesBySeq[`k${seq}`]
109
+ return record === undefined ? [] : [record]
110
+ })
111
+ next.visibleRuntimeFacts = runtimeMessageFacts(visibleRuntimeMessages)
48
112
  }
49
- return Object.freeze({ calls, inspections })
113
+ next.surfaceNodes = surfaceNodes
114
+ next.visibleRuntimeMessages = Object.freeze(visibleRuntimeMessages)
115
+ return next
116
+ }
117
+
118
+ export function createSessionLogProjection() {
119
+ return Object.freeze({
120
+ key: SESSION_LOG_PROJECTION_KEY,
121
+ stateVersion: 9,
122
+ stateSchema: Object.freeze({ parse(value) {
123
+ if (!isJsonValue(value) || !isRecord(value) || !isRecord(value.timeline)
124
+ || typeof value.timeline.openTurn !== 'boolean' || typeof value.timeline.found !== 'boolean'
125
+ || !Number.isSafeInteger(value.timeline.scope) || value.timeline.scope < 0
126
+ || !Number.isSafeInteger(value.contextStep) || value.contextStep < 0
127
+ || !Array.isArray(value.systemPromptSnapshots) || !Array.isArray(value.ptcMessages)
128
+ || !Array.isArray(value.visibleRuntimeMessages)
129
+ || !isRecord(value.runtimeHistory) || !isRecord(value.runtimeHistory.tips)
130
+ || !isRecord(value.runtimeHistory.seenNames) || !isRecord(value.visibleRuntimeFacts)
131
+ || !Array.isArray(value.surfaceNodes) || !value.surfaceNodes.every(isCanonicalSequence)
132
+ || new Set(value.surfaceNodes).size !== value.surfaceNodes.length
133
+ || !Array.isArray(value.timeline.calls) || !Array.isArray(value.timeline.boundaries)
134
+ || !isRecord(value.runtimeMessagesBySeq) || !isRecord(value.timeline.cordisTranscript)) {
135
+ throw new TypeError('invalid PTC session-log projection checkpoint')
136
+ }
137
+ validateSessionTimelineState(value.timeline)
138
+ if (!Array.isArray(value.logFacts)) throw new TypeError('invalid PTC session-log projection checkpoint facts')
139
+ const timelineFacts = new Map(value.timeline.eventFacts.map(fact => [fact.eventIndex, fact]))
140
+ let reconstructed = emptyLogState()
141
+ let lastIndex = -1
142
+ let timelineCount = 0
143
+ for (const fact of value.logFacts) {
144
+ if (!isRecord(fact) || Object.keys(fact).some(key => !['index', 'event'].includes(key))
145
+ || !isCanonicalSequence(fact.index) || fact.index <= lastIndex
146
+ || !isRecord(fact.event) || !isCanonicalSequence(fact.event.seq) || typeof fact.event.type !== 'string'
147
+ || !deepEqualJson(presentationEvent(fact.event), fact.event)) {
148
+ throw new TypeError('invalid PTC session-log projection checkpoint facts')
149
+ }
150
+ lastIndex = fact.index
151
+ const timelineFact = timelineFacts.get(fact.index)
152
+ if (timelineFact !== undefined) {
153
+ if (!deepEqualJson(presentationEvent(timelineFact.event), fact.event)) {
154
+ throw new TypeError('inconsistent PTC session-log projection checkpoint event identity')
155
+ }
156
+ timelineCount += 1
157
+ }
158
+ if (timelineFact === undefined
159
+ && advanceSessionTimeline(reconstructed.timeline, fact.event, fact.index) !== reconstructed.timeline) {
160
+ throw new TypeError('inconsistent PTC session-log projection checkpoint missing timeline facts')
161
+ }
162
+ const timeline = timelineFact === undefined ? reconstructed.timeline
163
+ : replaySessionTimelineFact(reconstructed.timeline, timelineFact)
164
+ reconstructed = applyLogEvent(reconstructed, fact.event, fact.index, timeline)
165
+ }
166
+ if (timelineCount !== timelineFacts.size || !deepEqualJson(reconstructed, value)) {
167
+ throw new TypeError('inconsistent PTC session-log projection checkpoint facts')
168
+ }
169
+ return deepFreeze(value)
170
+ } }),
171
+ init: emptyLogState,
172
+ apply: applyLogEvent,
173
+ })
174
+ }
175
+
176
+ export function createSessionLogOwner(ctx) {
177
+ const projection = createSessionLogProjection()
178
+ let registeredService
179
+ const injection = ctx.inject(['sessionProjections'], scope => {
180
+ try {
181
+ scope.effect(() => {
182
+ const registry = scope.sessionProjections
183
+ const unregister = registry.register(projection)
184
+ if (typeof unregister !== 'function') throw new Error('ptc-plus: sessionProjections.register did not return a disposer')
185
+ registeredService = registry
186
+ return async () => {
187
+ await unregister()
188
+ if (registeredService === registry) registeredService = undefined
189
+ }
190
+ }, 'ptc-plus host-only session-log projection')
191
+ } catch (error) {
192
+ ctx.logger?.warn?.('ptc-plus: session-log projection unavailable', error)
193
+ }
194
+ })
195
+ ctx.effect(() => () => typeof injection === 'function' ? injection() : injection?.dispose?.(),
196
+ 'ptc-plus session-log projection injection lifecycle')
197
+ const service = name => typeof ctx.get === 'function' ? ctx.get(name) : ctx[name]
198
+ return Object.freeze({
199
+ read: (session, signal) => readSessionLog(session, service('sessionQuery'), signal),
200
+ project(agent, requestedEdit) {
201
+ if (Array.isArray(agent?.session?.events)) return projectSessionLog(agent, requestedEdit)
202
+ const registry = registeredService ?? service('sessionProjections')
203
+ const state = agent?.session === undefined ? undefined : registry?.stateOf?.(agent.session, SESSION_LOG_PROJECTION_KEY)
204
+ return projectSessionLog(agent, requestedEdit, state)
205
+ },
206
+ })
50
207
  }
51
208
 
52
209
  /** Project session events into immutable facts consumed by prompt presentation. */
53
- export function projectSessionLog(agent, requestedEdit) {
54
- const events = sessionEvents(agent?.session)
55
- if (!Array.isArray(events)) {
210
+ export function projectSessionLog(agent, requestedEdit, projectedState) {
211
+ const events = projectedState === undefined ? sessionEvents(agent?.session) : undefined
212
+ if (projectedState === undefined && !Array.isArray(events)) {
56
213
  return Object.freeze({
57
214
  openTurn: false,
58
215
  contextStep: 0,
59
216
  systemPromptSnapshots: Object.freeze([]),
60
217
  ptcMessages: Object.freeze([]),
61
218
  visibleRuntimeMessages: undefined,
219
+ runtimeHistory: Object.freeze(createRuntimeHistory()),
62
220
  lastSuccessfulRunIndex: undefined,
63
221
  latestRun: undefined,
64
222
  editableRun: undefined,
@@ -66,45 +224,30 @@ export function projectSessionLog(agent, requestedEdit) {
66
224
  cordisTranscript: Object.freeze({ calls: 0, inspections: 0 }),
67
225
  })
68
226
  }
69
- let contextStep = 0
70
- const systemPromptSnapshots = []
71
- const ptcMessages = []
72
- const runtimeMessagesBySeq = new Map()
73
- for (let index = 0; index < events.length; index += 1) {
74
- const event = events[index]
75
- const sections = event?.type === 'user/message' ? systemPromptSnapshotSections(event.data) : undefined
76
- if (sections !== undefined) {
77
- const snapshot = Object.freeze({ index, contextStep, sections })
78
- systemPromptSnapshots.push(snapshot)
79
- runtimeMessagesBySeq.set(event.seq, Object.freeze({ ...snapshot, producer: 'aggregate', form: 'snapshot' }))
80
- }
81
- const ptc = event?.type === 'user/message' ? readRuntimeMessage(event.data) : undefined
82
- if (ptc !== undefined) {
83
- const record = Object.freeze({ ...ptc, index, contextStep, producer: 'ptc-plus' })
84
- ptcMessages.push(record)
85
- runtimeMessagesBySeq.set(event.seq, record)
86
- }
87
- if (isContextStep(event)) contextStep += 1
227
+ let state = projectedState ?? emptyLogState()
228
+ if (projectedState === undefined) {
229
+ for (let index = 0; index < events.length; index += 1) state = applyLogEvent(state, events[index], index)
88
230
  }
89
- const timeline = foldSessionTimeline(events)
231
+ const { timeline, contextStep, systemPromptSnapshots, ptcMessages, runtimeMessagesBySeq } = state
90
232
  const requestedEntry = requestedEdit === undefined
91
233
  ? undefined
92
- : timeline.executableCalls.get(requestedEdit.callSeq)
234
+ : timeline.executableCalls[`k${requestedEdit.callSeq}`]?.[1]
93
235
  const requestedEditTarget = requestedEdit !== undefined
94
236
  && requestedEntry?.event.data?.callId === requestedEdit.callId
95
237
  && requestedEntry.event.data.name === 'edit_run_code'
96
- ? timeline.editTargets.get(requestedEdit.callSeq)
238
+ ? timeline.editTargets[`k${requestedEdit.callSeq}`]?.[1] ?? undefined
97
239
  : undefined
98
240
  const latestRun = timeline.openTurn ? timeline.latestRun : undefined
99
241
  const editableRun = timeline.openTurn ? timeline.editableRun : undefined
100
- const cordisTranscript = cordisTranscriptFacts(timeline)
101
- let visibleRuntimeMessages
102
- try {
242
+ const cordisTranscript = Object.freeze(timeline.cordisTranscript)
243
+ let visibleRuntimeMessages = projectedState === undefined ? undefined : state.visibleRuntimeMessages
244
+ if (projectedState === undefined) try {
103
245
  const nodes = agent?.session?.surface?.nodes
104
- const knownSeqs = new Set(events.map(event => event?.seq))
105
- if (Array.isArray(nodes) && nodes.every(seq => isCanonicalSequence(seq) && knownSeqs.has(seq))) {
246
+ const knownSeqs = events === undefined ? undefined : new Set(events.map(event => event?.seq))
247
+ if (Array.isArray(nodes) && nodes.every(seq => isCanonicalSequence(seq)
248
+ && (knownSeqs === undefined ? seq < agent.session.seq : knownSeqs.has(seq)))) {
106
249
  visibleRuntimeMessages = Object.freeze(nodes.flatMap(seq => {
107
- const record = runtimeMessagesBySeq.get(seq)
250
+ const record = runtimeMessagesBySeq[`k${seq}`]
108
251
  return record === undefined ? [] : [record]
109
252
  }))
110
253
  }
@@ -115,6 +258,9 @@ export function projectSessionLog(agent, requestedEdit) {
115
258
  systemPromptSnapshots: Object.freeze(systemPromptSnapshots),
116
259
  ptcMessages: Object.freeze(ptcMessages),
117
260
  visibleRuntimeMessages,
261
+ visibleRuntimeFacts: projectedState === undefined && visibleRuntimeMessages !== undefined
262
+ ? runtimeMessageFacts(visibleRuntimeMessages) : state.visibleRuntimeFacts,
263
+ runtimeHistory: state.runtimeHistory,
118
264
  lastSuccessfulRunIndex: timeline.lastSuccessfulRunIndex,
119
265
  latestRun,
120
266
  editableRun,