runline 0.28.1 → 0.30.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 (73) hide show
  1. package/dist/auth/errors.d.ts +22 -0
  2. package/dist/auth/errors.js +25 -0
  3. package/dist/auth/oauth2.d.ts +21 -0
  4. package/dist/auth/oauth2.js +154 -0
  5. package/dist/auth/token.d.ts +11 -0
  6. package/dist/auth/token.js +231 -0
  7. package/dist/auth/types.d.ts +93 -0
  8. package/dist/auth/types.js +1 -0
  9. package/dist/commands/auth.js +26 -13
  10. package/dist/commands/connection.js +3 -6
  11. package/dist/commands/exec.js +7 -2
  12. package/dist/commands/init.js +9 -2
  13. package/dist/config/loader.d.ts +7 -13
  14. package/dist/config/loader.js +58 -88
  15. package/dist/config/store.d.ts +17 -0
  16. package/dist/config/store.js +85 -0
  17. package/dist/connections/file.d.ts +13 -0
  18. package/dist/connections/file.js +53 -0
  19. package/dist/connections/memory.d.ts +15 -0
  20. package/dist/connections/memory.js +70 -0
  21. package/dist/connections/types.d.ts +47 -0
  22. package/dist/connections/types.js +10 -0
  23. package/dist/core/engine.d.ts +5 -2
  24. package/dist/core/engine.js +48 -55
  25. package/dist/core/oauth.d.ts +20 -15
  26. package/dist/core/oauth.js +195 -98
  27. package/dist/credentials/http.d.ts +19 -0
  28. package/dist/credentials/http.js +109 -0
  29. package/dist/credentials/policy.d.ts +5 -0
  30. package/dist/credentials/policy.js +92 -0
  31. package/dist/credentials/registry.d.ts +31 -0
  32. package/dist/credentials/registry.js +171 -0
  33. package/dist/credentials/transport.d.ts +38 -0
  34. package/dist/credentials/transport.js +283 -0
  35. package/dist/credentials/types.d.ts +80 -0
  36. package/dist/credentials/types.js +1 -0
  37. package/dist/index.d.ts +12 -0
  38. package/dist/index.js +8 -0
  39. package/dist/plugin/types.d.ts +30 -17
  40. package/dist/plugins/_shared/credentialAdapter.js +88 -0
  41. package/dist/plugins/_shared/googleAuth.js +89 -103
  42. package/dist/plugins/_shared/googleCredentials.js +160 -0
  43. package/dist/plugins/_shared/microsoftAuth.js +81 -74
  44. package/dist/plugins/_shared/microsoftCredentials.js +90 -0
  45. package/dist/plugins/_shared/tokenRefresh.js +39 -0
  46. package/dist/plugins/bitwarden/src/index.js +13 -31
  47. package/dist/plugins/gmail/src/index.js +8 -44
  48. package/dist/plugins/googleAppsScript/src/index.js +3 -25
  49. package/dist/plugins/googleCalendar/src/index.js +3 -41
  50. package/dist/plugins/googleContacts/src/index.js +3 -36
  51. package/dist/plugins/googleDocs/src/index.js +5 -0
  52. package/dist/plugins/googleDocs/src/shared.js +2 -30
  53. package/dist/plugins/googleDrive/src/index.js +52 -98
  54. package/dist/plugins/googleSheets/src/index.js +3 -40
  55. package/dist/plugins/googleSlides/src/index.js +5 -39
  56. package/dist/plugins/googleTasks/src/index.js +3 -30
  57. package/dist/plugins/halopsa/src/index.js +16 -31
  58. package/dist/plugins/linear/src/comments.js +2 -2
  59. package/dist/plugins/linear/src/index.js +1 -1
  60. package/dist/plugins/linear/src/issues.js +1 -17
  61. package/dist/plugins/microsoftCalendar/src/index.js +2 -1
  62. package/dist/plugins/microsoftFiles/src/index.js +10 -24
  63. package/dist/plugins/microsoftMail/src/index.js +2 -1
  64. package/dist/plugins/openai/src/index.js +13 -8
  65. package/dist/plugins/paypal/src/index.js +14 -29
  66. package/dist/plugins/plaud/src/index.js +210 -0
  67. package/dist/plugins/plaud/src/shared.js +221 -0
  68. package/dist/plugins/salesforce/src/shared.js +17 -32
  69. package/dist/sdk.d.ts +13 -5
  70. package/dist/sdk.js +35 -29
  71. package/dist/utils/observer.d.ts +2 -0
  72. package/dist/utils/observer.js +11 -0
  73. package/package.json +1 -1
@@ -0,0 +1,221 @@
1
+ import { AuthError, downloadResource, OAuthGrantSchema, } from "runline";
2
+ import * as t from "typebox";
3
+ import { credentialRuntime } from "../../_shared/credentialAdapter.js";
4
+ const BASE = "https://platform.plaud.ai/developer/api";
5
+ /** Public client identifier and fixed callback published in Plaud's official CLI. */
6
+ export const PLAUD_PUBLIC_CLIENT_ID = "client_f9e0b214-c11f-434b-8b95-c4497d1feb81";
7
+ export const PLAUD_REDIRECT_URI = "http://localhost:8199/auth/callback";
8
+ /** Third-party personal-recording API, NOT Plaud Embedded's partner-token API. */
9
+ export const PLAUD_OAUTH = {
10
+ id: "plaud.oauth2",
11
+ provider: "plaud",
12
+ authorization: { url: "https://web.plaud.ai/platform/oauth" },
13
+ exchange: {
14
+ url: `${BASE}/oauth/third-party/access-token`,
15
+ clientAuthentication: "client_id_basic",
16
+ grantType: null,
17
+ sendState: true,
18
+ requirePkce: true,
19
+ },
20
+ refresh: {
21
+ url: `${BASE}/oauth/third-party/access-token/refresh`,
22
+ clientAuthentication: "none",
23
+ grantType: null,
24
+ },
25
+ };
26
+ export const PLAUD_CREDENTIAL = {
27
+ id: "plaud",
28
+ methods: {
29
+ oauth2: {
30
+ schema: t.Object({ grant: t.Optional(OAuthGrantSchema) }, { additionalProperties: false }),
31
+ authentication: {
32
+ kind: "oauth2",
33
+ grantField: "grant",
34
+ definition: PLAUD_OAUTH,
35
+ renewal: "refresh",
36
+ },
37
+ targets: {
38
+ api: { baseUrl: `${BASE}/open/third-party/`, methods: ["GET"] },
39
+ },
40
+ probe: {
41
+ target: "api",
42
+ path: "users/current",
43
+ method: "GET",
44
+ acceptedStatuses: [200],
45
+ },
46
+ },
47
+ },
48
+ };
49
+ export function plaudRuntime(ctx) {
50
+ return credentialRuntime(ctx, PLAUD_CREDENTIAL, "oauth2", (config) => [
51
+ config.clientId,
52
+ ]);
53
+ }
54
+ function object(value) {
55
+ if (!value || typeof value !== "object" || Array.isArray(value))
56
+ throw new AuthError("invalid_response");
57
+ return value;
58
+ }
59
+ export async function request(ctx, path) {
60
+ const { binding, transport } = plaudRuntime(ctx);
61
+ const response = await transport.request(binding, { target: "api", path });
62
+ if (!response.ok)
63
+ throw new Error(`plaud: request failed (HTTP ${response.status})`);
64
+ try {
65
+ return object(await response.json());
66
+ }
67
+ catch {
68
+ throw new AuthError("invalid_response");
69
+ }
70
+ }
71
+ export async function recording(ctx, id) {
72
+ // Encoding protects separators/query injection; the transport also rejects traversal.
73
+ return request(ctx, `files/${encodeURIComponent(id)}`);
74
+ }
75
+ export async function list(ctx, page, pageSize) {
76
+ const result = await request(ctx, `files/?${new URLSearchParams({ page: String(page), page_size: String(pageSize) })}`);
77
+ if (!Array.isArray(result.data) || result.data.length > pageSize)
78
+ throw new AuthError("invalid_response");
79
+ const data = result.data.map(object);
80
+ return { ...result, data };
81
+ }
82
+ export function blocks(file, field) {
83
+ const value = file[field];
84
+ if (value === undefined || value === null)
85
+ return [];
86
+ if (!Array.isArray(value))
87
+ throw new AuthError("invalid_response");
88
+ return value.map(object);
89
+ }
90
+ /** Linked content is a separate, unauthenticated capability approved by host config. */
91
+ export async function blockContent(ctx, block) {
92
+ if (!block)
93
+ return { status: "unavailable", content: null };
94
+ if (typeof block.data_content === "string" && block.data_content.length)
95
+ return { status: "available", content: block.data_content };
96
+ const link = block.data_link;
97
+ if (typeof link !== "string" || !link)
98
+ return { status: "unavailable", content: null };
99
+ const origins = ctx.connection.config.contentOrigins ?? [];
100
+ if (!Array.isArray(origins) ||
101
+ origins.some((origin) => {
102
+ if (typeof origin !== "string")
103
+ return true;
104
+ try {
105
+ const url = new URL(origin);
106
+ return url.protocol !== "https:" || url.origin !== origin;
107
+ }
108
+ catch {
109
+ return true;
110
+ }
111
+ }))
112
+ throw new AuthError("invalid_credentials");
113
+ let origin;
114
+ try {
115
+ const url = new URL(link);
116
+ if (url.protocol !== "https:" || url.username || url.password || url.hash)
117
+ throw new Error();
118
+ origin = url.origin;
119
+ }
120
+ catch {
121
+ throw new AuthError("request_not_allowed");
122
+ }
123
+ if (!origins.includes(origin))
124
+ return {
125
+ status: "link_requires_approval",
126
+ content: null,
127
+ contentUrl: link,
128
+ };
129
+ const response = await downloadResource(link, {
130
+ allowedOrigins: [...origins],
131
+ fetch: globalThis.fetch,
132
+ maxResponseBytes: 8 * 1024 * 1024,
133
+ });
134
+ if (!response.ok)
135
+ throw new Error(`plaud: content download failed (HTTP ${response.status})`);
136
+ const content = await response.text();
137
+ return {
138
+ status: content ? "available" : "unavailable",
139
+ content: content || null,
140
+ };
141
+ }
142
+ function utcDay(value) {
143
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
144
+ return undefined;
145
+ const ms = Date.parse(`${value}T00:00:00.000Z`);
146
+ return Number.isFinite(ms) &&
147
+ new Date(ms).toISOString().slice(0, 10) === value
148
+ ? ms
149
+ : undefined;
150
+ }
151
+ /** Timezone-less timestamps are UTC; invalid calendar days/times are not normalized. */
152
+ function timestamp(value) {
153
+ if (typeof value !== "string")
154
+ return undefined;
155
+ const match = /^(\d{4}-\d{2}-\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/i.exec(value.trim());
156
+ if (!match)
157
+ return undefined;
158
+ const [, day, hour, minute, second = "00", fraction = "", zone = "Z"] = match;
159
+ const midnight = utcDay(day);
160
+ if (midnight === undefined || hour === undefined)
161
+ return midnight;
162
+ if (Number(hour) > 23 || Number(minute) > 59 || Number(second) > 59)
163
+ return undefined;
164
+ const result = Date.parse(`${day}T${hour}:${minute}:${second}${fraction}${zone}`);
165
+ return Number.isFinite(result) ? result : undefined;
166
+ }
167
+ export function dateBoundary(value, end) {
168
+ if (value === undefined)
169
+ return undefined;
170
+ const ms = utcDay(value);
171
+ if (ms === undefined)
172
+ throw new Error("plaud: invalid date; use YYYY-MM-DD");
173
+ return ms + (end ? 86_400_000 - 1 : 0);
174
+ }
175
+ export async function scan(ctx, options) {
176
+ if (options.from !== undefined &&
177
+ options.to !== undefined &&
178
+ options.from > options.to)
179
+ throw new Error("plaud: from must not be after to");
180
+ const recordings = [];
181
+ const seen = new Set();
182
+ const query = options.query?.toLowerCase();
183
+ let scanned = 0;
184
+ let pagesScanned = 0;
185
+ for (let page = 1; page <= options.maxPages; page++) {
186
+ const result = await list(ctx, page, 100);
187
+ pagesScanned++;
188
+ for (let index = 0; index < result.data.length; index++) {
189
+ const file = result.data[index];
190
+ scanned++;
191
+ if (typeof file.id !== "string" || !file.id)
192
+ throw new AuthError("invalid_response");
193
+ if (seen.has(file.id))
194
+ continue;
195
+ seen.add(file.id);
196
+ if (query &&
197
+ !(typeof file.name === "string" &&
198
+ file.name.toLowerCase().includes(query)))
199
+ continue;
200
+ if (options.from !== undefined || options.to !== undefined) {
201
+ const created = timestamp(file.created_at);
202
+ if (created === undefined ||
203
+ (options.from !== undefined && created < options.from) ||
204
+ (options.to !== undefined && created > options.to))
205
+ continue;
206
+ }
207
+ recordings.push(file);
208
+ if (recordings.length >= options.limit)
209
+ return {
210
+ recordings,
211
+ scanned,
212
+ pagesScanned,
213
+ truncated: index < result.data.length - 1 || result.data.length === 100,
214
+ };
215
+ }
216
+ if (result.data.length < 100)
217
+ return { recordings, scanned, pagesScanned, truncated: false };
218
+ }
219
+ // Do not claim exhaustive results or assume provider ordering beyond the scanned pages.
220
+ return { recordings, scanned, pagesScanned, truncated: true };
221
+ }
@@ -1,3 +1,4 @@
1
+ import { requestOAuth2Token } from "runline";
1
2
  export const DEFAULT_API_VERSION = "v59.0";
2
3
  export function config(ctx) {
3
4
  return ctx.connection.config;
@@ -37,40 +38,24 @@ export async function getSession(ctx) {
37
38
  const loginUrl = validateInstanceUrl(requireString(c.loginUrl ?? c.instanceUrl, "loginUrl or instanceUrl"));
38
39
  const clientId = requireString(c.clientId, "clientId");
39
40
  const clientSecret = requireString(c.clientSecret, "clientSecret");
40
- const body = new URLSearchParams();
41
- body.set("grant_type", "client_credentials");
42
- body.set("client_id", clientId);
43
- body.set("client_secret", clientSecret);
44
- const res = await fetch(`${loginUrl}/services/oauth2/token`, {
45
- method: "POST",
46
- headers: {
47
- Accept: "application/json",
48
- "Content-Type": "application/x-www-form-urlencoded",
41
+ const tokens = await requestOAuth2Token({
42
+ url: `${loginUrl}/services/oauth2/token`,
43
+ clientAuthentication: "client_secret_post",
44
+ response: {
45
+ metadata: {
46
+ instanceUrl: "instance_url",
47
+ id: "id",
48
+ issuedAt: "issued_at",
49
+ },
49
50
  },
50
- body,
51
- });
52
- const text = await res.text();
53
- let data;
54
- try {
55
- data = text ? JSON.parse(text) : {};
56
- }
57
- catch {
58
- data = { raw: text };
59
- }
60
- if (!res.ok) {
61
- const err = typeof data.error === "string" ? data.error : res.status;
62
- const description = typeof data.error_description === "string"
63
- ? `: ${data.error_description}`
64
- : "";
65
- throw new Error(`Salesforce token error ${err}${description}`);
66
- }
51
+ }, { grant_type: "client_credentials" }, { clientId, clientSecret });
67
52
  return {
68
- instanceUrl: validateInstanceUrl(requireString(data.instance_url, "token response instance_url")),
69
- accessToken: requireString(data.access_token, "token response access_token"),
70
- tokenType: typeof data.token_type === "string" ? data.token_type : "Bearer",
71
- scope: typeof data.scope === "string" ? data.scope : undefined,
72
- id: typeof data.id === "string" ? data.id : undefined,
73
- issuedAt: typeof data.issued_at === "string" ? data.issued_at : undefined,
53
+ instanceUrl: validateInstanceUrl(requireString(tokens.metadata?.instanceUrl, "token response instance_url")),
54
+ accessToken: tokens.accessToken,
55
+ tokenType: tokens.tokenType ?? "Bearer",
56
+ scope: tokens.scope,
57
+ id: tokens.metadata?.id,
58
+ issuedAt: tokens.metadata?.issuedAt,
74
59
  };
75
60
  }
76
61
  export async function api(ctx, method, endpoint, body, qs, session = getSession(ctx)) {
package/dist/sdk.d.ts CHANGED
@@ -1,9 +1,13 @@
1
- import { type ActionInvocation, type ExecuteResult } from "./core/engine.js";
1
+ import type { ConnectionProvider } from "./connections/types.js";
2
+ import { type EngineHooks, type ExecuteResult } from "./core/engine.js";
2
3
  import type { PluginFunction } from "./plugin/api.js";
3
4
  import type { ActionAccess, ConnectionConfig, InputSchema, PluginDef } from "./plugin/types.js";
4
5
  export interface RunlineOptions {
5
6
  plugins?: Array<PluginDef | PluginFunction>;
7
+ /** Initial process-local connections. No environment lookup or disk writes. */
6
8
  connections?: ConnectionConfig[];
9
+ /** Host-owned credentials; mutually exclusive with initial connections. */
10
+ connectionProvider?: ConnectionProvider;
7
11
  timeoutMs?: number;
8
12
  memoryLimitBytes?: number;
9
13
  /**
@@ -19,7 +23,7 @@ export interface RunlineOptions {
19
23
  * never breaks a run. Not part of `RunlineConfig` on purpose —
20
24
  * config round-trips through JSON (`fromProject`), functions do not.
21
25
  */
22
- onAction?: (info: ActionInvocation) => void;
26
+ onAction?: EngineHooks["onAction"];
23
27
  }
24
28
  export interface RunlineExecuteOptions {
25
29
  timeoutMs?: number;
@@ -30,10 +34,11 @@ export interface RunlineExecuteOptions {
30
34
  export declare class Runline {
31
35
  private _registry;
32
36
  private _config;
37
+ private readonly _connectionProvider;
38
+ private readonly _memoryConnections?;
33
39
  /**
34
40
  * One engine per Runline instance, so its pooled worker survives across
35
- * `execute()` calls. Constructing an engine per call would spawn (and
36
- * leak) a fresh worker every time — the exact bug pooling exists to fix.
41
+ * `execute()` calls and avoids per-execution thread allocation.
37
42
  */
38
43
  private _engine;
39
44
  private readonly _onAction;
@@ -65,7 +70,10 @@ export declare class Runline {
65
70
  actions: string[];
66
71
  connectionConfigSchema?: PluginDef["connectionConfigSchema"];
67
72
  }>;
68
- /** Return all connections currently configured. */
73
+ /**
74
+ * Snapshot of process-local connections. Host-managed providers are not
75
+ * enumerated: their accounts can be caller-scoped and must be listed by the host.
76
+ */
69
77
  connections(): ConnectionConfig[];
70
78
  /**
71
79
  * Load runline from a project directory.
package/dist/sdk.js CHANGED
@@ -1,6 +1,9 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { loadConfigFrom } from "./config/loader.js";
3
4
  import { DEFAULT_CONFIG } from "./config/types.js";
5
+ import { FileConnectionProvider } from "./connections/file.js";
6
+ import { MemoryConnectionProvider } from "./connections/memory.js";
4
7
  import { ExecutionEngine, } from "./core/engine.js";
5
8
  import { resolvePluginExport } from "./plugin/api.js";
6
9
  import { discoverPlugins } from "./plugin/loader.js";
@@ -8,22 +11,33 @@ import { PluginRegistry } from "./plugin/registry.js";
8
11
  export class Runline {
9
12
  _registry;
10
13
  _config;
14
+ _connectionProvider;
15
+ _memoryConnections;
11
16
  /**
12
17
  * One engine per Runline instance, so its pooled worker survives across
13
- * `execute()` calls. Constructing an engine per call would spawn (and
14
- * leak) a fresh worker every time — the exact bug pooling exists to fix.
18
+ * `execute()` calls and avoids per-execution thread allocation.
15
19
  */
16
20
  _engine = null;
17
21
  _onAction;
18
22
  constructor(options) {
19
23
  this._registry = new PluginRegistry();
20
24
  this._onAction = options.onAction;
25
+ if (options.connectionProvider && options.connections) {
26
+ throw new Error("Pass connections or connectionProvider, not both");
27
+ }
28
+ if (options.connectionProvider) {
29
+ this._connectionProvider = options.connectionProvider;
30
+ }
31
+ else {
32
+ this._memoryConnections = new MemoryConnectionProvider(options.connections);
33
+ this._connectionProvider = this._memoryConnections;
34
+ }
21
35
  for (const pluginOrFn of options.plugins ?? []) {
22
36
  const plugin = resolvePluginExport(pluginOrFn, "unknown");
23
37
  this._registry.register(plugin);
24
38
  }
25
39
  this._config = {
26
- connections: options.connections ?? [],
40
+ connections: [],
27
41
  timeoutMs: options.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
28
42
  memoryLimitBytes: options.memoryLimitBytes ?? DEFAULT_CONFIG.memoryLimitBytes,
29
43
  ...(options.maxRunsPerWorker !== undefined
@@ -35,6 +49,7 @@ export class Runline {
35
49
  if (!this._engine) {
36
50
  this._engine = new ExecutionEngine(this._registry, this._config, {
37
51
  onAction: this._onAction,
52
+ connectionProvider: this._connectionProvider,
38
53
  });
39
54
  }
40
55
  return this._engine;
@@ -58,15 +73,14 @@ export class Runline {
58
73
  /** Register an additional plugin after creation. */
59
74
  addPlugin(pluginOrFn, connections) {
60
75
  const plugin = resolvePluginExport(pluginOrFn, "unknown");
61
- this._registry.register(plugin);
62
76
  if (connections) {
63
- this._config = {
64
- ...this._config,
65
- connections: [...this._config.connections, ...connections],
66
- };
77
+ if (!this._memoryConnections) {
78
+ throw new Error("Configure connections through the host connection provider");
79
+ }
80
+ this._memoryConnections.add(connections);
67
81
  }
68
- // The pooled worker baked in the old plugin surface and the engine holds
69
- // the old config object: both are stale now.
82
+ this._registry.register(plugin);
83
+ // Rebuild the worker's action surface; connection custody survives disposal.
70
84
  this.dispose();
71
85
  }
72
86
  /** List all available actions across all plugins. */
@@ -88,9 +102,12 @@ export class Runline {
88
102
  connectionConfigSchema: p.connectionConfigSchema,
89
103
  }));
90
104
  }
91
- /** Return all connections currently configured. */
105
+ /**
106
+ * Snapshot of process-local connections. Host-managed providers are not
107
+ * enumerated: their accounts can be caller-scoped and must be listed by the host.
108
+ */
92
109
  connections() {
93
- return [...this._config.connections];
110
+ return this._memoryConnections?.list() ?? [];
94
111
  }
95
112
  /**
96
113
  * Load runline from a project directory.
@@ -118,16 +135,17 @@ export class Runline {
118
135
  const configDir = findRunlineDir(dir);
119
136
  if (!configDir)
120
137
  return null;
121
- const config = loadConfigFrom(configDir);
138
+ const config = loadConfigFrom(join(configDir, "config.json"));
122
139
  const builtinAllowlist = new Set(config.connections.map((c) => c.plugin));
123
140
  const plugins = await discoverPlugins(configDir, {
124
141
  builtinAllowlist,
125
142
  builtinDir: options.builtinDir,
126
143
  });
127
144
  const rl = new Runline({
128
- connections: config.connections,
145
+ connectionProvider: new FileConnectionProvider(join(configDir, "config.json")),
129
146
  timeoutMs: config.timeoutMs,
130
147
  memoryLimitBytes: config.memoryLimitBytes,
148
+ maxRunsPerWorker: config.maxRunsPerWorker,
131
149
  });
132
150
  for (const plugin of plugins) {
133
151
  rl._registry.register(plugin);
@@ -136,7 +154,7 @@ export class Runline {
136
154
  }
137
155
  }
138
156
  function findRunlineDir(from) {
139
- let dir = from;
157
+ let dir = resolve(from);
140
158
  while (true) {
141
159
  if (existsSync(join(dir, ".runline")))
142
160
  return join(dir, ".runline");
@@ -147,15 +165,3 @@ function findRunlineDir(from) {
147
165
  }
148
166
  return null;
149
167
  }
150
- function loadConfigFrom(configDir) {
151
- const configPath = join(configDir, "config.json");
152
- if (!existsSync(configPath))
153
- return { ...DEFAULT_CONFIG };
154
- try {
155
- const raw = JSON.parse(readFileSync(configPath, "utf-8"));
156
- return { ...DEFAULT_CONFIG, ...raw };
157
- }
158
- catch {
159
- return { ...DEFAULT_CONFIG };
160
- }
161
- }
@@ -0,0 +1,2 @@
1
+ /** Observers cannot fail an operation, whether they throw or reject asynchronously. */
2
+ export declare function notifyObserver<T>(observer: ((event: T) => void | Promise<void>) | undefined, event: T): void;
@@ -0,0 +1,11 @@
1
+ /** Observers cannot fail an operation, whether they throw or reject asynchronously. */
2
+ export function notifyObserver(observer, event) {
3
+ if (!observer)
4
+ return;
5
+ try {
6
+ void Promise.resolve(observer(event)).catch(() => { });
7
+ }
8
+ catch {
9
+ // Operational success does not depend on observability.
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runline",
3
- "version": "0.28.1",
3
+ "version": "0.30.0",
4
4
  "description": "Code mode for agents \u2014 turn any API or command into a callable action",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",