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.
- package/bin/dimcode +2 -2
- package/default-plugins.json +0 -1
- package/package.json +1 -1
- package/plugins/scheduler/README.md +4 -9
- package/plugins/scheduler/data.ts +8 -29
- package/plugins/scheduler/settings.ts +0 -9
- package/plugins/workflow/README.md +22 -19
- package/plugins/workflow/store.ts +61 -18
- package/release-manifest.json +9 -26
- package/plugins/session-title/README.md +0 -76
- package/plugins/session-title/dim-plugin.json +0 -11
- package/plugins/session-title/index.ts +0 -13
- package/plugins/session-title/node_modules/.package-lock.json +0 -18
- package/plugins/session-title/node_modules/@arcships/rutis/README.md +0 -32
- package/plugins/session-title/node_modules/@arcships/rutis/package.json +0 -46
- package/plugins/session-title/node_modules/@arcships/rutis/src/advanced.d.ts +0 -90
- package/plugins/session-title/node_modules/@arcships/rutis/src/advanced.mjs +0 -32
- package/plugins/session-title/node_modules/@arcships/rutis/src/index.d.ts +0 -43
- package/plugins/session-title/node_modules/@arcships/rutis/src/index.mjs +0 -71
- package/plugins/session-title/node_modules/@arcships/rutis/src/testing.d.ts +0 -77
- package/plugins/session-title/node_modules/@arcships/rutis/src/testing.mjs +0 -350
- package/plugins/session-title/node_modules/@arcships/rutis/src/values.mjs +0 -261
- package/plugins/session-title/package-lock.json +0 -45
- package/plugins/session-title/package.json +0 -9
- package/plugins/session-title/part.ts +0 -172
- 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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 104311000 bytes
|
|
4
|
+
sha256: bbeb81fb9b8bc26ca3e2447036592629025ea0c193c02f2f1cf68a2f821a4bc6
|
package/default-plugins.json
CHANGED
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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 | `
|
|
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
|
-
|
|
|
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
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
(
|
|
56
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
138
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
|
158
|
+
| Public behavior | Evidence |
|
|
154
159
|
| --- | --- |
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
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.
|
|
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[]> {
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
}
|
package/release-manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"sourceCommit": "
|
|
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": "
|
|
14
|
-
"bin/dimcode": "
|
|
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": "
|
|
27
|
-
"plugins/scheduler/data.ts": "
|
|
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": "
|
|
37
|
+
"plugins/scheduler/settings.ts": "ddafc111c7e6f6b86ddd47b7f4ec4d20b4c2f313b48712430e4e55a07af321a9",
|
|
38
38
|
"plugins/scheduler/store.ts": "796e072e3596e81d750140b0826a298198cbc4fa474c00bccbd30f22e95e41c2",
|
|
39
39
|
"plugins/scheduler/windows.ts": "4edbd2036750b7d7225df4a977e239fea179ebad42b1cdc2a585c02ba31c1783",
|
|
40
|
-
"plugins/
|
|
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": "
|
|
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,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).
|