dimcode-darwin-x64 0.1.0-alpha.16 → 0.1.0-alpha.17

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 (26) hide show
  1. package/bin/dimcode +2 -2
  2. package/default-plugins.json +0 -1
  3. package/package.json +1 -1
  4. package/plugins/scheduler/README.md +4 -9
  5. package/plugins/scheduler/data.ts +8 -29
  6. package/plugins/scheduler/settings.ts +0 -9
  7. package/plugins/workflow/README.md +22 -19
  8. package/plugins/workflow/store.ts +61 -18
  9. package/release-manifest.json +9 -26
  10. package/plugins/session-title/README.md +0 -76
  11. package/plugins/session-title/dim-plugin.json +0 -11
  12. package/plugins/session-title/index.ts +0 -13
  13. package/plugins/session-title/node_modules/.package-lock.json +0 -18
  14. package/plugins/session-title/node_modules/@arcships/rutis/README.md +0 -32
  15. package/plugins/session-title/node_modules/@arcships/rutis/package.json +0 -46
  16. package/plugins/session-title/node_modules/@arcships/rutis/src/advanced.d.ts +0 -90
  17. package/plugins/session-title/node_modules/@arcships/rutis/src/advanced.mjs +0 -32
  18. package/plugins/session-title/node_modules/@arcships/rutis/src/index.d.ts +0 -43
  19. package/plugins/session-title/node_modules/@arcships/rutis/src/index.mjs +0 -71
  20. package/plugins/session-title/node_modules/@arcships/rutis/src/testing.d.ts +0 -77
  21. package/plugins/session-title/node_modules/@arcships/rutis/src/testing.mjs +0 -350
  22. package/plugins/session-title/node_modules/@arcships/rutis/src/values.mjs +0 -261
  23. package/plugins/session-title/package-lock.json +0 -45
  24. package/plugins/session-title/package.json +0 -9
  25. package/plugins/session-title/part.ts +0 -172
  26. package/plugins/session-title/settings.ts +0 -49
package/bin/dimcode CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/dimcode
3
- size: 98714040 bytes
4
- sha256: 70eb60ca30debdd2e226d5c2914da6a56f6e67cc6fe6d6d6ca00eff8fad4b03d
3
+ size: 104311000 bytes
4
+ sha256: bbeb81fb9b8bc26ca3e2447036592629025ea0c193c02f2f1cf68a2f821a4bc6
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "plugins": {
3
3
  "goal": "plugins/goal",
4
- "session-title": "plugins/session-title",
5
4
  "workflow": "plugins/workflow",
6
5
  "scheduler": "plugins/scheduler",
7
6
  "plugin-authoring": "plugins/plugin-authoring"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dimcode-darwin-x64",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.17",
4
4
  "description": "Dim native CLI and App Server",
5
5
  "license": "SEE LICENSE IN NOTICE",
6
6
  "repository": {
@@ -114,7 +114,7 @@ Run from the repository root, using Node 24:
114
114
  ```sh
115
115
  npm ci --prefix runtime/plugins/scheduler --ignore-scripts
116
116
  node runtime/node_modules/typescript/bin/tsc -p runtime/plugins/scheduler/tsconfig.json
117
- node --test runtime/plugins/scheduler/scheduler.test.mjs
117
+ cargo test --locked -p dim-app-server --test transport_flow node_scheduler -- --ignored
118
118
  ```
119
119
 
120
120
  Before native flows, reinstall the local packages using the CI
@@ -124,12 +124,8 @@ package copy; source edits alone do not refresh it.
124
124
 
125
125
  | Public flow | Evidence |
126
126
  | --- | --- |
127
- | Save takeover, CAS failure, live permissions, shared cap/reuse vectors, tool restrictions and retirement | SDK service flows in `scheduler.test.mjs` |
128
- | Cron DST gap/fold, enabled anchor, missed windows and one-off disable | SDK flows with a controlled wall clock |
129
- | Held/consumed/applied input deadlines, uncertain withdrawal, late launch/reuse replies and revision rechecks | SDK execution flows |
130
- | Restart lookup/history, single-attempt reports, admitted-report cleanup and deletion retry | SDK persistence/lifetime flows |
131
127
  | Actual source defaults/protected fields, tool save, later live widening bounded by the saved cap, non-interactive execution, takeover, reporting and inner/outer-v1 records | `node_scheduler_saves_live_authority_runs_noninteractive_and_reports_to_the_latest_saver` |
132
- | An untitled interactive leader adopts its saving operator input before the raw Steer report; subsequent automatic title generation sends the original operator text to the real auxiliary HTTP binding and publishes its title | `node_scheduler_reports_preserve_the_first_adopted_operator_title` |
128
+ | An untitled interactive leader adopts its saving operator input before the raw Steer report; subsequent automatic title generation sends the original operator text to the real auxiliary HTTP binding and publishes its title | [`scheduler_reports_do_not_replace_the_first_operator_input_for_titles`](../../../crates/dim-session-title/tests/scheduler_flow.rs) |
133
129
  | Required invalid-record startup, unchanged bytes, failed optional owner and usable native Sessions | `node_scheduler_invalid_inner_records_preserve_bytes_and_do_not_block_native_sessions` |
134
130
  | Agents index replacement keeps the owner and run loaded; owner reload preserves pending approval, input, deadline and anchor; completion/report occur once | `node_scheduler_owner_reload_tracks_the_existing_native_run_without_replaying_input_or_reports` |
135
131
  | Native timer windows, DST gap/fold, enabled-anchor changes, one-off disable and missed-window collapse | `node_scheduler_native_clock_preserves_anchors_collapses_gaps_and_handles_dst_once` |
@@ -141,7 +137,7 @@ package copy; source edits alone do not refresh it.
141
137
  | Real filesystem deletion/replace failures preserve the last ownership record and retry with no jobs; external Sessions survive | `node_scheduler_retention_retries_real_delete_and_record_failures_with_no_jobs` |
142
138
  | Fresh and pre-interrupt history prefixes preserve applied inputs without model consumption, deletion or another stop; later reuse consumption cannot change the terminal record | `node_scheduler_applied_inputs_interrupted_before_consumption_expire_without_another_stop` |
143
139
  | A stale history prefix followed by genuine notHeld re-reads native deletion, completion or consumption before deciding | `node_scheduler_not_held_rereads_native_deletion_completion_and_consumption` |
144
- | All shared cap and reuse vectors pass real authorizing saves, native launch policies and reused-policy checks | `node_scheduler_shared_policy_vectors_reach_native_save_launch_and_reuse` |
140
+ | Shared cap/reuse vectors and unrepresentable saves use real native policies; default-policy groups share one host while job identities stay separate | `node_scheduler_shared_policy_vectors_reach_native_save_launch_and_reuse` |
145
141
  | Selected/other job edits, one-CAS conflict, frozen accepted prompt/authority/report target and accepted same-revision local writes | `node_scheduler_job_edits_cas_and_local_source_writes_preserve_accepted_snapshots` |
146
142
  | Both job origins preserve completed/failed/cancelled/timed-out/skipped/missed history; only selected tool outcomes send one Steer report with model-visible identities, and reload sends none | `node_scheduler_reports_only_selected_tool_outcomes_with_model_visible_identity` |
147
143
  | Closed/deleted report targets and a discarded reply after actual native publication preserve the outcome, record failure once and never redirect or resend after reload | `node_scheduler_failed_reports_keep_the_outcome_and_never_retry_or_redirect` |
@@ -153,8 +149,7 @@ A prefix captured before a user interrupt can permit a withdrawal command at the
153
149
  InputQueue returns `notHeld` for the applied input; the command does not imply an `input/deleted`
154
150
  fact. The native check preserves this distinction and still requires no model call or extra stop.
155
151
 
156
- The SDK suite exercises the public process/Session services; the native matrix
157
- runs with the repository's public Node binding setup in CI, including the required
152
+ The native matrix exercises the public process/Session services and runs with the repository's public Node binding setup in CI, including the required
158
153
  invalid-record case. The test fixture wraps the real package's public ports
159
154
  and uses Node's built-in test clock. Native SQLite publication and filesystem
160
155
  permissions supply the report and retention faults; production interfaces are
@@ -9,10 +9,9 @@ export class ScheduleError extends Error {
9
9
 
10
10
  export const id = z.string().min(1)
11
11
  export const preset = z.enum(['read-only', 'workspace-write', 'full-access'])
12
- const fileAccess = z.enum(['read-only', 'workspace-read-write', 'unrestricted'])
13
- export const policy = z.strictObject({ preset, fileAccess: fileAccess.optional(),
14
- toolApproval: z.enum(['ask', 'bypass']).optional(),
15
- allowedTools: z.array(z.string()), disallowedTools: z.array(z.string()) }) satisfies z.ZodType<PermissionPolicy>
12
+ export const policy = z.object({
13
+ preset, allowedTools: z.array(z.string()), disallowedTools: z.array(z.string()),
14
+ }) satisfies z.ZodType<PermissionPolicy>
16
15
  export const timestamp = z.iso.datetime({ precision: 3 }).max(24)
17
16
  export const address = z.strictObject({ session_id: id, branch_id: id })
18
17
  const cronField = '[0-9*/,-]+'
@@ -46,37 +45,17 @@ export type JobPatch = z.infer<typeof managementJob>
46
45
  /** The same conservative policy cap used by S2; lists are sets. */
47
46
  export function cap(requested: PermissionPolicy, bound: PermissionPolicy) {
48
47
  const ranks = preset.options
49
- const legacyReadOnly = [requested, bound].some(value => approval(value) === 'legacy-read-only')
50
- if (legacyReadOnly && [requested, bound].some(value => value.toolApproval === 'ask')) {
51
- throw new ScheduleError('schedule_invalid', 'Explicit Ask cannot be capped by legacy read-only tool rules')
52
- }
53
48
  const disallowedTools = [...new Set([...requested.disallowedTools, ...bound.disallowedTools])]
49
+ const allows = (value: PermissionPolicy, name: string) =>
50
+ value.preset === 'full-access' || value.allowedTools.includes(name)
54
51
  const allowedTools = [...new Set([...requested.allowedTools, ...bound.allowedTools])].filter(name =>
55
- (approval(requested) === 'bypass' || requested.allowedTools.includes(name))
56
- && (approval(bound) === 'bypass' || bound.allowedTools.includes(name)) && !disallowedTools.includes(name))
57
- return { preset: ranks[Math.min(ranks.indexOf(requested.preset), ranks.indexOf(bound.preset))],
58
- ...(requested.fileAccess !== undefined || bound.fileAccess !== undefined ? {
59
- fileAccess: fileAccess.options[Math.min(fileRank(requested), fileRank(bound))],
60
- } : {}),
61
- ...(!legacyReadOnly && (requested.toolApproval !== undefined || bound.toolApproval !== undefined) ? {
62
- toolApproval: approval(requested) === 'bypass' && approval(bound) === 'bypass' ? 'bypass' as const : 'ask' as const,
63
- } : {}), allowedTools, disallowedTools } satisfies PermissionPolicy
64
- }
65
-
66
- function fileRank(value: PermissionPolicy): number {
67
- return value.fileAccess === undefined ? preset.options.indexOf(value.preset) : fileAccess.options.indexOf(value.fileAccess)
68
- }
69
-
70
- function approval(value: PermissionPolicy) {
71
- return value.toolApproval ?? (value.preset === 'full-access' ? 'bypass' : value.preset === 'read-only' ? 'legacy-read-only' : 'ask')
52
+ allows(requested, name) && allows(bound, name) && !disallowedTools.includes(name))
53
+ return { preset: ranks[Math.min(ranks.indexOf(requested.preset), ranks.indexOf(bound.preset))], allowedTools, disallowedTools } satisfies PermissionPolicy
72
54
  }
73
55
 
74
56
  export function within(actual: PermissionPolicy, bound: PermissionPolicy): boolean {
75
- let capped: PermissionPolicy
76
- try { capped = cap(actual, bound) }
77
- catch (error) { if (error instanceof ScheduleError) return false; throw error }
57
+ const capped = cap(actual, bound)
78
58
  return actual.preset === capped.preset
79
- && fileRank(actual) === fileRank(capped) && approval(actual) === approval(capped)
80
59
  && [actual.allowedTools, actual.disallowedTools].every((names, index) => {
81
60
  const result = index === 0 ? capped.allowedTools : capped.disallowedTools
82
61
  return new Set(names).size === result.length && names.every(name => result.includes(name))
@@ -139,14 +139,6 @@ export const settings: SourceDeclaration = {
139
139
  "full-access"
140
140
  ]
141
141
  },
142
- "fileAccess": {
143
- "type": "string",
144
- "enum": ["read-only", "workspace-read-write", "unrestricted"]
145
- },
146
- "toolApproval": {
147
- "type": "string",
148
- "enum": ["ask", "bypass"]
149
- },
150
142
  "allowedTools": {
151
143
  "type": "array",
152
144
  "items": {
@@ -165,7 +157,6 @@ export const settings: SourceDeclaration = {
165
157
  "allowedTools",
166
158
  "disallowedTools"
167
159
  ],
168
- "additionalProperties": false
169
160
  },
170
161
  "created_by": {
171
162
  "anyOf": [
@@ -103,13 +103,20 @@ An `ambiguous` reply (duplicates written before branches accepted each id once)
103
103
  delivered and records a violation. Missing destinations are undeliverable.
104
104
 
105
105
  Run storage is `workflow/runs.json`, with inner `{version:2,runs:[...]}` inside the host's S10 v1
106
- wrapper. Runs admitted under the bounds below keep the text within the 8 MiB payload as the file
106
+ wrapper. `workflow/runs.version` holds the token of the last committed store write: a running stage's
107
+ one-second wait reads that token (36 bytes) and reuses the owner's snapshot, so its per-second cost
108
+ does not grow with the store; only a changed token, startup recovery and every write read the store
109
+ itself. Runs admitted under the bounds below keep the text within the 8 MiB payload as the file
107
110
  service carries it (the JSON string, where escapes can double): a start projects the store with
108
111
  every active run's record, the stage identities, `agent_id` copies, results and logs it can gain
109
112
  later, plus the new record; if that exceeds the payload it first expires the oldest settled
110
113
  terminal runs and otherwise returns `limit_exceeded` without persisting. The retained store,
111
114
  therefore, keeps each read, write and `workflow.status` reply within one bridge frame; a record
112
- committed before these bounds keeps its original size. Invalid, v1,
115
+ committed before these bounds keeps its original size. The token is written in its own transaction
116
+ after the store replace, so a crash between them leaves it naming older text; recovery reads the
117
+ store and does not depend on it. A failed token read or write is a storage failure, a failed store
118
+ access drops the snapshot so committed bytes stay visible, and a poll reaches the frame with the
119
+ token, never the store. Invalid, v1,
113
120
  unknown-version or missing-marker records return `invalid_data` and preserve their bytes. There is
114
121
  no migration. After a crash, unfinished runs become interrupted/lost without relaunch; a lost
115
122
  stage's running turn or pending approval is left untouched. Only orderly unload stops stages under
@@ -126,7 +133,7 @@ changes that accepted input/terminal-fact contract.
126
133
 
127
134
  ```sh
128
135
  node runtime/node_modules/typescript/bin/tsc -p runtime/plugins/workflow/tsconfig.json
129
- node --test runtime/plugins/workflow/workflow.test.mjs
136
+ cargo test --locked -p dim-app-server --test transport_flow node_workflow -- --ignored
130
137
  ```
131
138
 
132
139
  Actual Node/App Server flows are in
@@ -134,10 +141,10 @@ Actual Node/App Server flows are in
134
141
  `DIM_TEST_NODE`, `DIM_TEST_JS_RUNTIME` and `DIM_TEST_RUTIS_SDK` settings and disposable homes.
135
142
  They exercise stage Sessions, published reports, resubmission across row/runtime replacement, crashes and failed
136
143
  record replacement. Native retention flows use actual Session deletion, file read/replace failures,
137
- fixed-row replacement and the existing one-minute maintenance timer. The acceptance mapping below
138
- separates native coverage from SDK coverage.
144
+ fixed-row replacement and the real maintenance callback. The fixture advances Node interval timers
145
+ without waiting one minute per pass; stage I/O, SQLite publication and filesystem faults remain real.
139
146
  The read-failure retry case needs a valid startup restore before making the run
140
- file unreadable. It retains two real timer passes; corrupting that file before
147
+ file unreadable. It advances both maintenance passes; corrupting that file before
141
148
  row activation would exercise startup failure instead of retention recovery.
142
149
  The existing [#126](https://github.com/arcships/dim/issues/126) idle-unload work must land before
143
150
  shipping repeated stage creation. A launch whose reply is lost before its Session id is persisted
@@ -145,22 +152,18 @@ can leave an unidentified Session; it is not relaunched or recovered by a new re
145
152
 
146
153
  ## Acceptance coverage
147
154
 
148
- The [SDK flows](workflow.test.mjs) execute the actual modules through their declared ports.
149
155
  The [native flows](../../../crates/dim-app-server/tests/transport_flow/workflow.rs) use real
150
- Node rows, Settings, file authority, S2 calls, publication and Session history. This maps to
151
- [Workflow Appendix D](../../../docs/design/plugins/Workflow.md#appendix-d-acceptance):
156
+ Node rows, Settings, file authority, S2 calls, publication and Session history.
152
157
 
153
- | Items | Evidence |
158
+ | Public behavior | Evidence |
154
159
  | --- | --- |
155
- | 1 | SDK definition, template, parameter, frozen-run and whole-step overflow flows; native source identities, generic Settings writes and model-visible diagnostics |
156
- | 2, 5 | Native start + wait batch: report wakes wait, adoption follows the batch result, and interrupting the starter leaves accepted run work alive; native starter closure makes its report undeliverable |
157
- | 3, 4 | Native held, consumed/approval and completed-turn stop cases; SDK applied-before-consumption and concurrent-withdrawal cases; repeated stop and no-report assertions |
158
- | 6 | Native project default-disabled/client enablement, editing a running definition and the next run reading the edit |
159
- | 7 | Native running-stage Node crash, row/runtime report replacement, complete host restart and Agents-index reload; no stage relaunch |
160
- | 8–11 | Native live first-admission narrowing with the sibling launch gated, cap persistence before siblings, starter loss, non-interactive/default-profile stage Sessions with and without `workflow` in the mount list, ReadOnly denial and handler refusal when it is declared |
161
- | 12 | Native retention keeps the latest 100 terminal records, newer active/pending work and retained references; SDK flows prove that older active/pending records also survive the terminal limit and that a start expires older settled records for frame space; user forks and external Sessions survive owned-parent deletion. Real deletion/read/atomic-replace failures preserve ownership, minute maintenance retries without active runs or definitions, and Node restart reconciles deletion before record removal. Native terminal/report-settlement triggers continue with an unreadable definition source; native source CAS and invalid-file preservation are also covered |
162
- | 13 | Native resubmission across row/runtime replacement with the append held, before/after-input crashes, failed atomic replacement, a resubmitted report written once and host restart; SDK rejected, failed, ambiguous, reloading and publication-unknown replies |
163
- | 14 | SDK definition, user-document, run-store and listing payload flows: over-1-MiB definitions are rejected, a user save over the 8 MiB document is rejected without writing, quote/backslash/control-character results and logs are measured in their committed JSON form and a run's results stop at 512 KiB, `agent_id` copies are charged, an over-payload active admission returns `limit_exceeded` without persisting, a start expires older settled records to fit, the listing is sent once and stops at its payload with `truncated`, and the store and status replies stay within 8 MiB. Native inner-v2 records remain decodable |
160
+ | Definition and parameter admission, source identities, project enablement, source-file preservation and whole-parallel-step prompt rejection | `node_workflow_sources_stages_policy_and_report_use_the_real_package`, `node_workflow_project_enablement_invalid_sources_and_tool_policy` |
161
+ | Live first-admission policy, starter loss, frozen authority and native non-interactive stage Sessions | `node_workflow_captures_live_first_admission_and_survives_starter_and_index_changes` |
162
+ | Held, consumed and completed work settles on stop and reload | `node_workflow_stop_and_reload_settle_held_consumed_and_completed_work` |
163
+ | Reports wake wait and adopt after the start batch commits | `node_workflow_report_wakes_wait_and_is_adopted_after_its_start_batch` |
164
+ | Crash, row/runtime replacement, complete-host restart and uncertain report publication | `node_workflow_reconciles_crashes_failed_records_and_resubmitted_reports`, `node_workflow_reports_recover_after_complete_host_restart`, `node_workflow_background_report_resubmits_after_row_and_runtime_replacement` |
165
+ | Invalid inner records and failed change-token writes preserve committed runs | `node_workflow_rejects_invalid_inner_records_without_changing_bytes`, `node_workflow_failed_token_write_keeps_the_committed_run_readable` |
166
+ | Retention preserves live references, user forks and external Sessions, and retries real deletion/read/record faults | The three `node_workflow_retention_*` flows |
164
167
 
165
168
  Scope cleanup also exercises a failing parallel reader while another native launch is admitted:
166
169
  the original task joins its siblings before its file scope can close. No additional lock or owner
@@ -1,13 +1,12 @@
1
+ import { randomUUID } from 'node:crypto'
1
2
  import { z } from 'zod'
2
- import type { FileScope, PermissionPolicy } from '../../../npm/plugin.js'
3
+ import type { FileLease, FileScope, PermissionPolicy } from '../../../npm/plugin.js'
3
4
  import { definitionSchema, issues, logEntries, logMessageBytes, resultBytes, stageReasonBytes, stages, WorkflowError } from './definition.ts'
4
5
 
5
6
  const id = z.string().min(1)
6
7
  const timestamp = z.iso.datetime({ precision: 3 })
7
- const policy = z.strictObject({
8
+ const policy = z.object({
8
9
  preset: z.enum(['read-only', 'workspace-write', 'full-access']),
9
- fileAccess: z.enum(['read-only', 'workspace-read-write', 'unrestricted']).optional(),
10
- toolApproval: z.enum(['ask', 'bypass']).optional(),
11
10
  allowedTools: z.array(z.string()), disallowedTools: z.array(z.string()),
12
11
  }) satisfies z.ZodType<PermissionPolicy>
13
12
  const address = z.strictObject({ session_id: id, branch_id: id })
@@ -139,19 +138,62 @@ export function projectedBytes(runs: Run[]): number {
139
138
  return total
140
139
  }
141
140
 
142
- /** The only decoder for the committed inner-v2 run document. */
141
+ /** The only decoder for the committed inner-v2 run document.
142
+ *
143
+ * A poll reads `workflow/runs.version` instead of the store: it holds the
144
+ * token of the last committed store write, and the owner re-reads and decodes
145
+ * the store only when that token differs from the snapshot's. The token is
146
+ * derived state; startup and every write read the store itself, and a failed
147
+ * access drops the snapshot, so a committed replace whose token write failed
148
+ * stays visible to the next read. */
143
149
  export class RunStore {
144
150
  readonly scope: FileScope
151
+ private snapshot: { token: string | null; runs: Run[] } | undefined
152
+
145
153
  constructor(scope: FileScope) { this.scope = scope }
146
154
 
147
- async read(): Promise<Run[]> { return this.access(runs => runs, false) }
155
+ async read(): Promise<Run[]> {
156
+ const token = await this.signal()
157
+ if (this.snapshot?.token === token) return this.snapshot.runs
158
+ const runs = await this.access(runs => runs, false)
159
+ this.snapshot = { token, runs }
160
+ return runs
161
+ }
162
+
148
163
  async update<T>(change: (runs: Run[]) => T): Promise<T> { return this.access(change, true) }
149
164
 
150
- private async access<T>(change: (runs: Run[]) => T, write: boolean): Promise<T> {
165
+ private signal(): Promise<string | null> {
166
+ return this.locked('workflow/runs.version', async file => {
167
+ const reply = await file.read()
168
+ if (!reply.ok) throw new WorkflowError(reply.error)
169
+ return reply.value
170
+ })
171
+ }
172
+
173
+ private commit(): Promise<string> {
174
+ const token = randomUUID()
175
+ return this.locked('workflow/runs.version', async file => {
176
+ const reply = await file.replace(token)
177
+ if (!reply.ok) throw new WorkflowError(reply.error)
178
+ return token
179
+ })
180
+ }
181
+
182
+ private async locked<T>(path: string, work: (file: FileLease) => Promise<T>): Promise<T> {
151
183
  let value!: T
152
184
  let problem: unknown
153
- const outcome = await this.scope.withLock('workflow/runs.json', async file => {
154
- try {
185
+ const outcome = await this.scope.withLock(path, async file => {
186
+ try { value = await work(file) } catch (error) { problem = error }
187
+ })
188
+ if (!outcome.ok) throw new WorkflowError(outcome.error)
189
+ if (problem !== undefined) throw problem
190
+ return value
191
+ }
192
+
193
+ private async access<T>(change: (runs: Run[]) => T, write: boolean): Promise<T> {
194
+ let committed: { token: string; runs: Run[] } | undefined
195
+ try {
196
+ const value = await this.locked('workflow/runs.json', async file => {
155
197
  const text = await file.read()
156
198
  if (!text.ok) throw new WorkflowError(text.error)
157
199
  let runs: Run[]
@@ -160,18 +202,19 @@ export class RunStore {
160
202
  try { runs = storedSchema.parse(JSON.parse(text.value)).runs }
161
203
  catch { throw new WorkflowError('invalid_data') }
162
204
  }
163
- value = change(runs)
205
+ const changed = change(runs)
164
206
  if (write) {
165
207
  const reply = await file.replace(JSON.stringify({ version: 2, runs }))
166
208
  if (!reply.ok) throw new WorkflowError(reply.error)
209
+ committed = { token: await this.commit(), runs }
167
210
  }
168
- } catch (error) {
169
- problem = error
170
- }
171
- return null
172
- })
173
- if (!outcome.ok) throw new WorkflowError(outcome.error)
174
- if (problem !== undefined) throw problem
175
- return value
211
+ return changed
212
+ })
213
+ if (committed) this.snapshot = committed
214
+ return value
215
+ } catch (error) {
216
+ this.snapshot = undefined
217
+ throw error
218
+ }
176
219
  }
177
220
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.1.0-alpha.16",
4
- "sourceCommit": "7a232d0ebe939c4f9cc7140e368a53567b76b7d8",
3
+ "version": "0.1.0-alpha.17",
4
+ "sourceCommit": "10b7088064ea7e3add35743ce35eba9ea374e7a5",
5
5
  "platform": "darwin-x64",
6
6
  "toolchain": {
7
7
  "rust": "1.98.1"
@@ -10,8 +10,8 @@
10
10
  "files": {
11
11
  "LICENSE": "39100788d88143503514bc617369f1d1c7c0265c3f8742171e6af9701b0511b0",
12
12
  "NOTICE": "dd8e025eb8f6672bef1d021ea302602d6c3402856f277d48d4ed3c1a5f0c9e4d",
13
- "default-plugins.json": "cb0f1baef72b42bdf9491cfa50065a5de4fe24ce0033a1c825ad2ba69f2d0351",
14
- "bin/dimcode": "70eb60ca30debdd2e226d5c2914da6a56f6e67cc6fe6d6d6ca00eff8fad4b03d",
13
+ "default-plugins.json": "b5be1a8552248fbe87346960163e62cf1eb8f6346a74a801afa98d20113467d4",
14
+ "bin/dimcode": "bbeb81fb9b8bc26ca3e2447036592629025ea0c193c02f2f1cf68a2f821a4bc6",
15
15
  "licenses/grok-build/LICENSE": "116f7778b9802e569b7fa3a532b17bd80eb13c67837def01eed093d4ea472f28",
16
16
  "licenses/grok-build/NOTICE": "8db60f5ffc55a9845974915b1c5bfc7804a9b531ee0ffc6af786b3c0031c71e0",
17
17
  "plugins/goal/branch.ts": "37b1d033d48ad26fbf254090ccdaafb4bb4dc4f4146136f8e522be0fcc62d70c",
@@ -23,8 +23,8 @@
23
23
  "plugins/goal/session.ts": "75945194cb051f6e949049afc7a12fde34e63a6f1d924f20d7c669342bcf4d46",
24
24
  "plugins/goal/settings.ts": "2414bd311c11ac9f4a5bd69b8b46f8fe980dfd60b9a6adbf3bf15507a55819f5",
25
25
  "plugins/plugin-authoring/dim-plugin.json": "d43d6bd83f3b8ac05192f3ec8cadfe39500ccd541af336108ad1504c14f6ded8",
26
- "plugins/scheduler/README.md": "60163f9f6be2bf1b839f1a9ef1fcc4a0a0a04fd98acc9169a211b359844c176c",
27
- "plugins/scheduler/data.ts": "cf45347945ac609ad87795b8f03b4c10b6e0cec4bb5353dc5d345b6c4e5936f8",
26
+ "plugins/scheduler/README.md": "5730b50a4d8e1a6c386ae7f7100ad9b61f145125f8e2fe308c26d7749ab725d3",
27
+ "plugins/scheduler/data.ts": "8d95f8154556d815f41e61717be5a029afc74890d9a9b432c3e44b1bfb0f8d5c",
28
28
  "plugins/scheduler/dim-plugin.json": "c6b3bb2d5b6d3cef382567d9d8f473ec5db36b7a074e9ecc80b709c558e82d32",
29
29
  "plugins/scheduler/engine.ts": "9e224c008949fbaf6b8733df2219cbc1dcd29326bcf21b73b6f2ade3db0299d1",
30
30
  "plugins/scheduler/history.ts": "709f5ba2b1885ba9a28c527855c706444a6a2a02c16598182f2534dd33b1f635",
@@ -34,17 +34,10 @@
34
34
  "plugins/scheduler/package.json": "de006697139ef0bd04e90202ffeb6358458bffd8dea7f62cfa439235b80152e0",
35
35
  "plugins/scheduler/requests.ts": "9460a51c58800c8d3e4c2e2e45b4132c4f665988ea63a10cd7fdd7bc064376e8",
36
36
  "plugins/scheduler/session.ts": "26a9f013ce9e52c476123d3a69c3583f3c060e5ae8d7f1d3efe503a21cc33edc",
37
- "plugins/scheduler/settings.ts": "35e7986d10a2171e9401dda386689b7029ae5b117a62a819b6a6d1c737234ec6",
37
+ "plugins/scheduler/settings.ts": "ddafc111c7e6f6b86ddd47b7f4ec4d20b4c2f313b48712430e4e55a07af321a9",
38
38
  "plugins/scheduler/store.ts": "796e072e3596e81d750140b0826a298198cbc4fa474c00bccbd30f22e95e41c2",
39
39
  "plugins/scheduler/windows.ts": "4edbd2036750b7d7225df4a977e239fea179ebad42b1cdc2a585c02ba31c1783",
40
- "plugins/session-title/README.md": "37fc84524e2ffd3103bb3f5873d415ce5ef4e32cec9c27caa455739f8e9bc12b",
41
- "plugins/session-title/dim-plugin.json": "7d1a61a1eda6f233309395a50e8983bd244a6098681a5d3071eda73300ec4a2c",
42
- "plugins/session-title/index.ts": "bc35ec91bfb3f9e14a812bbb92800349ffbee663791686537f892721dd370b40",
43
- "plugins/session-title/package-lock.json": "e6f6b25573bbafeb77a19b38934c38ec515839bff15b185b40731a4e3c2a372b",
44
- "plugins/session-title/package.json": "5efa959d56ea8c1926a93bf9bc80d3ab64bdd36d99d6bb406f55bc20dce4a131",
45
- "plugins/session-title/part.ts": "646e0721b5c2f0962db73e426dc7c00634d89e23bce2fca9984a5456df3db5aa",
46
- "plugins/session-title/settings.ts": "a7c600d6cd67c9e02f5582dbd2c558995683f49461c2dd8b0cbc8f514d56a43a",
47
- "plugins/workflow/README.md": "cce53d09504923f7235e69eb001a7f869bce5d58b11aeaa29c1f12d0c7bf2e85",
40
+ "plugins/workflow/README.md": "d771ff56f72d99b3dc13373ad29e7be530826fd27d72b9276e573cc8e689c187",
48
41
  "plugins/workflow/catalog.ts": "f84131352323bd6d7607a521bf50427ab2bfa2d4f1be3651d544c20f25429cc7",
49
42
  "plugins/workflow/definition.ts": "4b2b90c9027cdf0f04120dee137f53caae9ead96a4702f86bc6badf00d069aee",
50
43
  "plugins/workflow/dim-plugin.json": "ee3b26ced63defe5a258ff7831c5941c7c8351984ecf31dd52c1fe002b737f37",
@@ -56,12 +49,11 @@
56
49
  "plugins/workflow/requests.ts": "226b0f83e05ee1ca52d57fc2938d1be0d7133a4585912dc8e5294eb045777674",
57
50
  "plugins/workflow/session.ts": "00b97f5eacc4d76acc8bbcad3e38aba9a50b69b95d9e612a9c588c8dada350d8",
58
51
  "plugins/workflow/settings.ts": "c906571b25c34d781170dc70fa954924a3240260f40b622944398d0496bbb484",
59
- "plugins/workflow/store.ts": "2bcdf6e954da2b27e299375d4a057935e53995a8ee721aca801c38d4c731c042",
52
+ "plugins/workflow/store.ts": "6193ddff16a98b6d8fe36918c12db35471527745912e2e5fe71d3e6cba4f4049",
60
53
  "licenses/grok-build/xai-ratatui-inline/NOTICE": "0b2ba5c4dcc49e4e17be08fe2980968fc7bce31e41ad5af09bb80097f61483dd",
61
54
  "licenses/grok-build/xai-ratatui-textarea/NOTICE": "5d310dde613653911ea026b5cb66367600f5a704d299fa0afedbf1d3efd5d493",
62
55
  "plugins/goal/node_modules/.package-lock.json": "96b3a00b4ac808cf7c3105ebbc0abc2a9a0b980f89140e275fca7e6cfbeb88d3",
63
56
  "plugins/scheduler/node_modules/.package-lock.json": "0e81b809be53fce3e194017e7d34e8935dd4d7a85af1f90a5810627304b46356",
64
- "plugins/session-title/node_modules/.package-lock.json": "1476e74ec17e24726f9c124d1eff69a63aaa7767d9c0a671f0379a1d1ab7b72a",
65
57
  "plugins/workflow/node_modules/.package-lock.json": "3716d0f89cc04eda67b76c126708b242459beb13ddaeba4c3f8b3e112e250736",
66
58
  "plugins/goal/node_modules/zod/LICENSE": "3f1189b28e3866e0d979968d466b78f813f76827cfdca1fbb124cc0a5c8841f8",
67
59
  "plugins/goal/node_modules/zod/README.md": "0ae96d90800b51cee45fe028e14474db1e58ea6479e4d9ed02de28b53c27500e",
@@ -207,8 +199,6 @@
207
199
  "plugins/scheduler/node_modules/zod/v4-mini/index.d.ts": "92f42fea7c90ec3f2f623a923d8798d2a316cb4ff93d6399084065b9214365de",
208
200
  "plugins/scheduler/node_modules/zod/v4-mini/index.js": "92f42fea7c90ec3f2f623a923d8798d2a316cb4ff93d6399084065b9214365de",
209
201
  "plugins/scheduler/node_modules/zod/v4-mini/package.json": "3e3125b5a537a24c3632c1c5c09640ce7bcb3935901b127ad8ae3c37a27f133f",
210
- "plugins/session-title/node_modules/@arcships/rutis/README.md": "765eddfcf43a3069d5e67168b83cbfe85bc298e3b1927fcbcdc0f87a144278eb",
211
- "plugins/session-title/node_modules/@arcships/rutis/package.json": "c803303b7d7dbf9e3b60d192758ea6c4d8b6853efed43e86075b96850d010116",
212
202
  "plugins/workflow/node_modules/@arcships/rutis/README.md": "765eddfcf43a3069d5e67168b83cbfe85bc298e3b1927fcbcdc0f87a144278eb",
213
203
  "plugins/workflow/node_modules/@arcships/rutis/package.json": "c803303b7d7dbf9e3b60d192758ea6c4d8b6853efed43e86075b96850d010116",
214
204
  "plugins/workflow/node_modules/yaml/browser/index.js": "5ae196b7abe55bed1d4d11f7eeba63aeda0c49fb73eb3c7359e3d6b9a080696b",
@@ -1114,13 +1104,6 @@
1114
1104
  "plugins/scheduler/node_modules/zod/v4/mini/schemas.d.cts": "1a8564333cf4f7ab1940d80791b75d862c05481d5d2b709966fa53ddf8ca7344",
1115
1105
  "plugins/scheduler/node_modules/zod/v4/mini/schemas.d.ts": "f67a25ded944912bd4621b68df17c7c70a52aae117278c076c0aabbc32a3abba",
1116
1106
  "plugins/scheduler/node_modules/zod/v4/mini/schemas.js": "51b3a7869df2d3a1bd98acb9d5b6a61bbccc7653d7a3d8fca7f0e0db7e84e284",
1117
- "plugins/session-title/node_modules/@arcships/rutis/src/advanced.d.ts": "9b75feb7a68b662561ae05f907680c8f2b6f691c7d0358525411767be5bed546",
1118
- "plugins/session-title/node_modules/@arcships/rutis/src/advanced.mjs": "d498fca3d835840657f9bce22cd94631e68185b5c828b94b1f1cb358eac98b59",
1119
- "plugins/session-title/node_modules/@arcships/rutis/src/index.d.ts": "58f83cebc736219cc3da67afd4e05760a9555524cd94c364d831b540bf0b3892",
1120
- "plugins/session-title/node_modules/@arcships/rutis/src/index.mjs": "3b0ee7d43ec527e6da8ea0c09b3e0361c51e1bbd4c8e141bde1e59cf38c43101",
1121
- "plugins/session-title/node_modules/@arcships/rutis/src/testing.d.ts": "c5ca82ed4e39a9f8a6c774e0a0bb6e70daa0ba7397e0748648770881b1032413",
1122
- "plugins/session-title/node_modules/@arcships/rutis/src/testing.mjs": "6afe5359dc1a28734aab932a98eb449b0b1af8974205e42917b9c0bc9f1ba6ef",
1123
- "plugins/session-title/node_modules/@arcships/rutis/src/values.mjs": "d668102fd296e2021cc735177bc6d377257e2edfcaa6bbd04da55f1783006f8d",
1124
1107
  "plugins/workflow/node_modules/@arcships/rutis/src/advanced.d.ts": "9b75feb7a68b662561ae05f907680c8f2b6f691c7d0358525411767be5bed546",
1125
1108
  "plugins/workflow/node_modules/@arcships/rutis/src/advanced.mjs": "d498fca3d835840657f9bce22cd94631e68185b5c828b94b1f1cb358eac98b59",
1126
1109
  "plugins/workflow/node_modules/@arcships/rutis/src/index.d.ts": "58f83cebc736219cc3da67afd4e05760a9555524cd94c364d831b540bf0b3892",
@@ -1,76 +0,0 @@
1
- # SessionTitle
2
-
3
- Optional Session TypeScript package implementing the [SessionTitle contract](../../../docs/design/plugins/SessionTitle.md).
4
- Its one row registers the Session part (the `part` export) with `partRegistry`, and each Session
5
- mounts a copy. The copy registers `session.rename` with the Session's `sessionMethods` and
6
- reads its Session through `sessionIdentity`, `sessionConfig` and `sessionLifecycle`; the existing
7
- process services own history, settings, model execution and title persistence.
8
-
9
- ## Loading
10
-
11
- Install the shared runtime and this package's pinned SDK dependencies:
12
-
13
- ```sh
14
- npm ci --prefix runtime --ignore-scripts
15
- npm ci --prefix runtime/plugins/session-title --ignore-scripts
16
- (cd runtime && npm install --no-save --package-lock=false --ignore-scripts --install-links ./plugins/session-title)
17
- ```
18
-
19
- These commands set up the shared runtime and this package in the checkout for `--session-rows` embedding and the package's tests. `./dev` needs none of them: it stages the default plugins, with their own dependencies, into `staging/dev-plugins`.
20
-
21
- The plugin directory carries [dim-plugin.json](dim-plugin.json) and is a default plugin listed in
22
- [runtime/default-plugins.json](../../default-plugins.json), which PluginManager loads (`--defaults`).
23
- An embedding host may still build a row from the manifest's `code` fields (`entry`, `inject`); the
24
- module imports `inject` from the same manifest and provides nothing.
25
- The `dim-app-server` binary takes such rows with `--session-rows FILE` (a JSON array whose
26
- file location is the anchor); see the [App Server README](../../../crates/dim-app-server/README.md#session-typescript-embedding).
27
-
28
- The Session part's required named dependencies are `sessions`, `settings`, `sessionMetadata` and
29
- `auxiliaryModel`. An unavailable provider keeps the part Pending, including rename. Native Sessions
30
- continue under existing assembly rules. Events missed while the part is unavailable are not replayed;
31
- the next adopted operator input can trigger an attempt using the first adopted input in history.
32
-
33
- ## Behavior
34
-
35
- Each Session's copy listens to its Session's `session/{sessionId}` channel. An interactive leader's own
36
- `main` adoption schedules owned asynchronous work and returns from the listener. A fixed published history prefix supplies only the first adopted
37
- operator input's text, bounded to 2000 Unicode code points. Images, raw blocks, deleted inputs,
38
- later input text and tool/assistant/system content are excluded. An image-only first input
39
- produces no automatic title and is remembered for this load.
40
-
41
- Each attempt first reads the validated user-only `session-title` Settings document; disabled
42
- generation reads neither metadata nor history. Defaults are
43
- `enabled: true`, `maxLength: 80` (10–200 code points) and `model: null`. The native backend
44
- stores it in `user/session-title`; the file backend uses the user file `session-title.json`.
45
- Use existing `settings.read`/`settings.update` with the returned revision. Project scope is
46
- rejected. Business edits affect the next attempt or rename without reloading the row.
47
-
48
- With `model: null`, the latest adopted `context/configured` in the fixed prefix supplies the
49
- model. A prefix without one fails the attempt; creation configuration and Provider defaults
50
- are not substitutes. The request omits `maxOutputTokens` and uses S4's existing model limit.
51
- Generation has a 30-second budget and three failed attempts per loaded generation. The package
52
- emits outcome/duration diagnostics to stderr. The supplied C4 host currently discards runtime
53
- stderr; it does not expose these diagnostics to clients.
54
-
55
- `session.rename({sessionId, title})` trims and validates a manual title; `null` clears it.
56
- Manual conflicts allow three retries. An automatic result uses the revision read before
57
- generation, so a concurrent manual write wins. Existing SessionStore notices and list fields
58
- announce committed titles. Titles add no Timeline facts or main-model context.
59
-
60
- Closing cancels and joins the original auxiliary operation through S4's existing reference.
61
- After `setTitle` handover, SessionStore settles the mutation; a lost reply permits no repeat
62
- or rollback. Generation takes no Session activity hold. Clearing permits a new attempt on
63
- the next adopted operator input, still using the original first input.
64
-
65
- ## Verification
66
-
67
- ```sh
68
- node runtime/node_modules/typescript/bin/tsc -p runtime/plugins/session-title/tsconfig.json
69
- node --test runtime/plugins/session-title/session-title.test.mjs
70
- ```
71
-
72
- The SDK flows execute the actual plugin over its declared ports. The real Node/App Server flow is
73
- `node_session_title_generation_rename_settings_restore_fork_and_close` in
74
- [session_title.rs](../../../crates/dim-app-server/tests/transport_flow/session_title.rs), run with
75
- the same `DIM_TEST_NODE`, `DIM_TEST_JS_RUNTIME` and `DIM_TEST_RUTIS_SDK` settings as public Node CI.
76
- It uses disposable stores and existing Prompt adoption, Provider, Settings, S2 and metadata bindings.
@@ -1,11 +0,0 @@
1
- {
2
- "name": "session-title",
3
- "description": "Titles Sessions from their first exchange",
4
- "code": {
5
- "entry": "index.ts",
6
- "inject": [
7
- "partRegistry",
8
- "settings"
9
- ]
10
- }
11
- }
@@ -1,13 +0,0 @@
1
- import { definePlugin } from '@arcships/rutis'
2
- import type { PartRegistry, Settings } from '../../../npm/plugin.d.ts'
3
- import manifest from './dim-plugin.json' with { type: 'json' }
4
- import part from './part.ts'
5
- import { settings as declaration } from './settings.ts'
6
-
7
- export default definePlugin({
8
- name: 'SessionTitle', inject: manifest.code.inject,
9
- async apply(ctx) {
10
- await ctx.use<Settings>('settings').register(declaration)
11
- await ctx.use<PartRegistry>('partRegistry').registerDefinition('session', 'SessionTitle', part)
12
- },
13
- })
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@dimcode/session-title",
3
- "version": "0.1.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "node_modules/@arcships/rutis": {
8
- "version": "0.19.3",
9
- "resolved": "https://registry.npmjs.org/@arcships/rutis/-/rutis-0.19.3.tgz",
10
- "integrity": "sha512-mpeVQ2tIZFyiKibgmRtyEhFu0NW3xam/OMUWSkddXIj8a/QqyW3AiE37GKlb4ihpHtuRx1dIz9HyCYHgvnecJw==",
11
- "license": "MIT",
12
- "engines": {
13
- "bun": ">=1.4",
14
- "node": ">=22"
15
- }
16
- }
17
- }
18
- }
@@ -1,32 +0,0 @@
1
- # @arcships/rutis
2
-
3
- Write [rutis](https://github.com/arcships/rutis) plugins in TypeScript or JavaScript.
4
-
5
- ```ts
6
- import { definePlugin } from '@arcships/rutis'
7
-
8
- export default definePlugin({
9
- inject: ['llm'], // services it uses
10
- config: { type: 'object', properties: { city: { type: 'string' } } },
11
- apply(ctx, config) {
12
- ctx.provide('weather', new Weather(ctx.use('llm'), config.city))
13
- return () => {} // cleanup
14
- },
15
- })
16
- ```
17
-
18
- What `ctx.provide` provides reaches rutis by itself, its methods the object's functions, called as `async`. `provides` is optional: `provides: { weather: { unit: 'sync' } }` marks methods `sync` and tells `rutis-host check` what the plugin provides before it runs.
19
-
20
- Test it on a real host (`rutis-host test`; `@arcships/rutis-runtime` and `@arcships/rutis-host` as dev dependencies, which `rutis-host new` adds); the plugin is the default export of its module:
21
-
22
- ```ts
23
- import { load } from '@arcships/rutis/testing'
24
-
25
- const t = await load(plugin, { config: { city: 'Oslo' }, services: { llm: { ask: async () => 'sunny' } } })
26
- assert.equal(await t.service('weather').today(), 'sunny in Oslo')
27
- await t.unload()
28
- ```
29
-
30
- This package has no dependencies. Events (`ctx.on`, and `ctx.emit` / `parallel` / `serial` / `waterfall` / `bail` / `filter`) are one bus across every plugin, runtime and linked node, dispatched by the host; in a test too (`host()` puts several plugins on one bus). The host installs the runtime that runs the plugin (`@arcships/rutis-runtime` for Node, `@arcships/rutis-bun` for Bun). Start a project with `npx @arcships/rutis-host new <name> --lang node` (or `--lang bun`).
31
-
32
- Guide (Chinese): [docs/guide/typescript-plugin.md](https://github.com/arcships/rutis/blob/main/docs/guide/typescript-plugin.md).