@north-light/api 0.3.561 → 0.3.563
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/dist/api/client.d.ts +27 -4
- package/dist/api/client.js +53 -0
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/bash-jobs.d.ts +5 -1
- package/dist/api/dto/broker-ops.d.ts +25 -1
- package/dist/api/dto/charges.d.ts +9 -2
- package/dist/api/dto/delivery.d.ts +26 -2
- package/dist/api/dto/execution-start.d.ts +57 -0
- package/dist/api/dto/execution-start.js +2 -0
- package/dist/api/dto/modelauth.d.ts +5 -0
- package/dist/api/dto/node-records.d.ts +16 -0
- package/dist/api/dto/objects.d.ts +1 -1
- package/dist/api/dto/reports.d.ts +0 -3
- package/dist/api/dto/run-events.d.ts +7 -1
- package/dist/api/dto/settings.d.ts +55 -0
- package/dist/api/dto/settings.js +4 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/routes.d.ts +11 -0
- package/dist/api/routes.js +12 -0
- package/dist/core/canvas/paths.d.ts +0 -1
- package/dist/core/canvas/paths.js +0 -3
- package/dist/core/canvas/person-state.d.ts +26 -0
- package/dist/core/canvas/person-state.js +41 -0
- package/dist/core/command-plugins/bundle.d.ts +1 -1
- package/dist/core/command-plugins/bundle.js +5 -5
- package/dist/core/config.d.ts +16 -15
- package/dist/core/config.js +99 -76
- package/dist/core/conversation-store/client.d.ts +2 -0
- package/dist/core/conversation-store/client.js +111 -0
- package/dist/core/conversation-store/index.d.ts +96 -0
- package/dist/core/conversation-store/index.js +62 -0
- package/dist/core/conversation-store/patch.d.ts +3 -0
- package/dist/core/conversation-store/patch.js +192 -0
- package/dist/core/conversation-store/repair.d.ts +45 -0
- package/dist/core/conversation-store/repair.js +84 -0
- package/dist/core/env-name.d.ts +1 -0
- package/dist/core/env-name.js +16 -0
- package/dist/core/events/emit.js +7 -5
- package/dist/core/git.d.ts +28 -0
- package/dist/core/git.js +103 -0
- package/dist/core/installed-plugins.js +12 -7
- package/dist/core/managed-providers.d.ts +72 -0
- package/dist/core/managed-providers.js +61 -0
- package/dist/core/plugin-env.js +1 -2
- package/dist/core/profiles/manifest.d.ts +14 -18
- package/dist/core/profiles/manifest.js +79 -98
- package/dist/core/runtime/broker/attachments-fetch.d.ts +5 -0
- package/dist/core/runtime/broker/attachments-fetch.js +20 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +10 -1
- package/dist/core/runtime/broker/daemon-ops.js +41 -3
- package/dist/core/runtime/broker/node-token.d.ts +3 -0
- package/dist/core/runtime/broker/node-token.js +16 -0
- package/dist/core/runtime/broker/signal-stream.js +2 -2
- package/dist/core/scoped-state/materialized-plugins.d.ts +18 -0
- package/dist/core/scoped-state/materialized-plugins.js +103 -0
- package/dist/core/scoped-state/person-store.d.ts +72 -0
- package/dist/core/scoped-state/person-store.js +112 -0
- package/dist/core/user-settings.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +48 -11
- package/dist/daemon/broker-signals/bus.js +116 -46
- package/dist/shared/inbox-entry-body.js +2 -2
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
- package/dist/core/canvas/legacy-person.d.ts +0 -4
- package/dist/core/canvas/legacy-person.js +0 -4
- package/dist/core/locked-file.d.ts +0 -27
- package/dist/core/locked-file.js +0 -121
- package/dist/core/profiles/env-store.d.ts +0 -22
- package/dist/core/profiles/env-store.js +0 -166
- package/dist/core/scoped-state/db.d.ts +0 -17
- package/dist/core/scoped-state/db.js +0 -261
- package/dist/core/scoped-state/migrate.d.ts +0 -8
- package/dist/core/scoped-state/migrate.js +0 -187
- package/dist/core/scoped-state/profiles.d.ts +0 -27
- package/dist/core/scoped-state/profiles.js +0 -94
- package/dist/core/scoped-state/providers.d.ts +0 -24
- package/dist/core/scoped-state/providers.js +0 -19
- package/dist/core/scoped-state/schema.d.ts +0 -10
- package/dist/core/scoped-state/schema.js +0 -47
- package/dist/core/scoped-state/settings.d.ts +0 -28
- package/dist/core/scoped-state/settings.js +0 -104
- package/dist/core/sqlite-statements.d.ts +0 -4
- package/dist/core/sqlite-statements.js +0 -17
- package/dist/core/subscription-state.d.ts +0 -129
- package/dist/core/subscription-state.js +0 -299
package/dist/api/routes.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare const routes: {
|
|
|
13
13
|
readonly reviveAll: () => string;
|
|
14
14
|
readonly node: (id: string) => string;
|
|
15
15
|
readonly nodeMemoryReads: (id: string) => string;
|
|
16
|
+
readonly executionStart: (id: string) => string;
|
|
17
|
+
readonly environmentGitCredential: (id: string) => string;
|
|
16
18
|
readonly nodeOutcome: (id: string) => string;
|
|
17
19
|
readonly nodeOutcomeDelivery: (id: string) => string;
|
|
18
20
|
readonly nodeEvents: (id: string) => string;
|
|
@@ -27,6 +29,7 @@ export declare const routes: {
|
|
|
27
29
|
readonly nodeResult: (id: string) => string;
|
|
28
30
|
readonly nodeJobs: (id: string) => string;
|
|
29
31
|
readonly nodeJob: (id: string, jobId: string) => string;
|
|
32
|
+
readonly nodeJobReport: (id: string, jobId: string) => string;
|
|
30
33
|
readonly nodeJobBackground: (id: string, jobId: string) => string;
|
|
31
34
|
readonly nodeDelivery: (id: string) => string;
|
|
32
35
|
readonly deliveryDryRun: () => string;
|
|
@@ -38,10 +41,14 @@ export declare const routes: {
|
|
|
38
41
|
readonly nodeRelaunchRoot: (id: string) => string;
|
|
39
42
|
readonly nodeBrokerSessionBound: (id: string) => string;
|
|
40
43
|
readonly nodeBrokerSettle: (id: string) => string;
|
|
44
|
+
readonly nodeBrokerIdle: (id: string) => string;
|
|
41
45
|
readonly nodeBrokerParkComplete: (id: string) => string;
|
|
42
46
|
readonly nodeBrokerParkActivity: (id: string) => string;
|
|
43
47
|
readonly nodeBrokerTelemetry: (id: string) => string;
|
|
44
48
|
readonly nodeLog: (id: string) => string;
|
|
49
|
+
readonly nodeSummaries: (id: string) => string;
|
|
50
|
+
readonly nodeAttachments: (id: string) => string;
|
|
51
|
+
readonly nodeAttachment: (id: string, sha256: string) => string;
|
|
45
52
|
readonly nodeTelemetry: (id: string) => string;
|
|
46
53
|
readonly nodeRecap: (id: string) => string;
|
|
47
54
|
readonly nodeInboxReport: (id: string) => string;
|
|
@@ -52,6 +59,8 @@ export declare const routes: {
|
|
|
52
59
|
readonly nodeBrokerModel: (id: string) => string;
|
|
53
60
|
readonly nodeBrokerExtensionState: (id: string) => string;
|
|
54
61
|
readonly nodeBrokerSignals: (id: string) => string;
|
|
62
|
+
readonly nodeBrokerStructuredRequest: (id: string) => string;
|
|
63
|
+
readonly nodeBrokerGoal: (id: string) => string;
|
|
55
64
|
readonly nodeBrokerGeneratedName: (id: string) => string;
|
|
56
65
|
readonly nodeBrokerPersonaAck: (id: string) => string;
|
|
57
66
|
readonly brokerTurn: (id: string) => string;
|
|
@@ -140,9 +149,11 @@ export declare const routes: {
|
|
|
140
149
|
readonly appEnvVar: (variable: string) => string;
|
|
141
150
|
readonly profileEnv: (name: string) => string;
|
|
142
151
|
readonly profileEnvVar: (name: string, variable: string) => string;
|
|
152
|
+
readonly settings: () => string;
|
|
143
153
|
readonly modelAuths: () => string;
|
|
144
154
|
readonly modelAuthReadiness: () => string;
|
|
145
155
|
readonly modelAuth: (provider: string) => string;
|
|
156
|
+
readonly modelAuthAccount: (provider: string, label: string) => string;
|
|
146
157
|
readonly modelAuthFlows: () => string;
|
|
147
158
|
readonly modelAuthFlow: (flowId: string) => string;
|
|
148
159
|
readonly modelAuthFlowComplete: (flowId: string) => string;
|
package/dist/api/routes.js
CHANGED
|
@@ -28,6 +28,8 @@ export const routes = {
|
|
|
28
28
|
reviveAll: () => `${V}/nodes/revive-all`,
|
|
29
29
|
node: (id) => `${V}/nodes/${id}`,
|
|
30
30
|
nodeMemoryReads: (id) => `${V}/nodes/${id}/memory-reads`,
|
|
31
|
+
executionStart: (id) => `${V}/executions/${id}/start`,
|
|
32
|
+
environmentGitCredential: (id) => `${V}/environments/${id}/git-credential`,
|
|
31
33
|
nodeOutcome: (id) => `${V}/nodes/${id}/outcome`,
|
|
32
34
|
nodeOutcomeDelivery: (id) => `${V}/nodes/${id}/outcome-delivery`,
|
|
33
35
|
nodeEvents: (id) => `${V}/nodes/${id}/events`,
|
|
@@ -43,6 +45,7 @@ export const routes = {
|
|
|
43
45
|
nodeResult: (id) => `${V}/nodes/${id}/result`,
|
|
44
46
|
nodeJobs: (id) => `${V}/nodes/${id}/jobs`,
|
|
45
47
|
nodeJob: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}`,
|
|
48
|
+
nodeJobReport: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}/report`,
|
|
46
49
|
nodeJobBackground: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}/background`,
|
|
47
50
|
nodeDelivery: (id) => `${V}/nodes/${id}/delivery`,
|
|
48
51
|
deliveryDryRun: () => `${V}/delivery/dry-run`,
|
|
@@ -56,10 +59,14 @@ export const routes = {
|
|
|
56
59
|
nodeRelaunchRoot: (id) => `${V}/nodes/${id}/relaunch-root`,
|
|
57
60
|
nodeBrokerSessionBound: (id) => `${V}/nodes/${id}/broker/session-bound`,
|
|
58
61
|
nodeBrokerSettle: (id) => `${V}/nodes/${id}/broker/settle`,
|
|
62
|
+
nodeBrokerIdle: (id) => `${V}/nodes/${id}/broker/idle`,
|
|
59
63
|
nodeBrokerParkComplete: (id) => `${V}/nodes/${id}/broker/park-complete`,
|
|
60
64
|
nodeBrokerParkActivity: (id) => `${V}/nodes/${id}/broker/park-activity`,
|
|
61
65
|
nodeBrokerTelemetry: (id) => `${V}/nodes/${id}/broker/telemetry`,
|
|
62
66
|
nodeLog: (id) => `${V}/nodes/${id}/records/log`,
|
|
67
|
+
nodeSummaries: (id) => `${V}/nodes/${id}/records/summaries`,
|
|
68
|
+
nodeAttachments: (id) => `${V}/nodes/${id}/attachments`,
|
|
69
|
+
nodeAttachment: (id, sha256) => `${V}/nodes/${id}/attachments/${sha256}`,
|
|
63
70
|
nodeTelemetry: (id) => `${V}/nodes/${id}/records/telemetry`,
|
|
64
71
|
nodeRecap: (id) => `${V}/nodes/${id}/records/recap`,
|
|
65
72
|
nodeInboxReport: (id) => `${V}/nodes/${id}/records/inbox`,
|
|
@@ -70,6 +77,8 @@ export const routes = {
|
|
|
70
77
|
nodeBrokerModel: (id) => `${V}/nodes/${id}/broker/model`,
|
|
71
78
|
nodeBrokerExtensionState: (id) => `${V}/nodes/${id}/broker/extension-state`,
|
|
72
79
|
nodeBrokerSignals: (id) => `${V}/nodes/${id}/broker/signals`,
|
|
80
|
+
nodeBrokerStructuredRequest: (id) => `${V}/nodes/${id}/broker/structured-request`,
|
|
81
|
+
nodeBrokerGoal: (id) => `${V}/nodes/${id}/broker/goal`,
|
|
73
82
|
nodeBrokerGeneratedName: (id) => `${V}/nodes/${id}/broker/generated-name`,
|
|
74
83
|
nodeBrokerPersonaAck: (id) => `${V}/nodes/${id}/broker/persona-ack`,
|
|
75
84
|
brokerTurn: (id) => `${V}/broker/${id}/turn`,
|
|
@@ -168,10 +177,13 @@ export const routes = {
|
|
|
168
177
|
appEnvVar: (variable) => `${V}/apps/self/env/${encodeURIComponent(variable)}`,
|
|
169
178
|
profileEnv: (name) => `${V}/profiles/${name}/env`,
|
|
170
179
|
profileEnvVar: (name, variable) => `${V}/profiles/${name}/env/${variable}`,
|
|
180
|
+
// Person settings
|
|
181
|
+
settings: () => `${V}/settings`,
|
|
171
182
|
// Model auth
|
|
172
183
|
modelAuths: () => `${V}/model-auth`,
|
|
173
184
|
modelAuthReadiness: () => `${V}/model-auth/readiness`,
|
|
174
185
|
modelAuth: (provider) => `${V}/model-auth/${provider}`,
|
|
186
|
+
modelAuthAccount: (provider, label) => `${V}/model-auth/${provider}/accounts/${encodeURIComponent(label)}`,
|
|
175
187
|
modelAuthFlows: () => `${V}/model-auth/flows`,
|
|
176
188
|
modelAuthFlow: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}`,
|
|
177
189
|
modelAuthFlowComplete: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}/complete`,
|
|
@@ -71,7 +71,6 @@ export declare function inboxPath(nodeId: string): string;
|
|
|
71
71
|
export declare function situationalContextPath(nodeId: string): string;
|
|
72
72
|
export declare function passivePath(nodeId: string): string;
|
|
73
73
|
export declare function transcriptPath(nodeId: string): string;
|
|
74
|
-
export declare function sessionPtrPath(nodeId: string): string;
|
|
75
74
|
/** Create the full directory skeleton for a node. Idempotent. The agent and
|
|
76
75
|
* run folders are app-writable, so their subfolders are made beneath them. */
|
|
77
76
|
export declare function ensureNodeDirs(nodeId: string, grantee: string): void;
|
|
@@ -152,9 +152,6 @@ export function passivePath(nodeId) {
|
|
|
152
152
|
export function transcriptPath(nodeId) {
|
|
153
153
|
return join(nodeDir(nodeId), 'transcript.jsonl');
|
|
154
154
|
}
|
|
155
|
-
export function sessionPtrPath(nodeId) {
|
|
156
|
-
return join(nodeDir(nodeId), 'session.ptr');
|
|
157
|
-
}
|
|
158
155
|
/** Create the full directory skeleton for a node. Idempotent. The agent and
|
|
159
156
|
* run folders are app-writable, so their subfolders are made beneath them. */
|
|
160
157
|
export function ensureNodeDirs(nodeId, grantee) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Thrown by `personId()` before the person is known (a hosted first boot, before its first grant snapshot). */
|
|
2
|
+
export declare class PersonUnresolvedError extends Error {
|
|
3
|
+
readonly code = "person_unresolved";
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
/** Thrown by `personId()` on a cloud role (`crtrd --role api|reconcile`) outside a request: the control plane serves many people and
|
|
7
|
+
* has no install person to fall back to. A lane that works for a person takes the person from the row it claimed. */
|
|
8
|
+
export declare class PersonOutsideRequestError extends Error {
|
|
9
|
+
readonly code = "person_outside_request";
|
|
10
|
+
constructor();
|
|
11
|
+
}
|
|
12
|
+
/** crtrd sets this once at boot; it wins unconditionally. */
|
|
13
|
+
export declare function setPersonId(id: string): void;
|
|
14
|
+
/** crtrd calls this at boot on a cloud role, instead of resolving a person: from then on a person read outside a request throws. */
|
|
15
|
+
export declare function markCloudRole(): void;
|
|
16
|
+
/** Whether this process is a cloud role (`markCloudRole` ran at boot). */
|
|
17
|
+
export declare function isCloudRole(): boolean;
|
|
18
|
+
/** Run `fn` as `person` (`user:<id>`, the request token's `sub`): every `personId()` inside it, across awaits, answers `person`. */
|
|
19
|
+
export declare function runAsPerson<T>(person: string, fn: () => T): T;
|
|
20
|
+
/** The request's person; else, on a Mac, the boot-set person. Throws `PersonOutsideRequestError` on a cloud role outside a request and
|
|
21
|
+
* `PersonUnresolvedError` when a Mac's person is not bound. Tests and non-daemon processes use `personIdAsync()`. */
|
|
22
|
+
export declare function personId(): string;
|
|
23
|
+
/** The boot-set person, if any (for `personIdAsync`). */
|
|
24
|
+
export declare function bootPersonId(): string | undefined;
|
|
25
|
+
/** The request's person, if any (for `personIdAsync`). */
|
|
26
|
+
export declare function requestPersonId(): string | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// person-state.ts — the process's person binding (boot-set person, cloud-role flag, per-request person). Pure: no database, so the
|
|
2
|
+
// CLI-reachable settings cache can read the current person without reaching canvas state.
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
/** Thrown by `personId()` before the person is known (a hosted first boot, before its first grant snapshot). */
|
|
5
|
+
export class PersonUnresolvedError extends Error {
|
|
6
|
+
code = 'person_unresolved';
|
|
7
|
+
constructor() { super('the install\'s person is not resolved yet'); this.name = 'PersonUnresolvedError'; }
|
|
8
|
+
}
|
|
9
|
+
/** Thrown by `personId()` on a cloud role (`crtrd --role api|reconcile`) outside a request: the control plane serves many people and
|
|
10
|
+
* has no install person to fall back to. A lane that works for a person takes the person from the row it claimed. */
|
|
11
|
+
export class PersonOutsideRequestError extends Error {
|
|
12
|
+
code = 'person_outside_request';
|
|
13
|
+
constructor() { super('no person: a cloud role reads the person only inside a request, from its token'); this.name = 'PersonOutsideRequestError'; }
|
|
14
|
+
}
|
|
15
|
+
let daemonPersonId;
|
|
16
|
+
let cloudRole = false;
|
|
17
|
+
const requestPersons = new AsyncLocalStorage();
|
|
18
|
+
/** crtrd sets this once at boot; it wins unconditionally. */
|
|
19
|
+
export function setPersonId(id) { daemonPersonId = id; }
|
|
20
|
+
/** crtrd calls this at boot on a cloud role, instead of resolving a person: from then on a person read outside a request throws. */
|
|
21
|
+
export function markCloudRole() { cloudRole = true; }
|
|
22
|
+
/** Whether this process is a cloud role (`markCloudRole` ran at boot). */
|
|
23
|
+
export function isCloudRole() { return cloudRole; }
|
|
24
|
+
/** Run `fn` as `person` (`user:<id>`, the request token's `sub`): every `personId()` inside it, across awaits, answers `person`. */
|
|
25
|
+
export function runAsPerson(person, fn) { return requestPersons.run(person, fn); }
|
|
26
|
+
/** The request's person; else, on a Mac, the boot-set person. Throws `PersonOutsideRequestError` on a cloud role outside a request and
|
|
27
|
+
* `PersonUnresolvedError` when a Mac's person is not bound. Tests and non-daemon processes use `personIdAsync()`. */
|
|
28
|
+
export function personId() {
|
|
29
|
+
const request = requestPersons.getStore();
|
|
30
|
+
if (request !== undefined)
|
|
31
|
+
return request;
|
|
32
|
+
if (cloudRole)
|
|
33
|
+
throw new PersonOutsideRequestError();
|
|
34
|
+
if (daemonPersonId === undefined)
|
|
35
|
+
throw new PersonUnresolvedError();
|
|
36
|
+
return daemonPersonId;
|
|
37
|
+
}
|
|
38
|
+
/** The boot-set person, if any (for `personIdAsync`). */
|
|
39
|
+
export function bootPersonId() { return daemonPersonId; }
|
|
40
|
+
/** The request's person, if any (for `personIdAsync`). */
|
|
41
|
+
export function requestPersonId() { return requestPersons.getStore(); }
|
|
@@ -98,7 +98,7 @@ export declare function isInstalledGeneration(name: string, scope: Scope, root:
|
|
|
98
98
|
* are recorded either way, because both describe the bytes now at the root. */
|
|
99
99
|
export declare function recordCheckedGeneration(scope: Scope, name: string, etag: string | undefined, options: {
|
|
100
100
|
swapped: boolean;
|
|
101
|
-
}): void
|
|
101
|
+
}): Promise<void>;
|
|
102
102
|
/** Every file lands by temp file plus rename, fsynced, so a crash mid-write leaves the previous bytes or
|
|
103
103
|
* the whole new ones — never a 0-byte `plugin.json` or `commands.json` that crashes the next boot. */
|
|
104
104
|
export declare function writeStagedBundle(stagingRoot: string, manifest: PluginManifest, bundle: ValidatedBundle): void;
|
|
@@ -474,8 +474,8 @@ export function isInstalledGeneration(name, scope, root, version) {
|
|
|
474
474
|
/** The ledger for a check that reached a served archive. `swapped` says whether
|
|
475
475
|
* that archive replaced the package on disk; the validator and the check time
|
|
476
476
|
* are recorded either way, because both describe the bytes now at the root. */
|
|
477
|
-
export function recordCheckedGeneration(scope, name, etag, options) {
|
|
478
|
-
updateState(scope, (state) => {
|
|
477
|
+
export async function recordCheckedGeneration(scope, name, etag, options) {
|
|
478
|
+
await updateState(scope, (state) => {
|
|
479
479
|
if (state.plugins[name] === undefined)
|
|
480
480
|
state.plugins[name] = {};
|
|
481
481
|
if (options.swapped)
|
|
@@ -598,10 +598,10 @@ export async function installBundleArchive(name, raw, options) {
|
|
|
598
598
|
// rewritten. The forbidden state — a validator for a generation the disk does
|
|
599
599
|
// not have, and so a permanent 304 against a package that was never unpacked
|
|
600
600
|
// — is unreachable from this order.
|
|
601
|
-
ensureScopeInitialized(scope, scopeRootPath);
|
|
602
|
-
updateConfig(scope, (cfg) => {
|
|
601
|
+
await ensureScopeInitialized(scope, scopeRootPath);
|
|
602
|
+
await updateConfig(scope, (cfg) => {
|
|
603
603
|
cfg.plugins[name] = { enabled: options.enable ? true : (cfg.plugins[name]?.enabled ?? true), version };
|
|
604
604
|
});
|
|
605
|
-
recordCheckedGeneration(scope, name, options.etag, { swapped: true });
|
|
605
|
+
await recordCheckedGeneration(scope, name, options.etag, { swapped: true });
|
|
606
606
|
return { manifest, validated, version, root, staged };
|
|
607
607
|
}
|
package/dist/core/config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PageComponentRegistration, Scope, ScopeConfig, ScopeState, HumanActionConfig, KindConfig, ModelLaddersConfig, ModelProvider, ModelStrength, ModelRouteConfig, ModelRoutingConfig, ProviderOptionsConfig } from '../types.js';
|
|
2
2
|
import { type BindingId } from './keybindings/catalog.js';
|
|
3
|
-
import type { RotationConfig } from './
|
|
3
|
+
import type { RotationConfig } from './managed-providers.js';
|
|
4
4
|
export declare function configPath(scope: Scope): string | null;
|
|
5
5
|
export declare function statePath(scope: Scope): string | null;
|
|
6
6
|
export interface BrokerConfigSnapshot {
|
|
@@ -26,16 +26,17 @@ export declare function readRawSettingsForTarget(target: RawSettingsTarget): {
|
|
|
26
26
|
settings: Record<string, unknown> | null;
|
|
27
27
|
revision: string;
|
|
28
28
|
};
|
|
29
|
-
export declare function replaceRawSettingsForTarget(target: RawSettingsTarget, next: Record<string, unknown>, ifRevision: string): {
|
|
29
|
+
export declare function replaceRawSettingsForTarget(target: RawSettingsTarget, next: Record<string, unknown>, ifRevision: string): Promise<{
|
|
30
30
|
revision: string;
|
|
31
31
|
changed: boolean;
|
|
32
|
-
}
|
|
32
|
+
}>;
|
|
33
33
|
type RawConfig = Record<string, unknown>;
|
|
34
|
-
export declare function updateRawConfigAtomically(scope: Scope, mutate: (latest: RawConfig) => RawConfig): RawConfig
|
|
35
|
-
export declare function writeConfig(scope: Scope, config: ScopeConfig): void
|
|
36
|
-
export declare function writeRawConfig(scope: Scope, config: Partial<ScopeConfig>): void
|
|
37
|
-
export declare function writeState(scope: Scope, state: ScopeState): void
|
|
38
|
-
|
|
34
|
+
export declare function updateRawConfigAtomically(scope: Scope, mutate: (latest: RawConfig) => RawConfig): Promise<RawConfig>;
|
|
35
|
+
export declare function writeConfig(scope: Scope, config: ScopeConfig): Promise<void>;
|
|
36
|
+
export declare function writeRawConfig(scope: Scope, config: Partial<ScopeConfig>): Promise<void>;
|
|
37
|
+
export declare function writeState(scope: Scope, state: ScopeState): Promise<void>;
|
|
38
|
+
/** A project scope is a directory holding `config.json`; the user and profile scopes are the person's rows and need no directory setup. */
|
|
39
|
+
export declare function ensureScopeInitialized(scope: Scope, root: string): Promise<void>;
|
|
39
40
|
/** Normalize the user-owned list of attach actions allowed to bridge through an
|
|
40
41
|
* occupied tmux root binding. Unknown and non-attach ids have no effect. */
|
|
41
42
|
export declare function normalizeTmuxPassthrough(raw: unknown): BindingId[];
|
|
@@ -207,16 +208,16 @@ export declare function persistModelLadders(root: string, patch: {
|
|
|
207
208
|
defaultProvider?: ModelProvider;
|
|
208
209
|
anthropic?: Partial<Record<ModelStrength, string>>;
|
|
209
210
|
openai?: Partial<Record<ModelStrength, string>>;
|
|
210
|
-
}): void
|
|
211
|
+
}): Promise<void>;
|
|
211
212
|
/** Persist one complete route at one exact config root. The route is the only
|
|
212
213
|
* raw route entry touched; unrelated routes and local ladder defaults remain
|
|
213
214
|
* absent. Pass `undefined` to remove this scope's entry, which resumes lower
|
|
214
215
|
* scope inheritance rather than introducing a tombstone. */
|
|
215
|
-
export declare function persistModelRoute(root: string, routeId: string, route: ModelRouteConfig | undefined): void
|
|
216
|
+
export declare function persistModelRoute(root: string, routeId: string, route: ModelRouteConfig | undefined): Promise<void>;
|
|
216
217
|
/** Mutate one exact config root. Setup uses this for profile and explicit
|
|
217
218
|
* project targets that cannot be represented by the user/project Scope enum. */
|
|
218
|
-
export declare function updateConfigAtRoot(root: string, mutate: (cfg: ScopeConfig) => void): ScopeConfig
|
|
219
|
-
export declare function updateConfig(scope: Scope, mutate: (cfg: ScopeConfig) => void): ScopeConfig
|
|
219
|
+
export declare function updateConfigAtRoot(root: string, mutate: (cfg: ScopeConfig) => void): Promise<ScopeConfig>;
|
|
220
|
+
export declare function updateConfig(scope: Scope, mutate: (cfg: ScopeConfig) => void): Promise<ScopeConfig>;
|
|
220
221
|
/** Persist one sparse kind-model field at an exact config root. The sparse
|
|
221
222
|
* patch keeps every unrelated kind and launch knob inherited from lower
|
|
222
223
|
* scopes instead of freezing a materialized registry snapshot. */
|
|
@@ -225,9 +226,9 @@ export declare function persistDefaultKindModel(opts: {
|
|
|
225
226
|
kind: string;
|
|
226
227
|
model: string;
|
|
227
228
|
orchestrator?: boolean;
|
|
228
|
-
}): {
|
|
229
|
+
}): Promise<{
|
|
229
230
|
path: string;
|
|
230
231
|
field: 'model' | 'orchestratorModel';
|
|
231
|
-
}
|
|
232
|
-
export declare function updateState(scope: Scope, mutate: (s: ScopeState) => void): ScopeState
|
|
232
|
+
}>;
|
|
233
|
+
export declare function updateState(scope: Scope, mutate: (s: ScopeState) => void): Promise<ScopeState>;
|
|
233
234
|
export {};
|
package/dist/core/config.js
CHANGED
|
@@ -10,9 +10,8 @@ import { atomicWriteJson, readJsonIfExists, ensureDir } from './fs-utils.js';
|
|
|
10
10
|
import { scopeRoot, requireScopeRoot, findProjectScopeRoots, userScopeRoot } from './scope.js';
|
|
11
11
|
import { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
12
12
|
import { profileDir, listProfiles } from './profiles/manifest.js';
|
|
13
|
-
import { canonicalRoot
|
|
14
|
-
import {
|
|
15
|
-
import { mutateRawSettings, readBookkeeping, readRawSettings, readRawSettingsWithRevision, replaceRawSettings, writeBookkeeping, writeRawSettings } from './scoped-state/settings.js';
|
|
13
|
+
import { canonicalRoot } from './scoped-state/paths.js';
|
|
14
|
+
import { readCachedSettings, readCachedState, revisionOf, SettingsRevisionConflictError, writeSettings } from './scoped-state/person-store.js';
|
|
16
15
|
import { withExclusiveDirectoryLock } from './exclusive-lock.js';
|
|
17
16
|
import { SCOPE_CONFIG_KEYS } from './user-settings.js';
|
|
18
17
|
import { normalizeWorkingGerunds } from '../shared/working-activity.js';
|
|
@@ -39,23 +38,18 @@ function settingsOwnerForRoot(root) {
|
|
|
39
38
|
const canonical = canonicalRoot(root);
|
|
40
39
|
const userRoot = canonicalRoot(userScopeRoot());
|
|
41
40
|
if (canonical === userRoot)
|
|
42
|
-
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (basename(canonical) === '.crouter') {
|
|
46
|
-
convertLegacyScopedStore({ kind: 'project', root: canonical });
|
|
47
|
-
return { store: projectStore(canonical), key: 'project', kind: 'project', root: canonical };
|
|
48
|
-
}
|
|
41
|
+
return { key: 'user', kind: 'user', root: userRoot };
|
|
42
|
+
if (basename(canonical) === '.crouter')
|
|
43
|
+
return { key: 'project', kind: 'project', root: canonical };
|
|
49
44
|
for (const profile of listProfiles()) {
|
|
50
45
|
const profilePath = canonicalRoot(profileDir(profile.profileId));
|
|
51
46
|
if (canonical === profilePath)
|
|
52
|
-
return {
|
|
47
|
+
return { key: `profile:${profile.profileId}`, kind: 'profile', root: profilePath, profileId: profile.profileId };
|
|
53
48
|
}
|
|
54
49
|
throw new Error(`settings root is not a recognized owner: ${root}`);
|
|
55
50
|
}
|
|
56
51
|
function readRawAtRoot(root) {
|
|
57
|
-
// Project configuration is a file; reading it must not
|
|
58
|
-
// (or its lock directory) after the project disappears.
|
|
52
|
+
// Project configuration is a file; reading it must not touch the person's rows (or its lock directory) after the project disappears.
|
|
59
53
|
if (basename(root) === '.crouter') {
|
|
60
54
|
let canonical;
|
|
61
55
|
try {
|
|
@@ -69,21 +63,33 @@ function readRawAtRoot(root) {
|
|
|
69
63
|
if (canonical !== canonicalRoot(userScopeRoot()))
|
|
70
64
|
return readJsonIfExists(configPathFor(root));
|
|
71
65
|
}
|
|
72
|
-
return
|
|
66
|
+
return readCachedSettings(settingsOwnerForRoot(root).key).settings;
|
|
67
|
+
}
|
|
68
|
+
/** Replace a user or profile owner's settings through the one write algorithm: `compute` runs against the cached raw settings and
|
|
69
|
+
* runs again if the rows changed underneath it. */
|
|
70
|
+
async function mutatePersonSettings(key, mutate) {
|
|
71
|
+
let value;
|
|
72
|
+
await writeSettings((snapshot) => {
|
|
73
|
+
const current = snapshot.settings[key];
|
|
74
|
+
const result = mutate(structuredClone(current ?? {}));
|
|
75
|
+
value = result.value;
|
|
76
|
+
return [{ op: 'settings.replace', owner: key, raw: result.next, ifRevision: revisionOf(current) }];
|
|
77
|
+
});
|
|
78
|
+
return value;
|
|
73
79
|
}
|
|
74
|
-
function writeRawAtRoot(root, raw) {
|
|
80
|
+
async function writeRawAtRoot(root, raw) {
|
|
75
81
|
const owner = settingsOwnerForRoot(root);
|
|
76
82
|
if (owner.kind === 'project') {
|
|
77
83
|
withConfigPathLock(configPathFor(owner.root), (path) => atomicWriteJson(path, raw, { createParent: false }));
|
|
78
84
|
return;
|
|
79
85
|
}
|
|
80
|
-
|
|
86
|
+
await mutatePersonSettings(owner.key, () => ({ value: undefined, next: raw }));
|
|
81
87
|
}
|
|
82
|
-
function mutateRawAtRoot(root, mutate) {
|
|
88
|
+
async function mutateRawAtRoot(root, mutate) {
|
|
83
89
|
const owner = settingsOwnerForRoot(root);
|
|
84
90
|
if (owner.kind === 'project')
|
|
85
91
|
return withConfigPathLock(configPathFor(owner.root), (path) => { const result = mutate(readJsonIfExists(path) ?? {}); atomicWriteJson(path, result.next, { createParent: false }); return result.value; });
|
|
86
|
-
return
|
|
92
|
+
return await mutatePersonSettings(owner.key, mutate);
|
|
87
93
|
}
|
|
88
94
|
const brokerSnapshotState = globalThis;
|
|
89
95
|
const brokerSnapshotKey = Symbol.for('crtr.broker.config-snapshot');
|
|
@@ -104,18 +110,17 @@ export function readConfig(scope) {
|
|
|
104
110
|
return root ? readConfigAtRoot(root) : defaultScopeConfig();
|
|
105
111
|
}
|
|
106
112
|
export function readConfigAtRoot(root) { const existing = readRawAtRoot(root); return existing ? mergeConfig(existing) : defaultScopeConfig(); }
|
|
113
|
+
function readProjectState(root) {
|
|
114
|
+
const raw = readJsonIfExists(statePathFor(root));
|
|
115
|
+
return { marketplaces: raw?.marketplaces ?? {}, plugins: raw?.plugins ?? {}, activeCanvas: raw?.activeCanvas ?? null, ...(raw?.last_self_check === undefined ? {} : { last_self_check: raw.last_self_check }) };
|
|
116
|
+
}
|
|
107
117
|
export function readState(scope) {
|
|
108
118
|
const root = scopeRoot(scope);
|
|
109
119
|
if (!root)
|
|
110
120
|
return defaultScopeState();
|
|
111
|
-
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
catch (error) {
|
|
115
|
-
if (scope === 'project' && !existsSync(root))
|
|
116
|
-
return defaultScopeState();
|
|
117
|
-
throw error;
|
|
118
|
-
}
|
|
121
|
+
if (scope === 'project')
|
|
122
|
+
return existsSync(root) ? readProjectState(root) : defaultScopeState();
|
|
123
|
+
return readCachedState(settingsOwnerForRoot(root).key);
|
|
119
124
|
}
|
|
120
125
|
export class InvalidRawSettingsError extends Error {
|
|
121
126
|
constructor(cause) {
|
|
@@ -123,53 +128,64 @@ export class InvalidRawSettingsError extends Error {
|
|
|
123
128
|
this.name = 'InvalidRawSettingsError';
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
|
-
function
|
|
131
|
+
function rawSettingsKeyForTarget(target) {
|
|
127
132
|
if (target.scope === 'user')
|
|
128
|
-
return
|
|
133
|
+
return 'user';
|
|
129
134
|
if (!listProfiles().some((profile) => profile.profileId === target.profileId)) {
|
|
130
135
|
throw notFound(`profile not found: ${target.profileId}`, { received: target.profileId, next: 'Run `crtr profile list` to see available profiles.' });
|
|
131
136
|
}
|
|
132
|
-
return
|
|
137
|
+
return `profile:${target.profileId}`;
|
|
133
138
|
}
|
|
134
139
|
export function readRawSettingsForTarget(target) {
|
|
135
|
-
return
|
|
136
|
-
}
|
|
137
|
-
export function replaceRawSettingsForTarget(target, next, ifRevision) {
|
|
138
|
-
return replaceRawSettings(rawSettingsOwnerForTarget(target), next, ifRevision, (settings) => {
|
|
139
|
-
try {
|
|
140
|
-
mergeConfig(settings);
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
throw new InvalidRawSettingsError(error);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
140
|
+
return readCachedSettings(rawSettingsKeyForTarget(target));
|
|
146
141
|
}
|
|
147
|
-
export function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
export async function replaceRawSettingsForTarget(target, next, ifRevision) {
|
|
143
|
+
const key = rawSettingsKeyForTarget(target);
|
|
144
|
+
try {
|
|
145
|
+
mergeConfig(next);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new InvalidRawSettingsError(error);
|
|
149
|
+
}
|
|
150
|
+
const before = readCachedSettings(key);
|
|
151
|
+
if (before.revision !== ifRevision)
|
|
152
|
+
throw new SettingsRevisionConflictError();
|
|
153
|
+
const after = await writeSettings((snapshot) => (revisionOf(snapshot.settings[key]) === ifRevision
|
|
154
|
+
? [{ op: 'settings.replace', owner: key, raw: next, ifRevision }] : (() => { throw new SettingsRevisionConflictError(); })()));
|
|
155
|
+
const revision = revisionOf(after.settings[key]);
|
|
156
|
+
return { revision, changed: revision !== ifRevision };
|
|
157
|
+
}
|
|
158
|
+
export async function updateRawConfigAtomically(scope, mutate) { const root = requireScopeRoot(scope); return await mutateRawAtRoot(root, (latest) => { const next = mutate(latest); return { value: next, next }; }); }
|
|
159
|
+
export async function writeConfig(scope, config) { await writeRawAtRoot(requireScopeRoot(scope), config); }
|
|
160
|
+
export async function writeRawConfig(scope, config) { await writeRawAtRoot(requireScopeRoot(scope), config); }
|
|
161
|
+
function writeProjectState(root, state) { withConfigPathLock(statePathFor(root), (path) => atomicWriteJson(path, state, { createParent: false })); }
|
|
162
|
+
export async function writeState(scope, state) {
|
|
163
|
+
const root = requireScopeRoot(scope);
|
|
152
164
|
if (scope === 'project') {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
if (error.code !== 'EEXIST' || !statSync(root).isDirectory())
|
|
158
|
-
throw error;
|
|
159
|
-
}
|
|
165
|
+
writeProjectState(root, state);
|
|
166
|
+
return;
|
|
160
167
|
}
|
|
161
|
-
|
|
168
|
+
const key = settingsOwnerForRoot(root).key;
|
|
169
|
+
await writeSettings(() => [{ op: 'state.write', owner: key, state }]);
|
|
170
|
+
}
|
|
171
|
+
/** A project scope is a directory holding `config.json`; the user and profile scopes are the person's rows and need no directory setup. */
|
|
172
|
+
export async function ensureScopeInitialized(scope, root) {
|
|
173
|
+
if (scope !== 'project') {
|
|
162
174
|
ensureDir(root);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// the full default document would make every default (kinds, display
|
|
168
|
-
// toggles, auto_update) a project-level override that silently shadows the
|
|
169
|
-
// user's own settings for every directory beneath it — reads default-fill
|
|
170
|
-
// missing keys from the running build, so a sparse file loses nothing.
|
|
171
|
-
writeRawAtRoot(owner.root, { schema_version: defaultScopeConfig().schema_version });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
mkdirSync(root);
|
|
172
179
|
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (error.code !== 'EEXIST' || !statSync(root).isDirectory())
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
// A project config holds only what the project overrides. Seeding it with the full default document would make every default
|
|
185
|
+
// (kinds, display toggles, auto_update) a project-level override that silently shadows the user's own settings for every
|
|
186
|
+
// directory beneath it — reads default-fill missing keys from the running build, so a sparse file loses nothing.
|
|
187
|
+
if (readJsonIfExists(configPathFor(root)) === null)
|
|
188
|
+
await writeRawAtRoot(root, { schema_version: defaultScopeConfig().schema_version });
|
|
173
189
|
}
|
|
174
190
|
function normalizeKeybindings(raw) {
|
|
175
191
|
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
@@ -854,6 +870,13 @@ function mergeConfig(partial) {
|
|
|
854
870
|
if (ledger?.take_pct !== undefined && (typeof ledger.take_pct !== 'number' || !Number.isFinite(ledger.take_pct) || ledger.take_pct < 0 || ledger.take_pct > 100)) {
|
|
855
871
|
throw new Error('ledger.take_pct must be a number from 0 to 100');
|
|
856
872
|
}
|
|
873
|
+
for (const key of ['sandbox_usd_per_gb_hour', 'sandbox_standby_s', 'storage_usd_per_gb_month', 'sandbox_disk_usd_per_gb_month', 'infra_free_usd_per_month']) {
|
|
874
|
+
const value = ledger?.[key];
|
|
875
|
+
if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value) || value < 0))
|
|
876
|
+
throw new Error(`ledger.${key} must be a number of at least 0`);
|
|
877
|
+
}
|
|
878
|
+
if (ledger?.r2_move !== undefined && typeof ledger.r2_move !== 'boolean')
|
|
879
|
+
throw new Error('ledger.r2_move must be true or false');
|
|
857
880
|
const store = partial.store;
|
|
858
881
|
const runtime = partial.runtime;
|
|
859
882
|
if (runtime?.person_uids !== undefined && (!Array.isArray(runtime.person_uids)
|
|
@@ -1052,8 +1075,8 @@ function sparseModelLaddersToPersist(rawLadders, baseline, mutated) {
|
|
|
1052
1075
|
* `sys config set` never freezes a full concrete `modelLadders` to disk,
|
|
1053
1076
|
* and a direct ladder-rung set still persists only the touched rung -- all
|
|
1054
1077
|
* through one lock-protected atomic update path. */
|
|
1055
|
-
function mutateConfigAtRoot(root, mutate) {
|
|
1056
|
-
return mutateRawAtRoot(root, (raw) => {
|
|
1078
|
+
async function mutateConfigAtRoot(root, mutate) {
|
|
1079
|
+
return await mutateRawAtRoot(root, (raw) => {
|
|
1057
1080
|
const baseline = mergeConfig(raw);
|
|
1058
1081
|
const cfg = mergeConfig(raw);
|
|
1059
1082
|
mutate(cfg);
|
|
@@ -1080,8 +1103,8 @@ function mutateConfigAtRoot(root, mutate) {
|
|
|
1080
1103
|
* default produces an empty diff and silently persists nothing, so the choice
|
|
1081
1104
|
* evaporates as soon as the default moves. Explicit intent is recorded as
|
|
1082
1105
|
* written. Mirrors `persistDefaultKindModel`. */
|
|
1083
|
-
export function persistModelLadders(root, patch) {
|
|
1084
|
-
mutateRawAtRoot(root, (raw) => { const current = (raw.modelLadders ?? {}); const next = { ...current }; if (patch.defaultProvider !== undefined)
|
|
1106
|
+
export async function persistModelLadders(root, patch) {
|
|
1107
|
+
await mutateRawAtRoot(root, (raw) => { const current = (raw.modelLadders ?? {}); const next = { ...current }; if (patch.defaultProvider !== undefined)
|
|
1085
1108
|
next.defaultProvider = patch.defaultProvider; for (const provider of ['anthropic', 'openai'])
|
|
1086
1109
|
if (patch[provider] !== undefined)
|
|
1087
1110
|
next[provider] = { ...(current[provider] ?? {}), ...patch[provider] }; return { value: undefined, next: { ...raw, modelLadders: next } }; });
|
|
@@ -1090,12 +1113,12 @@ export function persistModelLadders(root, patch) {
|
|
|
1090
1113
|
* raw route entry touched; unrelated routes and local ladder defaults remain
|
|
1091
1114
|
* absent. Pass `undefined` to remove this scope's entry, which resumes lower
|
|
1092
1115
|
* scope inheritance rather than introducing a tombstone. */
|
|
1093
|
-
export function persistModelRoute(root, routeId, route) {
|
|
1116
|
+
export async function persistModelRoute(root, routeId, route) {
|
|
1094
1117
|
if (route !== undefined)
|
|
1095
1118
|
assertCompleteModelRoute(routeId, route);
|
|
1096
1119
|
else if (routeId.trim() === '')
|
|
1097
1120
|
throw new Error('model route id must be a non-empty string');
|
|
1098
|
-
mutateRawAtRoot(root, (raw) => { const routes = copyRouteRecord((raw.modelRoutes ?? {})); if (route === undefined)
|
|
1121
|
+
await mutateRawAtRoot(root, (raw) => { const routes = copyRouteRecord((raw.modelRoutes ?? {})); if (route === undefined)
|
|
1099
1122
|
delete routes[routeId];
|
|
1100
1123
|
else
|
|
1101
1124
|
setRouteRecord(routes, routeId, { ...route, models: { ...route.models } }); const next = { ...raw }; if (Object.keys(routes).length === 0)
|
|
@@ -1105,23 +1128,23 @@ export function persistModelRoute(root, routeId, route) {
|
|
|
1105
1128
|
}
|
|
1106
1129
|
/** Mutate one exact config root. Setup uses this for profile and explicit
|
|
1107
1130
|
* project targets that cannot be represented by the user/project Scope enum. */
|
|
1108
|
-
export function updateConfigAtRoot(root, mutate) {
|
|
1109
|
-
return mutateConfigAtRoot(root, mutate);
|
|
1131
|
+
export async function updateConfigAtRoot(root, mutate) {
|
|
1132
|
+
return await mutateConfigAtRoot(root, mutate);
|
|
1110
1133
|
}
|
|
1111
|
-
export function updateConfig(scope, mutate) {
|
|
1112
|
-
return updateConfigAtRoot(requireScopeRoot(scope), mutate);
|
|
1134
|
+
export async function updateConfig(scope, mutate) {
|
|
1135
|
+
return await updateConfigAtRoot(requireScopeRoot(scope), mutate);
|
|
1113
1136
|
}
|
|
1114
1137
|
/** Persist one sparse kind-model field at an exact config root. The sparse
|
|
1115
1138
|
* patch keeps every unrelated kind and launch knob inherited from lower
|
|
1116
1139
|
* scopes instead of freezing a materialized registry snapshot. */
|
|
1117
|
-
export function persistDefaultKindModel(opts) {
|
|
1140
|
+
export async function persistDefaultKindModel(opts) {
|
|
1118
1141
|
const field = opts.orchestrator === true ? 'orchestratorModel' : 'model';
|
|
1119
|
-
mutateRawAtRoot(opts.root, (raw) => { const kinds = { ...(raw.kinds ?? {}), [opts.kind]: { ...(raw.kinds ?? {})[opts.kind], [field]: opts.model } }; return { value: undefined, next: { ...raw, kinds: kinds } }; });
|
|
1142
|
+
await mutateRawAtRoot(opts.root, (raw) => { const kinds = { ...(raw.kinds ?? {}), [opts.kind]: { ...(raw.kinds ?? {})[opts.kind], [field]: opts.model } }; return { value: undefined, next: { ...raw, kinds: kinds } }; });
|
|
1120
1143
|
return { path: configPathFor(opts.root), field };
|
|
1121
1144
|
}
|
|
1122
|
-
export function updateState(scope, mutate) {
|
|
1145
|
+
export async function updateState(scope, mutate) {
|
|
1123
1146
|
const s = readState(scope);
|
|
1124
1147
|
mutate(s);
|
|
1125
|
-
writeState(scope, s);
|
|
1148
|
+
await writeState(scope, s);
|
|
1126
1149
|
return s;
|
|
1127
1150
|
}
|