@jini-ai/daemon 0.3.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +50 -8
  3. package/dist/agent-executor/contracts.d.ts +736 -0
  4. package/dist/agent-executor/contracts.js +28 -0
  5. package/dist/agent-executor/event-translation.d.ts +87 -0
  6. package/dist/agent-executor/event-translation.js +230 -0
  7. package/dist/agent-executor/events.d.ts +190 -0
  8. package/dist/agent-executor/events.js +848 -0
  9. package/dist/agent-executor/handoff.d.ts +18 -0
  10. package/dist/agent-executor/handoff.js +16 -0
  11. package/dist/agent-executor/index.d.ts +98 -0
  12. package/dist/agent-executor/index.js +98 -0
  13. package/dist/agent-executor/launch.d.ts +375 -0
  14. package/dist/agent-executor/launch.js +1001 -0
  15. package/dist/agent-executor/mcp-bridge.d.ts +338 -0
  16. package/dist/agent-executor/mcp-bridge.js +835 -0
  17. package/dist/agent-executor/values.d.ts +5 -0
  18. package/dist/agent-executor/values.js +13 -0
  19. package/dist/agent-executor.d.ts +382 -177
  20. package/dist/agent-executor.js +408 -344
  21. package/dist/close-status.d.ts +11 -8
  22. package/dist/close-status.js +31 -19
  23. package/dist/continuation/continuation-transport.d.ts +3 -1
  24. package/dist/continuation/continuation-transport.js +1 -1
  25. package/dist/continuation/journal.d.ts +12 -5
  26. package/dist/continuation/journal.js +4 -4
  27. package/dist/continuation/run-scoped-context-store.d.ts +13 -4
  28. package/dist/continuation/run-scoped-context-store.js +6 -6
  29. package/dist/continuation/run-start-handler.d.ts +1 -1
  30. package/dist/continuation/run-start-handler.js +3 -16
  31. package/dist/delegated-tool-bridge.d.ts +7 -3
  32. package/dist/delegated-tool-bridge.js +32 -31
  33. package/dist/event-log.d.ts +7 -5
  34. package/dist/event-log.js +12 -11
  35. package/dist/frontend-capability-tools.d.ts +1 -1
  36. package/dist/frontend-capability-tools.js +3 -2
  37. package/dist/frontend-session-registry.d.ts +32 -9
  38. package/dist/frontend-session-registry.js +12 -11
  39. package/dist/http/active-context.d.ts +105 -0
  40. package/dist/http/active-context.js +67 -0
  41. package/dist/http/agents.d.ts +68 -0
  42. package/dist/http/agents.js +12 -0
  43. package/dist/http/attachments.d.ts +618 -0
  44. package/dist/http/attachments.js +1034 -0
  45. package/dist/http/cancel-owned-runs.d.ts +49 -0
  46. package/dist/http/cancel-owned-runs.js +35 -0
  47. package/dist/http/component-catalog.d.ts +84 -0
  48. package/dist/http/component-catalog.js +64 -0
  49. package/dist/http/connectors.d.ts +209 -0
  50. package/dist/http/connectors.js +409 -0
  51. package/dist/http/daemon-status.d.ts +76 -0
  52. package/dist/http/daemon-status.js +55 -0
  53. package/dist/http/db-ops.d.ts +123 -0
  54. package/dist/http/db-ops.js +189 -0
  55. package/dist/http/delegated-tools.d.ts +113 -0
  56. package/dist/http/delegated-tools.js +230 -0
  57. package/dist/http/frontend-control.d.ts +136 -0
  58. package/dist/http/frontend-control.js +79 -0
  59. package/dist/http/frontend-sessions.d.ts +110 -0
  60. package/dist/http/frontend-sessions.js +180 -0
  61. package/dist/http/health.d.ts +95 -0
  62. package/dist/http/health.js +40 -0
  63. package/dist/http/host-tools.d.ts +214 -0
  64. package/dist/http/host-tools.js +329 -0
  65. package/dist/http/index.d.ts +52 -0
  66. package/dist/http/index.js +27 -0
  67. package/dist/http/media.d.ts +146 -0
  68. package/dist/http/media.js +346 -0
  69. package/dist/http/memory.d.ts +265 -0
  70. package/dist/http/memory.js +328 -0
  71. package/dist/http/model-proxy.d.ts +49 -0
  72. package/dist/http/model-proxy.js +642 -0
  73. package/dist/http/remote-run-events.d.ts +70 -0
  74. package/dist/http/remote-run-events.js +224 -0
  75. package/dist/http/research.d.ts +48 -0
  76. package/dist/http/research.js +315 -0
  77. package/dist/http/route-manifest.d.ts +35 -0
  78. package/dist/http/route-manifest.js +103 -0
  79. package/dist/http/routines.d.ts +120 -0
  80. package/dist/http/routines.js +298 -0
  81. package/dist/http/run-credentials.d.ts +73 -0
  82. package/dist/http/run-credentials.js +71 -0
  83. package/dist/http/runs.d.ts +91 -0
  84. package/dist/http/runs.js +221 -0
  85. package/dist/http/terminals.d.ts +138 -0
  86. package/dist/http/terminals.js +325 -0
  87. package/dist/http/tool-catalog.d.ts +78 -0
  88. package/dist/http/tool-catalog.js +66 -0
  89. package/dist/http/workspace-root.d.ts +77 -0
  90. package/dist/http/workspace-root.js +63 -0
  91. package/dist/http/xai.d.ts +90 -0
  92. package/dist/http/xai.js +655 -0
  93. package/dist/image-prompt-delivery.d.ts +13 -3
  94. package/dist/image-prompt-delivery.js +5 -5
  95. package/dist/index.d.ts +556 -4
  96. package/dist/index.js +556 -4
  97. package/dist/legacy-data-migration.d.ts +59 -8
  98. package/dist/legacy-data-migration.js +38 -33
  99. package/dist/read-only-tools.d.ts +130 -0
  100. package/dist/read-only-tools.js +115 -0
  101. package/dist/remote-tool-bridge.d.ts +11 -5
  102. package/dist/remote-tool-bridge.js +15 -15
  103. package/dist/routines/index.d.ts +1 -1
  104. package/dist/routines/index.js +1 -1
  105. package/dist/routines/routine-store.d.ts +34 -12
  106. package/dist/routines/routine-store.js +21 -20
  107. package/dist/routines/schedule.d.ts +48 -10
  108. package/dist/routines/schedule.js +21 -21
  109. package/dist/routines/scheduler.d.ts +32 -11
  110. package/dist/routines/scheduler.js +61 -57
  111. package/dist/routines/types.d.ts +10 -3
  112. package/dist/run/core/result.d.ts +6 -2
  113. package/dist/run/core/result.js +3 -3
  114. package/dist/run/core/retry.d.ts +17 -4
  115. package/dist/run/core/retry.js +7 -11
  116. package/dist/run/diagnostics/diagnostics.d.ts +14 -4
  117. package/dist/run/diagnostics/diagnostics.js +7 -7
  118. package/dist/run/index.d.ts +1 -1
  119. package/dist/run/index.js +1 -1
  120. package/dist/run-credentials.d.ts +138 -0
  121. package/dist/run-credentials.js +152 -0
  122. package/dist/run-lifecycle.d.ts +92 -33
  123. package/dist/run-lifecycle.js +86 -73
  124. package/dist/scheduler.d.ts +8 -0
  125. package/dist/scheduler.js +2 -0
  126. package/dist/session-coordination.d.ts +115 -0
  127. package/dist/session-coordination.js +111 -0
  128. package/dist/store/agent-sessions/errors.d.ts +8 -0
  129. package/dist/store/agent-sessions/errors.js +10 -0
  130. package/dist/store/agent-sessions/index.d.ts +12 -0
  131. package/dist/store/agent-sessions/index.js +19 -0
  132. package/dist/store/agent-sessions/pglite.d.ts +12 -0
  133. package/dist/store/agent-sessions/pglite.js +7 -0
  134. package/dist/store/agent-sessions/ports.d.ts +24 -0
  135. package/dist/store/agent-sessions/ports.js +2 -0
  136. package/dist/store/agent-sessions/postgres.d.ts +12 -0
  137. package/dist/store/agent-sessions/postgres.js +7 -0
  138. package/dist/store/agent-sessions/schema.d.ts +3 -0
  139. package/dist/store/agent-sessions/schema.js +23 -0
  140. package/dist/store/agent-sessions/sql.d.ts +35 -0
  141. package/dist/store/agent-sessions/sql.js +59 -0
  142. package/dist/store/agent-sessions/sqlite.d.ts +117 -0
  143. package/dist/store/agent-sessions/sqlite.js +128 -0
  144. package/dist/store/event-log/sqlite.d.ts +75 -0
  145. package/dist/store/event-log/sqlite.js +230 -0
  146. package/dist/surface-exchanges.d.ts +97 -0
  147. package/dist/surface-exchanges.js +153 -0
  148. package/dist/terminal-session.d.ts +43 -11
  149. package/dist/terminal-session.js +22 -20
  150. package/dist/tokens.js +4 -4
  151. package/dist/tool-audit.d.ts +114 -0
  152. package/dist/tool-audit.js +95 -0
  153. package/dist/tool-executor.d.ts +22 -7
  154. package/dist/tool-executor.js +16 -16
  155. package/dist/tool-result-media.d.ts +3 -1
  156. package/dist/tool-result-media.js +1 -1
  157. package/dist/tool-result-surfaces.d.ts +3 -1
  158. package/dist/tool-result-surfaces.js +1 -1
  159. package/package.json +127 -9
@@ -1,3 +1,23 @@
1
+ /**
2
+ * @module legacy-data-migration
3
+ *
4
+ * A one-shot, idempotent, host-configured migrator for moving a daemon's
5
+ * data root from one location to another (e.g. a pre-packaging install that
6
+ * ran out of a repo checkout, moved to a per-namespace directory under an
7
+ * OS user-data location). The mechanism — refuse-if-missing-proof,
8
+ * refuse-if-destination-populated, refuse-if-already-migrated, reject
9
+ * symlinks, stage-then-atomically-promote, rollback-on-any-failure — is
10
+ * product-neutral; the *shape* of a data root's payload (which files/dirs
11
+ * constitute "real data", and which single entry proves a directory holds
12
+ * real payload rather than an empty scaffold) is host-supplied via
13
+ * {@link LegacyDataMigrationConfig}, never hardcoded here.
14
+ *
15
+ * Sync by design: intended to run at daemon startup before any database
16
+ * connection opens, so it can't race a concurrent open of the destination.
17
+ */
18
+ import * as nodeFilesystem from 'node:fs';
19
+ /** Node filesystem ABI, injected so migration can run against a host-owned adapter. */
20
+ export type LegacyFilesystemPort = Pick<typeof nodeFilesystem, 'statSync' | 'lstatSync' | 'existsSync' | 'readdirSync' | 'mkdirSync' | 'cpSync' | 'renameSync' | 'rmSync' | 'writeFileSync'>;
1
21
  /** Which files/dirs constitute a data root's migratable payload, host-supplied. */
2
22
  export interface LegacyDataMigrationConfig {
3
23
  /** Every file/dir under a data root that is part of the migratable payload. */
@@ -19,8 +39,11 @@ export interface MigrateLegacyDataDirOptions extends LegacyDataMigrationConfig {
19
39
  dataDir: string;
20
40
  /** Optional logger. Defaults to console.log. */
21
41
  logger?: {
22
- info(message: string): void;
42
+ info(args: {
43
+ message: string;
44
+ }): void;
23
45
  };
46
+ filesystem?: LegacyFilesystemPort;
24
47
  /**
25
48
  * Test seam. The default writes the JSON marker with fs.writeFileSync;
26
49
  * tests inject a function that throws so the rollback path (which removes
@@ -29,7 +52,11 @@ export interface MigrateLegacyDataDirOptions extends LegacyDataMigrationConfig {
29
52
  * pass this.
30
53
  * @internal
31
54
  */
32
- writeMarker?: (dataDir: string, legacyDir: string, markerFile: string) => void;
55
+ writeMarker?: (args: {
56
+ dataDir: string;
57
+ legacyDir: string;
58
+ markerFile: string;
59
+ }) => void;
33
60
  }
34
61
  export type MigrateStatus = 'noop' | 'migrated' | 'skipped';
35
62
  export interface MigrateLegacyDataDirResult {
@@ -45,7 +72,10 @@ export interface MigrateLegacyDataDirResult {
45
72
  */
46
73
  export declare class LegacyMigrationError extends Error {
47
74
  readonly code: string;
48
- constructor(code: string, message: string);
75
+ constructor({ code, message }: {
76
+ readonly code: string;
77
+ readonly message: string;
78
+ });
49
79
  }
50
80
  /**
51
81
  * Returns true when `dataDir` looks like a fresh / never-used data root:
@@ -58,7 +88,12 @@ export declare class LegacyMigrationError extends Error {
58
88
  * @param dataDir - The data root to check.
59
89
  * @param config - The host's payload/proof-entry configuration.
60
90
  */
61
- export declare function dataDirIsEmptyOrFresh(dataDir: string, config: LegacyDataMigrationConfig): boolean;
91
+ export declare function dataDirIsEmptyOrFresh({ dataDir, config }: {
92
+ readonly dataDir: string;
93
+ readonly config: LegacyDataMigrationConfig;
94
+ }, { filesystem: fs }?: {
95
+ filesystem?: LegacyFilesystemPort;
96
+ }): boolean;
62
97
  /**
63
98
  * Returns true when `legacyDir` contains a payload worth migrating: it
64
99
  * exists and its configured `proofEntry` exists as a file.
@@ -66,7 +101,12 @@ export declare function dataDirIsEmptyOrFresh(dataDir: string, config: LegacyDat
66
101
  * @param legacyDir - The candidate legacy data directory.
67
102
  * @param config - The host's payload/proof-entry configuration.
68
103
  */
69
- export declare function legacyDirHasPayload(legacyDir: string, config: LegacyDataMigrationConfig): boolean;
104
+ export declare function legacyDirHasPayload({ legacyDir, config }: {
105
+ readonly legacyDir: string;
106
+ readonly config: LegacyDataMigrationConfig;
107
+ }, { filesystem: fs }?: {
108
+ filesystem?: LegacyFilesystemPort;
109
+ }): boolean;
70
110
  /**
71
111
  * Returns the list of configured payload entries that already exist under
72
112
  * `dataDir`. Migration refuses to merge on top of any of them — a database
@@ -76,7 +116,12 @@ export declare function legacyDirHasPayload(legacyDir: string, config: LegacyDat
76
116
  * @param dataDir - The data root to check.
77
117
  * @param config - The host's payload/proof-entry configuration.
78
118
  */
79
- export declare function dataDirHasExistingPayload(dataDir: string, config: LegacyDataMigrationConfig): string[];
119
+ export declare function dataDirHasExistingPayload({ dataDir, config }: {
120
+ readonly dataDir: string;
121
+ readonly config: LegacyDataMigrationConfig;
122
+ }, { filesystem: fs }?: {
123
+ filesystem?: LegacyFilesystemPort;
124
+ }): string[];
80
125
  /**
81
126
  * Move staged payload entries into the final `dataDir`. Each entry is moved
82
127
  * with `renameSync` (atomic on the same filesystem); a cross-device rename
@@ -89,7 +134,13 @@ export declare function dataDirHasExistingPayload(dataDir: string, config: Legac
89
134
  * @param entries - The payload entries to promote.
90
135
  * @returns The entries that were placed into `dataDir` before any failure.
91
136
  */
92
- export declare function promoteStaged(stagingDir: string, dataDir: string, entries: readonly string[]): readonly string[];
137
+ export declare function promoteStaged({ stagingDir, dataDir, entries }: {
138
+ readonly stagingDir: string;
139
+ readonly dataDir: string;
140
+ readonly entries: readonly string[];
141
+ }, { filesystem: fs }?: {
142
+ filesystem?: LegacyFilesystemPort;
143
+ }): readonly string[];
93
144
  /**
94
145
  * One-shot, idempotent legacy data migrator. Synchronous so it can run at
95
146
  * module import time before a database connection opens. Throws
@@ -100,5 +151,5 @@ export declare function promoteStaged(stagingDir: string, dataDir: string, entri
100
151
  * @returns The migration outcome (`noop` when unconfigured, `skipped` when
101
152
  * already migrated, `migrated` with the copied entry list otherwise).
102
153
  */
103
- export declare function migrateLegacyDataDirSync(options: MigrateLegacyDataDirOptions): MigrateLegacyDataDirResult;
154
+ export declare function migrateLegacyDataDirSync(requiredArgs: Pick<MigrateLegacyDataDirOptions, "legacyDir" | "dataDir" | "payloadEntries" | "proofEntry">, optionalArgs?: Pick<MigrateLegacyDataDirOptions, "logger" | "writeMarker" | "markerFile" | "filesystem">): MigrateLegacyDataDirResult;
104
155
  //# sourceMappingURL=legacy-data-migration.d.ts.map
@@ -15,7 +15,7 @@
15
15
  * Sync by design: intended to run at daemon startup before any database
16
16
  * connection opens, so it can't race a concurrent open of the destination.
17
17
  */
18
- import * as fs from 'node:fs';
18
+ import * as nodeFilesystem from 'node:fs';
19
19
  import * as path from 'node:path';
20
20
  const DEFAULT_MARKER_FILE = '.migrated-from';
21
21
  /**
@@ -26,13 +26,13 @@ const DEFAULT_MARKER_FILE = '.migrated-from';
26
26
  */
27
27
  export class LegacyMigrationError extends Error {
28
28
  code;
29
- constructor(code, message) {
29
+ constructor({ code, message }) {
30
30
  super(message);
31
31
  this.code = code;
32
32
  this.name = 'LegacyMigrationError';
33
33
  }
34
34
  }
35
- function isExistingDir(p) {
35
+ function isExistingDir(p, fs) {
36
36
  try {
37
37
  return fs.statSync(p).isDirectory();
38
38
  }
@@ -40,7 +40,7 @@ function isExistingDir(p) {
40
40
  return false;
41
41
  }
42
42
  }
43
- function isExistingFile(p) {
43
+ function isExistingFile(p, fs) {
44
44
  try {
45
45
  return fs.statSync(p).isFile();
46
46
  }
@@ -59,10 +59,10 @@ function isExistingFile(p) {
59
59
  * @param dataDir - The data root to check.
60
60
  * @param config - The host's payload/proof-entry configuration.
61
61
  */
62
- export function dataDirIsEmptyOrFresh(dataDir, config) {
63
- if (!isExistingDir(dataDir))
62
+ export function dataDirIsEmptyOrFresh({ dataDir, config }, { filesystem: fs = nodeFilesystem } = {}) {
63
+ if (!isExistingDir(dataDir, fs))
64
64
  return true;
65
- return !isExistingFile(path.join(dataDir, config.proofEntry));
65
+ return !isExistingFile(path.join(dataDir, config.proofEntry), fs);
66
66
  }
67
67
  /**
68
68
  * Returns true when `legacyDir` contains a payload worth migrating: it
@@ -71,10 +71,10 @@ export function dataDirIsEmptyOrFresh(dataDir, config) {
71
71
  * @param legacyDir - The candidate legacy data directory.
72
72
  * @param config - The host's payload/proof-entry configuration.
73
73
  */
74
- export function legacyDirHasPayload(legacyDir, config) {
75
- if (!isExistingDir(legacyDir))
74
+ export function legacyDirHasPayload({ legacyDir, config }, { filesystem: fs = nodeFilesystem } = {}) {
75
+ if (!isExistingDir(legacyDir, fs))
76
76
  return false;
77
- return isExistingFile(path.join(legacyDir, config.proofEntry));
77
+ return isExistingFile(path.join(legacyDir, config.proofEntry), fs);
78
78
  }
79
79
  /**
80
80
  * Returns the list of configured payload entries that already exist under
@@ -85,8 +85,8 @@ export function legacyDirHasPayload(legacyDir, config) {
85
85
  * @param dataDir - The data root to check.
86
86
  * @param config - The host's payload/proof-entry configuration.
87
87
  */
88
- export function dataDirHasExistingPayload(dataDir, config) {
89
- if (!isExistingDir(dataDir))
88
+ export function dataDirHasExistingPayload({ dataDir, config }, { filesystem: fs = nodeFilesystem } = {}) {
89
+ if (!isExistingDir(dataDir, fs))
90
90
  return [];
91
91
  return config.payloadEntries.filter((entry) => fs.existsSync(path.join(dataDir, entry)));
92
92
  }
@@ -95,26 +95,26 @@ export function dataDirHasExistingPayload(dataDir, config) {
95
95
  * preserved symlink would let a downstream reader escape the data root.
96
96
  * @internal
97
97
  */
98
- function assertNoSymlinks(srcRoot, displayPath = srcRoot) {
98
+ function assertNoSymlinks(srcRoot, fs, displayPath = srcRoot) {
99
99
  const stat = fs.lstatSync(srcRoot);
100
100
  if (stat.isSymbolicLink()) {
101
- throw new LegacyMigrationError('symlink_in_payload', `legacy payload contains a symlink at "${displayPath}"; refusing to migrate`);
101
+ throw new LegacyMigrationError({ code: 'symlink_in_payload', message: `legacy payload contains a symlink at "${displayPath}"; refusing to migrate` });
102
102
  }
103
103
  if (!stat.isDirectory())
104
104
  return;
105
105
  for (const child of fs.readdirSync(srcRoot)) {
106
- assertNoSymlinks(path.join(srcRoot, child), path.join(displayPath, child));
106
+ assertNoSymlinks(path.join(srcRoot, child), fs, path.join(displayPath, child));
107
107
  }
108
108
  }
109
109
  /** Stage every present payload entry into `stagingDir`. Returns the entries actually copied. @internal */
110
- function stagePayload(legacyDir, stagingDir, payloadEntries) {
110
+ function stagePayload(legacyDir, stagingDir, payloadEntries, fs) {
111
111
  fs.mkdirSync(stagingDir, { recursive: true });
112
112
  const copied = [];
113
113
  for (const entry of payloadEntries) {
114
114
  const src = path.join(legacyDir, entry);
115
115
  if (!fs.existsSync(src))
116
116
  continue;
117
- assertNoSymlinks(src);
117
+ assertNoSymlinks(src, fs);
118
118
  const dst = path.join(stagingDir, entry);
119
119
  fs.cpSync(src, dst, { recursive: true, force: true, errorOnExist: false });
120
120
  copied.push(entry);
@@ -133,7 +133,7 @@ function stagePayload(legacyDir, stagingDir, payloadEntries) {
133
133
  * @param entries - The payload entries to promote.
134
134
  * @returns The entries that were placed into `dataDir` before any failure.
135
135
  */
136
- export function promoteStaged(stagingDir, dataDir, entries) {
136
+ export function promoteStaged({ stagingDir, dataDir, entries }, { filesystem: fs = nodeFilesystem } = {}) {
137
137
  fs.mkdirSync(dataDir, { recursive: true });
138
138
  const promoted = [];
139
139
  try {
@@ -157,17 +157,17 @@ export function promoteStaged(stagingDir, dataDir, entries) {
157
157
  }
158
158
  }
159
159
  catch (err) {
160
- rollbackPromoted(dataDir, promoted);
160
+ rollbackPromoted(dataDir, promoted, fs);
161
161
  throw err;
162
162
  }
163
163
  return promoted;
164
164
  }
165
- function rollbackPromoted(dataDir, promoted) {
165
+ function rollbackPromoted(dataDir, promoted, fs) {
166
166
  for (const entry of promoted) {
167
167
  fs.rmSync(path.join(dataDir, entry), { recursive: true, force: true });
168
168
  }
169
169
  }
170
- function writeMarkerDefault(dataDir, legacyDir, markerFile) {
170
+ function writeMarkerDefault({ dataDir, legacyDir, markerFile }, fs) {
171
171
  const marker = path.join(dataDir, markerFile);
172
172
  const temp = `${marker}.tmp-${process.pid}-${Date.now()}`;
173
173
  const payload = JSON.stringify({ legacyDir: path.resolve(legacyDir), migratedAt: new Date().toISOString() }, null, 2);
@@ -190,8 +190,10 @@ function writeMarkerDefault(dataDir, legacyDir, markerFile) {
190
190
  * @returns The migration outcome (`noop` when unconfigured, `skipped` when
191
191
  * already migrated, `migrated` with the copied entry list otherwise).
192
192
  */
193
- export function migrateLegacyDataDirSync(options) {
194
- const log = options.logger ?? { info: (m) => console.log(`[jini-migrate] ${m}`) };
193
+ export function migrateLegacyDataDirSync(requiredArgs, optionalArgs = {}) {
194
+ const options = { ...requiredArgs, ...optionalArgs };
195
+ const fs = options.filesystem ?? nodeFilesystem;
196
+ const log = options.logger ?? { info: ({ message }) => console.log(`[jini-migrate] ${message}`) };
195
197
  const markerFile = options.markerFile ?? DEFAULT_MARKER_FILE;
196
198
  const raw = options.legacyDir;
197
199
  if (raw === undefined || raw.length === 0) {
@@ -209,31 +211,34 @@ export function migrateLegacyDataDirSync(options) {
209
211
  if (fs.existsSync(markerPath)) {
210
212
  return { status: 'skipped', reason: 'migration marker already present' };
211
213
  }
212
- if (!legacyDirHasPayload(legacyDir, options)) {
213
- throw new LegacyMigrationError('legacy_dir_invalid', `legacy data dir "${legacyDir}" is not a usable legacy data dir (expected "${options.proofEntry}" directly inside it).`);
214
+ if (!legacyDirHasPayload({ legacyDir: legacyDir, config: options }, { filesystem: fs })) {
215
+ throw new LegacyMigrationError({ code: 'legacy_dir_invalid', message: `legacy data dir "${legacyDir}" is not a usable legacy data dir (expected "${options.proofEntry}" directly inside it).` });
214
216
  }
215
- const existing = dataDirHasExistingPayload(dataDir, options);
217
+ const existing = dataDirHasExistingPayload({ dataDir: dataDir, config: options }, { filesystem: fs });
216
218
  if (existing.length > 0) {
217
- throw new LegacyMigrationError('data_dir_not_empty', `data dir "${dataDir}" already contains payload entries (${existing.join(', ')}); refusing to merge legacy data on top. Move the existing data aside or pick a fresh data root before retrying.`);
219
+ throw new LegacyMigrationError({ code: 'data_dir_not_empty', message: `data dir "${dataDir}" already contains payload entries (${existing.join(', ')}); refusing to merge legacy data on top. Move the existing data aside or pick a fresh data root before retrying.` });
218
220
  }
219
- log.info(`migrating legacy data from "${legacyDir}" to "${dataDir}"`);
221
+ log.info({ message: `migrating legacy data from "${legacyDir}" to "${dataDir}"` });
220
222
  fs.mkdirSync(path.dirname(dataDir), { recursive: true });
221
223
  const stagingDir = path.join(path.dirname(dataDir), `${path.basename(dataDir)}.migrate-${process.pid}-${Date.now()}`);
222
224
  let copied;
223
225
  let promoted = [];
224
226
  try {
225
- copied = stagePayload(legacyDir, stagingDir, options.payloadEntries);
226
- promoted = promoteStaged(stagingDir, dataDir, copied);
227
- (options.writeMarker ?? writeMarkerDefault)(dataDir, legacyDir, markerFile);
227
+ copied = stagePayload(legacyDir, stagingDir, options.payloadEntries, fs);
228
+ promoted = promoteStaged({ stagingDir: stagingDir, dataDir: dataDir, entries: copied }, { filesystem: fs });
229
+ if (options.writeMarker)
230
+ options.writeMarker({ dataDir, legacyDir, markerFile });
231
+ else
232
+ writeMarkerDefault({ dataDir, legacyDir, markerFile }, fs);
228
233
  }
229
234
  catch (err) {
230
- rollbackPromoted(dataDir, promoted);
235
+ rollbackPromoted(dataDir, promoted, fs);
231
236
  fs.rmSync(markerPath, { force: true });
232
237
  fs.rmSync(stagingDir, { recursive: true, force: true });
233
238
  throw err;
234
239
  }
235
240
  fs.rmSync(stagingDir, { recursive: true, force: true });
236
- log.info(`migration complete: copied ${copied.length} entr${copied.length === 1 ? 'y' : 'ies'} (${copied.join(', ')})`);
241
+ log.info({ message: `migration complete: copied ${copied.length} entr${copied.length === 1 ? 'y' : 'ies'} (${copied.join(', ')})` });
237
242
  return { status: 'migrated', reason: 'copied legacy payload', copied };
238
243
  }
239
244
  //# sourceMappingURL=legacy-data-migration.js.map
@@ -0,0 +1,130 @@
1
+ import { type Principal, type ToolRegistry } from '@jini-ai/core';
2
+ import type { ToolExecutor } from './tool-executor.js';
3
+ import type { IdGenerator } from '@jini-ai/core/primitives';
4
+ /**
5
+ * A `Principal` narrowed to tools that only read.
6
+ *
7
+ * An HTTP route preflight only ever validates the OUTER `toolId` — the one the caller
8
+ * named in the HTTP request. It cannot see what a consumer's own `ToolExecutor` composition does
9
+ * once `bridge.execute()` hands control to `deps.toolExecutor`: a decorator that composition
10
+ * stacks on top of the bare executor (a recovery loop that retries through a different tool id
11
+ * after a failure, for instance) can dispatch a tool the caller never named, using the exact same
12
+ * `principal` the outer call carried — one hop past the only check the HTTP route ever ran. Adding a
13
+ * second request-shaped flag would not close that gap either: it would have to be threaded, by
14
+ * hand, through every decorator between here and the handler, and a decorator that forgot would
15
+ * fail open silently. `principal` is the one value every layer already passes unchanged, so the
16
+ * constraint travels as an ATTENUATED IDENTITY instead — a principal permitted to invoke only
17
+ * read-only tools, checkable by {@link refuseNonReadOnlyDispatch} at any depth a consumer's own
18
+ * composition cares to ask.
19
+ *
20
+ * A distinct field rather than a `Principal.roles` entry: `roles` is what a `ToolPolicy` branches
21
+ * on, and a synthetic role would silently join that decision for every registration in the
22
+ * process. This field is inert to everything except {@link refuseNonReadOnlyDispatch}.
23
+ */
24
+ export interface ReadOnlyConstrainedPrincipal extends Principal {
25
+ readonly toolAccess: 'read-only';
26
+ }
27
+ /**
28
+ * Default daemon refusal wording, replaceable by the host. Unknown registrations and writes share
29
+ * one refusal: neither corroborates a safe read, and catalog discovery already reveals existence.
30
+ * Missing verification metadata always refuses, naming the missing wiring so the host can fix it.
31
+ */
32
+ export declare const defaultDaemonMessages: {
33
+ readOnly: {
34
+ unverifiableMessage: string;
35
+ toolRefusalMessage: ({ toolId }: {
36
+ toolId: string;
37
+ }) => string;
38
+ };
39
+ };
40
+ /** Required host wording; absent metadata is always a refusal. */
41
+ export interface ReadOnlyToolMessages {
42
+ readonly unverifiableMessage: string;
43
+ toolRefusalMessage(required: {
44
+ toolId: string;
45
+ }): string;
46
+ }
47
+ export interface ReadOnlyToolCheckRequired {
48
+ readonly toolId: string;
49
+ readonly registry: Pick<ToolRegistry, 'list'> | undefined;
50
+ readonly messages: ReadOnlyToolMessages;
51
+ }
52
+ export interface ReadOnlyToolConstraintRequired {
53
+ readonly inner: ToolExecutor;
54
+ readonly registry: Pick<ToolRegistry, 'list'> | undefined;
55
+ readonly idGenerator: IdGenerator;
56
+ readonly messages: ReadOnlyToolMessages;
57
+ }
58
+ /** @returns A copied, attenuated principal; its roles are unchanged. */
59
+ export declare function constrainPrincipalToReadOnlyTools({ principal }: {
60
+ principal: Principal;
61
+ }, _optional?: Record<string, never>): ReadOnlyConstrainedPrincipal;
62
+ /** @returns Whether attenuation survived the executor/decorator boundary. */
63
+ export declare function principalIsReadOnlyConstrained({ principal }: {
64
+ principal: Principal;
65
+ }, _optional?: Record<string, never>): boolean;
66
+ /** @returns Required host wording for an unknown or non-read-only tool. */
67
+ export declare function readOnlyToolRefusalMessage({ toolId, messages }: {
68
+ toolId: string;
69
+ messages: ReadOnlyToolMessages;
70
+ }, _optional?: Record<string, never>): string;
71
+ /**
72
+ * Single read-only registration decision, shared by routes and inner dispatch.
73
+ * @returns Null for an explicitly read-only registration, otherwise host refusal text.
74
+ */
75
+ export declare function checkReadOnlyTool({ toolId, registry, messages }: ReadOnlyToolCheckRequired, _optional?: Record<string, never>): string | null;
76
+ /**
77
+ * THE read-only dispatch decision for any tool id a consumer's own `ToolExecutor` composition is
78
+ * about to invoke — the outer call the route already checked, or a nested one a decorator issues
79
+ * one or more hops in. Every consumer's own composition should ask this before dispatching on
80
+ * behalf of a principal it did not itself resolve; none should re-implement the check, so changing
81
+ * what the constraint means stays one edit.
82
+ *
83
+ * @param required.principal - The principal the dispatch would run under. Unconstrained ⇒ always
84
+ * `null` — this is a no-op for every pre-existing caller that never attenuates a principal.
85
+ * @param required.toolId - The id actually about to be dispatched, which is not necessarily the id
86
+ * named in the original request — that difference is the whole reason this function exists.
87
+ * @param required.registry - Descriptors to resolve `toolId` against; `undefined` ⇒ refuse (an
88
+ * unverifiable constrained dispatch is refused, never waived, matching `checkReadOnlyTool`).
89
+ * @returns `null` when the dispatch may proceed, otherwise the refusal text to report.
90
+ * @complexity O(1) for an unconstrained principal; O(n) in registered tool count for a constrained
91
+ * one, since `ToolRegistry` exposes enumeration rather than lookup by id.
92
+ */
93
+ export declare function refuseNonReadOnlyDispatch(required: ReadOnlyToolCheckRequired & {
94
+ readonly principal: Principal;
95
+ }, _optional?: Record<string, never>): string | null;
96
+ /** @returns Caller-formatted recovery refusal, preserving the original refusal verbatim as input. */
97
+ export declare function readOnlyRemedyRefusalMessage({ refusal, formatMessage }: {
98
+ refusal: string;
99
+ formatMessage: (required: {
100
+ refusal: string;
101
+ }) => string;
102
+ }, _optional?: Record<string, never>): string;
103
+ /**
104
+ * Wraps `inner` so a read-only-constrained principal (one {@link constrainPrincipalToReadOnlyTools}
105
+ * attenuated) cannot dispatch a tool that isn't registered read-only — whichever layer chose the
106
+ * id. This is what turns the attenuation into an actual gate rather than a field nobody reads:
107
+ * {@link refuseNonReadOnlyDispatch} is the decision, this is its enforcement point.
108
+ *
109
+ * **Compose this as close to the bare `ToolExecutor` as your own stack allows** — directly around
110
+ * `@jini-ai/daemon`'s `createToolExecutor` output, beneath any decorator of your own that might
111
+ * dispatch a tool id the caller never named (a retry, a recovery loop that calls a different tool
112
+ * as a remedy, ...). Every such decorator's own `inner.execute` call still has to come through here
113
+ * to reach a handler, which is what makes this a gate on the CLASS of defect (an unnamed nested
114
+ * dispatch) rather than on one instance of it.
115
+ *
116
+ * `delegatedToolExecuteRoute`'s own `handle` additionally composes this around whatever
117
+ * `ToolExecutor` a host supplies, whenever a call attenuates its principal. That outer composition
118
+ * only re-covers the SAME outer `toolId` the route preflight already checked — it
119
+ * cannot see a nested dispatch a host's own inner decorator issues, because that decorator holds a
120
+ * reference to whatever `ToolExecutor` IT was built with, not to the HTTP route's wrapped one. Closing
121
+ * the nested case for a given host's stack requires that host to compose this decorator itself,
122
+ * innermost, in its own `ToolExecutor` construction — this export exists so it can, without having
123
+ * to rediscover or reimplement the mechanism.
124
+ *
125
+ * @returns A drop-in `ToolExecutor`; `resumeConfirmation`/`cancel`/`getAuditRecord` delegate
126
+ * straight through.
127
+ * @complexity One extra property read per unconstrained call; one registry scan per constrained one.
128
+ */
129
+ export declare function withReadOnlyToolConstraint({ inner, registry, idGenerator, messages }: ReadOnlyToolConstraintRequired, _optional?: Record<string, never>): ToolExecutor;
130
+ //# sourceMappingURL=read-only-tools.d.ts.map
@@ -0,0 +1,115 @@
1
+ import { isReadOnlyTool } from '@jini-ai/core';
2
+ /**
3
+ * Default daemon refusal wording, replaceable by the host. Unknown registrations and writes share
4
+ * one refusal: neither corroborates a safe read, and catalog discovery already reveals existence.
5
+ * Missing verification metadata always refuses, naming the missing wiring so the host can fix it.
6
+ */
7
+ export const defaultDaemonMessages = {
8
+ readOnly: {
9
+ unverifiableMessage: 'this host cannot verify read-only tools — POST /api/delegated-tool-calls was mounted without DelegatedToolsHttpDeps.toolRegistry, so a requireReadOnly call cannot be checked and is refused',
10
+ toolRefusalMessage: ({ toolId }) => `tool "${toolId}" is not registered as read-only — this gateway executes only tools whose registration declares readOnly; call it through execute_delegated_tool instead`,
11
+ },
12
+ };
13
+ /** @returns A copied, attenuated principal; its roles are unchanged. */
14
+ export function constrainPrincipalToReadOnlyTools({ principal }, _optional = {}) {
15
+ return { ...principal, toolAccess: 'read-only' };
16
+ }
17
+ // Read structurally so passing through layers typed as the base Principal cannot erase the
18
+ // execution's authority constraint.
19
+ /** @returns Whether attenuation survived the executor/decorator boundary. */
20
+ export function principalIsReadOnlyConstrained({ principal }, _optional = {}) {
21
+ return 'toolAccess' in principal && principal.toolAccess === 'read-only';
22
+ }
23
+ /** @returns Required host wording for an unknown or non-read-only tool. */
24
+ export function readOnlyToolRefusalMessage({ toolId, messages }, _optional = {}) {
25
+ return messages.toolRefusalMessage({ toolId });
26
+ }
27
+ // Unknown registrations and omitted read-only classification both lack evidence of safety.
28
+ // Every dispatch/preflight consults this decision; recovery preflight prevents asking a human
29
+ // for form input that the innermost gate would refuse after submission.
30
+ /**
31
+ * Single read-only registration decision, shared by routes and inner dispatch.
32
+ * @returns Null for an explicitly read-only registration, otherwise host refusal text.
33
+ */
34
+ export function checkReadOnlyTool({ toolId, registry, messages }, _optional = {}) {
35
+ if (registry === undefined)
36
+ return messages.unverifiableMessage;
37
+ const descriptor = registry.list({}).find((candidate) => candidate.id === toolId);
38
+ if (isReadOnlyTool({ descriptor }))
39
+ return null;
40
+ return readOnlyToolRefusalMessage({ toolId, messages });
41
+ }
42
+ /**
43
+ * THE read-only dispatch decision for any tool id a consumer's own `ToolExecutor` composition is
44
+ * about to invoke — the outer call the route already checked, or a nested one a decorator issues
45
+ * one or more hops in. Every consumer's own composition should ask this before dispatching on
46
+ * behalf of a principal it did not itself resolve; none should re-implement the check, so changing
47
+ * what the constraint means stays one edit.
48
+ *
49
+ * @param required.principal - The principal the dispatch would run under. Unconstrained ⇒ always
50
+ * `null` — this is a no-op for every pre-existing caller that never attenuates a principal.
51
+ * @param required.toolId - The id actually about to be dispatched, which is not necessarily the id
52
+ * named in the original request — that difference is the whole reason this function exists.
53
+ * @param required.registry - Descriptors to resolve `toolId` against; `undefined` ⇒ refuse (an
54
+ * unverifiable constrained dispatch is refused, never waived, matching `checkReadOnlyTool`).
55
+ * @returns `null` when the dispatch may proceed, otherwise the refusal text to report.
56
+ * @complexity O(1) for an unconstrained principal; O(n) in registered tool count for a constrained
57
+ * one, since `ToolRegistry` exposes enumeration rather than lookup by id.
58
+ */
59
+ export function refuseNonReadOnlyDispatch(required, _optional = {}) {
60
+ if (!principalIsReadOnlyConstrained({ principal: required.principal }))
61
+ return null;
62
+ return checkReadOnlyTool(required);
63
+ }
64
+ // Report the original failure alongside the refused remedy; silently hiding an automatic
65
+ // recovery attempt would mislead the caller even when the constraint correctly prevents writes.
66
+ /** @returns Caller-formatted recovery refusal, preserving the original refusal verbatim as input. */
67
+ export function readOnlyRemedyRefusalMessage({ refusal, formatMessage }, _optional = {}) {
68
+ return formatMessage({ refusal });
69
+ }
70
+ // A route can validate the caller's read-only tool and still miss a recovery decorator choosing
71
+ // a writing remedy afterward. Enforce on every actual dispatch at the innermost executor to
72
+ // close that class of authority laundering, including dispatchers introduced later.
73
+ /**
74
+ * Wraps `inner` so a read-only-constrained principal (one {@link constrainPrincipalToReadOnlyTools}
75
+ * attenuated) cannot dispatch a tool that isn't registered read-only — whichever layer chose the
76
+ * id. This is what turns the attenuation into an actual gate rather than a field nobody reads:
77
+ * {@link refuseNonReadOnlyDispatch} is the decision, this is its enforcement point.
78
+ *
79
+ * **Compose this as close to the bare `ToolExecutor` as your own stack allows** — directly around
80
+ * `@jini-ai/daemon`'s `createToolExecutor` output, beneath any decorator of your own that might
81
+ * dispatch a tool id the caller never named (a retry, a recovery loop that calls a different tool
82
+ * as a remedy, ...). Every such decorator's own `inner.execute` call still has to come through here
83
+ * to reach a handler, which is what makes this a gate on the CLASS of defect (an unnamed nested
84
+ * dispatch) rather than on one instance of it.
85
+ *
86
+ * `delegatedToolExecuteRoute`'s own `handle` additionally composes this around whatever
87
+ * `ToolExecutor` a host supplies, whenever a call attenuates its principal. That outer composition
88
+ * only re-covers the SAME outer `toolId` the route preflight already checked — it
89
+ * cannot see a nested dispatch a host's own inner decorator issues, because that decorator holds a
90
+ * reference to whatever `ToolExecutor` IT was built with, not to the HTTP route's wrapped one. Closing
91
+ * the nested case for a given host's stack requires that host to compose this decorator itself,
92
+ * innermost, in its own `ToolExecutor` construction — this export exists so it can, without having
93
+ * to rediscover or reimplement the mechanism.
94
+ *
95
+ * @returns A drop-in `ToolExecutor`; `resumeConfirmation`/`cancel`/`getAuditRecord` delegate
96
+ * straight through.
97
+ * @complexity One extra property read per unconstrained call; one registry scan per constrained one.
98
+ */
99
+ export function withReadOnlyToolConstraint({ inner, registry, idGenerator, messages }, _optional = {}) {
100
+ return {
101
+ execute: async (requiredArgs, optionalArgs = {}) => {
102
+ const { principal, toolId } = requiredArgs;
103
+ const refusal = refuseNonReadOnlyDispatch({ principal, toolId, registry, messages });
104
+ // Deny before inner: no handler or kernel audit record exists. Use the authorization result
105
+ // union instead of throwing, and mint a denial ID because there is no inner execution ID.
106
+ if (refusal !== null)
107
+ return { executionId: idGenerator.newId(), status: 'denied', error: refusal };
108
+ return inner.execute(requiredArgs, optionalArgs);
109
+ },
110
+ resumeConfirmation: (requiredArgs) => inner.resumeConfirmation(requiredArgs),
111
+ cancel: (requiredArgs) => inner.cancel(requiredArgs),
112
+ getAuditRecord: (requiredArgs) => inner.getAuditRecord(requiredArgs),
113
+ };
114
+ }
115
+ //# sourceMappingURL=read-only-tools.js.map
@@ -7,11 +7,11 @@
7
7
  * This exists because `DelegatedToolBridge.execute()` (this package's `delegated-tool-bridge.ts`)
8
8
  * calls `lifecycle.emit()` directly, in-process — there was previously no way for a caller that
9
9
  * executed a tool somewhere else to report the outcome back into this run's log. That absence is
10
- * what forced `ToolExecutor`/`ToolRegistry`/`RunLifecycle` to all live in one process per run (see
11
- * this repo's own `tovu-learnings.md` §1a). This module is the missing half: the same two event
10
+ * what forced `ToolExecutor`/`ToolRegistry`/`RunLifecycle` to live in one process per run: only an
11
+ * in-process lifecycle could append those events. This module supplies the same two event
12
12
  * shapes `DelegatedToolBridge.execute()` already emits (`tool_use` then `tool_result`), exposed as
13
13
  * a small, explicit recording API a *transport* (this package does not itself expose one — see
14
- * `@jini-ai/http-kit`'s `remote-run-events.ts`) can wrap with authentication and call remotely.
14
+ * `@jini-ai/daemon/http`'s `remote-run-events.ts`) can wrap with authentication and call remotely.
15
15
  *
16
16
  * This does not change how tool execution is authorized. A caller of `recordToolUse`/
17
17
  * `recordToolResult` has, by definition, already run the tool through its OWN `ToolExecutor` (with
@@ -34,9 +34,15 @@ export interface RemoteToolResultRecord {
34
34
  }
35
35
  export interface RemoteToolEventRecorder {
36
36
  /** Records that a tool call started. Throws (matching `RunLifecycle.emit`) if `runId` is unknown or already terminal. */
37
- recordToolUse(runId: string, record: RemoteToolUseRecord): Promise<RunProtocolEvent>;
37
+ recordToolUse(args: {
38
+ readonly runId: string;
39
+ readonly record: RemoteToolUseRecord;
40
+ }): Promise<RunProtocolEvent>;
38
41
  /** Records that a tool call finished (or failed) — mirrors `DelegatedToolBridge.execute()`'s own `tool_result` shape exactly. */
39
- recordToolResult(runId: string, record: RemoteToolResultRecord): Promise<RunProtocolEvent>;
42
+ recordToolResult(args: {
43
+ readonly runId: string;
44
+ readonly record: RemoteToolResultRecord;
45
+ }): Promise<RunProtocolEvent>;
40
46
  }
41
47
  export interface CreateRemoteToolEventRecorderOptions {
42
48
  readonly lifecycle: RunLifecycle;
@@ -1,22 +1,22 @@
1
1
  export function createRemoteToolEventRecorder(options) {
2
2
  const { lifecycle } = options;
3
3
  return {
4
- recordToolUse(runId, record) {
5
- return lifecycle.emit(runId, {
6
- event: 'agent',
7
- data: { type: 'tool_use', id: record.toolUseId, name: record.toolId, input: record.input },
8
- });
4
+ recordToolUse({ runId, record }) {
5
+ return lifecycle.emit({ runId: runId, input: {
6
+ event: 'agent',
7
+ data: { type: 'tool_use', id: record.toolUseId, name: record.toolId, input: record.input },
8
+ } });
9
9
  },
10
- recordToolResult(runId, record) {
11
- return lifecycle.emit(runId, {
12
- event: 'agent',
13
- data: {
14
- type: 'tool_result',
15
- toolUseId: record.toolUseId,
16
- content: record.content,
17
- ...(record.isError ? { isError: true } : {}),
18
- },
19
- });
10
+ recordToolResult({ runId, record }) {
11
+ return lifecycle.emit({ runId: runId, input: {
12
+ event: 'agent',
13
+ data: {
14
+ type: 'tool_result',
15
+ toolUseId: record.toolUseId,
16
+ content: record.content,
17
+ ...(record.isError ? { isError: true } : {}),
18
+ },
19
+ } });
20
20
  },
21
21
  };
22
22
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The routine scheduler ({@link ./scheduler.js}'s `RoutineService`, `./schedule.js`'s DST-safe
5
5
  * wall-clock math) plus its HTTP-facing CRUD + run-history counterpart ({@link ./routine-store.js}'s
6
- * `RoutineStore`). See `../../source-map.md`'s dated routines section for full provenance.
6
+ * `RoutineStore`). See `../../archived provenance ledger`'s dated routines section for full provenance.
7
7
  */
8
8
  export * from './types.js';
9
9
  export * from './schedule.js';
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The routine scheduler ({@link ./scheduler.js}'s `RoutineService`, `./schedule.js`'s DST-safe
5
5
  * wall-clock math) plus its HTTP-facing CRUD + run-history counterpart ({@link ./routine-store.js}'s
6
- * `RoutineStore`). See `../../source-map.md`'s dated routines section for full provenance.
6
+ * `RoutineStore`). See `../../archived provenance ledger`'s dated routines section for full provenance.
7
7
  */
8
8
  export * from './types.js';
9
9
  export * from './schedule.js';