orchajs 0.20.3 → 0.22.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 (107) hide show
  1. package/README.md +103 -0
  2. package/dist/amarsia/analytics.d.ts +21 -0
  3. package/dist/amarsia/analytics.js +457 -0
  4. package/dist/amarsia/endpoint.d.ts +1 -0
  5. package/dist/amarsia/endpoint.js +8 -0
  6. package/dist/amarsia/sync.js +1 -4
  7. package/dist/bin.d.ts +2 -0
  8. package/dist/bin.js +13 -0
  9. package/dist/browser.d.ts +2 -0
  10. package/dist/browser.js +12 -0
  11. package/dist/cli.js +35 -9
  12. package/dist/compiler/build-project.d.ts +1 -0
  13. package/dist/compiler/build-project.js +75 -8
  14. package/dist/compiler/compile.d.ts +1 -1
  15. package/dist/compiler/compile.js +11 -9
  16. package/dist/errors.d.ts +5 -0
  17. package/dist/errors.js +102 -0
  18. package/dist/file-content.js +42 -6
  19. package/dist/index.d.ts +1 -5
  20. package/dist/index.js +1 -2
  21. package/dist/integrations/esbuild.js +5 -2
  22. package/dist/integrations/expo.d.ts +1 -0
  23. package/dist/integrations/expo.js +1 -0
  24. package/dist/integrations/metro.d.ts +15 -0
  25. package/dist/integrations/metro.js +25 -0
  26. package/dist/integrations/next.d.ts +23 -0
  27. package/dist/integrations/next.js +53 -0
  28. package/dist/integrations/runtime-build.d.ts +4 -0
  29. package/dist/integrations/runtime-build.js +32 -0
  30. package/dist/integrations/vite.d.ts +23 -0
  31. package/dist/integrations/vite.js +32 -0
  32. package/dist/integrations/webpack.d.ts +24 -0
  33. package/dist/integrations/webpack.js +21 -0
  34. package/dist/orcha.js +17 -8
  35. package/dist/playground/server.js +57 -7
  36. package/dist/project-loader.js +3 -0
  37. package/dist/providers/browser.d.ts +2 -0
  38. package/dist/providers/browser.js +20 -0
  39. package/dist/react-native.d.ts +2 -0
  40. package/dist/react-native.js +12 -0
  41. package/dist/runtime/actions/browser-native.d.ts +3 -0
  42. package/dist/runtime/actions/browser-native.js +28 -0
  43. package/dist/runtime/actions/execute.d.ts +7 -0
  44. package/dist/runtime/actions/execute.js +9 -2
  45. package/dist/runtime/agent.d.ts +1 -0
  46. package/dist/runtime/agent.js +38 -10
  47. package/dist/runtime/compiler-hook.d.ts +5 -0
  48. package/dist/runtime/compiler-hook.js +8 -0
  49. package/dist/runtime/create-orcha.d.ts +6 -1
  50. package/dist/runtime/create-orcha.js +35 -21
  51. package/dist/runtime/platform-browser.d.ts +2 -0
  52. package/dist/runtime/platform-browser.js +15 -0
  53. package/dist/runtime/platform-node.d.ts +2 -0
  54. package/dist/runtime/platform-node.js +31 -0
  55. package/dist/runtime/platform-react-native.d.ts +2 -0
  56. package/dist/runtime/platform-react-native.js +15 -0
  57. package/dist/runtime/platform.d.ts +15 -0
  58. package/dist/runtime/platform.js +1 -0
  59. package/dist/runtime/random-id.d.ts +1 -0
  60. package/dist/runtime/random-id.js +26 -0
  61. package/dist/runtime-entry.d.ts +5 -0
  62. package/dist/runtime-entry.js +2 -0
  63. package/dist/storage/browser-indexeddb.d.ts +7 -16
  64. package/dist/storage/browser-indexeddb.js +102 -19
  65. package/dist/storage/react-native-jsonl.d.ts +7 -16
  66. package/dist/storage/react-native-jsonl.js +65 -19
  67. package/dist/types.d.ts +54 -1
  68. package/dist-cjs/amarsia/analytics.js +447 -0
  69. package/dist-cjs/amarsia/endpoint.js +32 -0
  70. package/dist-cjs/amarsia/sync.js +2 -5
  71. package/dist-cjs/browser.js +48 -0
  72. package/dist-cjs/compiler/build-project.js +97 -8
  73. package/dist-cjs/compiler/compile.js +17 -9
  74. package/dist-cjs/errors.js +92 -2
  75. package/dist-cjs/file-content.js +42 -7
  76. package/dist-cjs/index.js +4 -9
  77. package/dist-cjs/integrations/esbuild.js +6 -2
  78. package/dist-cjs/integrations/expo.js +29 -0
  79. package/dist-cjs/integrations/metro.js +51 -0
  80. package/dist-cjs/integrations/next.js +75 -0
  81. package/dist-cjs/integrations/runtime-build.js +70 -0
  82. package/dist-cjs/integrations/vite.js +57 -0
  83. package/dist-cjs/integrations/webpack.js +51 -0
  84. package/dist-cjs/orcha.js +17 -12
  85. package/dist-cjs/playground/server.js +62 -9
  86. package/dist-cjs/project-loader.js +10 -0
  87. package/dist-cjs/providers/browser.js +61 -0
  88. package/dist-cjs/react-native.js +48 -0
  89. package/dist-cjs/runtime/actions/browser-native.js +68 -0
  90. package/dist-cjs/runtime/actions/execute.js +12 -2
  91. package/dist-cjs/runtime/agent.js +37 -14
  92. package/dist-cjs/runtime/compiler-hook.js +37 -0
  93. package/dist-cjs/runtime/create-orcha.js +34 -23
  94. package/dist-cjs/runtime/platform-browser.js +48 -0
  95. package/dist-cjs/runtime/platform-node.js +59 -0
  96. package/dist-cjs/runtime/platform-react-native.js +45 -0
  97. package/dist-cjs/runtime/platform.js +17 -0
  98. package/dist-cjs/runtime/random-id.js +55 -0
  99. package/dist-cjs/runtime-entry.js +34 -0
  100. package/dist-cjs/storage/browser-indexeddb.js +102 -5
  101. package/dist-cjs/storage/react-native-jsonl.js +73 -5
  102. package/dist-cjs/types/react-native-async-storage.d.js +2 -0
  103. package/package.json +69 -9
  104. package/dist/playground/public/app.css +0 -1
  105. package/dist/playground/public/app.js +0 -31
  106. package/dist/playground/public/index.html +0 -15
  107. package/dist/playground/public/orcha-logo.svg +0 -4
@@ -0,0 +1,8 @@
1
+ let registryCompiler;
2
+ /** @internal Installed by Node development tooling, never by runtime bundles. */
3
+ export function setRegistryCompiler(compiler) {
4
+ registryCompiler = compiler;
5
+ }
6
+ export function getRegistryCompiler() {
7
+ return registryCompiler;
8
+ }
@@ -1,11 +1,16 @@
1
1
  import type { ProviderResponseGenerator } from "../providers/types.js";
2
+ import type { RuntimePlatform } from "./platform.js";
2
3
  import type { AgentRuntime, CompiledBundle, OrchaInitConfiguration } from "../types.js";
3
4
  export interface Orcha {
4
5
  init(configuration: OrchaInitConfiguration): OrchaClient;
5
6
  isInitialized(): boolean;
7
+ /** Flushes queued Amarsia telemetry without blocking normal agent runs. */
8
+ flushTelemetry(options?: {
9
+ timeoutMs?: number;
10
+ }): Promise<void>;
6
11
  /** @internal Used by the development compiler. */
7
12
  getCompiledBundle(): CompiledBundle;
8
13
  }
9
14
  export type OrchaClient = Orcha & Record<string, AgentRuntime>;
10
15
  export type BundleResolver = (configuration: OrchaInitConfiguration) => CompiledBundle;
11
- export declare function createOrcha(resolveBundle: BundleResolver, generateProviderResponse: ProviderResponseGenerator): OrchaClient;
16
+ export declare function createOrcha(resolveBundle: BundleResolver, generateProviderResponse: ProviderResponseGenerator, platform: RuntimePlatform): OrchaClient;
@@ -1,32 +1,33 @@
1
- import { resolve } from "node:path";
2
1
  import { RuntimeAgent } from "./agent.js";
3
- import { createSessionStore } from "../storage/create-store.js";
4
- import { syncAmarsiaProject } from "../amarsia/sync.js";
2
+ import { AmarsiaTelemetryClient, withAmarsiaTelemetry, } from "../amarsia/analytics.js";
5
3
  import { setOrchaRuntimeContext } from "./context.js";
6
4
  class OrchaRuntimeCore {
7
5
  #agents = new Map();
8
6
  #bundle;
9
7
  #client;
8
+ #telemetry;
10
9
  #activeSessions = new Set();
11
10
  #sessionControls = new Map();
12
11
  #resolveBundle;
13
12
  #generateProviderResponse;
14
- constructor(resolveBundle, generateProviderResponse) {
13
+ #platform;
14
+ constructor(resolveBundle, generateProviderResponse, platform) {
15
15
  this.#resolveBundle = resolveBundle;
16
16
  this.#generateProviderResponse = generateProviderResponse;
17
+ this.#platform = platform;
17
18
  }
18
19
  attachClient(client) {
19
20
  this.#client = client;
20
21
  }
21
22
  init(configuration) {
22
- const projectRoot = resolve(configuration.root ??
23
- process.env.ORCHA_PROJECT_ROOT ??
24
- process.cwd());
25
- const bundle = this.#resolveBundle(configuration);
26
- if (configuration.amarsia?.key) {
27
- syncAmarsiaProject({
28
- key: configuration.amarsia.key,
29
- endpoint: configuration.amarsia.endpoint,
23
+ if (this.#telemetry) {
24
+ void this.#telemetry.close();
25
+ }
26
+ const projectRoot = this.#platform.projectRoot(configuration);
27
+ const bundle = configuration.bundle ?? this.#resolveBundle(configuration);
28
+ if (configuration.amarsia?.key && this.#platform.syncProject) {
29
+ this.#platform.syncProject({
30
+ configuration,
30
31
  bundle,
31
32
  projectRoot,
32
33
  });
@@ -46,13 +47,21 @@ class OrchaRuntimeCore {
46
47
  runtimeConfiguration.actions?.runtime !== bundle.actionRuntime) {
47
48
  throw new Error(`Production bundle expects local action runtime "${bundle.actionRuntime}".`);
48
49
  }
49
- const storageDirectory = configuration.storage?.directory ?? ".orcha/sessions";
50
- const createStore = (manifest) => createSessionStore({
51
- strategy: configuration.storage?.strategy,
52
- directory: storageDirectory,
50
+ const createStore = (manifest) => this.#platform.createSessionStore({
51
+ configuration,
53
52
  projectRoot,
54
- agentName: manifest.key ?? manifest.name,
53
+ manifest,
55
54
  });
55
+ const telemetry = configuration.amarsia?.key
56
+ ? new AmarsiaTelemetryClient(configuration.amarsia)
57
+ : undefined;
58
+ this.#telemetry = telemetry;
59
+ const createRuntimeStore = (manifest) => {
60
+ const store = createStore(manifest);
61
+ return telemetry
62
+ ? withAmarsiaTelemetry({ store, manifest, client: telemetry })
63
+ : store;
64
+ };
56
65
  const agents = new Map();
57
66
  for (const [name, manifest] of Object.entries(bundle.agents)) {
58
67
  const subagents = Object.fromEntries(Object.entries(manifest.subagents ?? {}).map(([subagentName, subagentManifest]) => [
@@ -60,23 +69,25 @@ class OrchaRuntimeCore {
60
69
  new RuntimeAgent({
61
70
  manifest: subagentManifest,
62
71
  configuration: runtimeConfiguration,
63
- store: createStore(subagentManifest),
72
+ store: createRuntimeStore(subagentManifest),
64
73
  activeSessions: this.#activeSessions,
65
74
  sessionControls: this.#sessionControls,
66
75
  generateProviderResponse: this.#generateProviderResponse,
67
76
  delegatedOnly: true,
68
77
  projectRoot,
78
+ randomUUID: this.#platform.randomUUID,
69
79
  }),
70
80
  ]));
71
81
  agents.set(name, new RuntimeAgent({
72
82
  manifest,
73
83
  configuration: runtimeConfiguration,
74
- store: createStore(manifest),
84
+ store: createRuntimeStore(manifest),
75
85
  activeSessions: this.#activeSessions,
76
86
  sessionControls: this.#sessionControls,
77
87
  generateProviderResponse: this.#generateProviderResponse,
78
88
  subagents,
79
89
  projectRoot,
90
+ randomUUID: this.#platform.randomUUID,
80
91
  }));
81
92
  }
82
93
  this.#bundle = bundle;
@@ -93,6 +104,9 @@ class OrchaRuntimeCore {
93
104
  isInitialized() {
94
105
  return this.#bundle !== undefined;
95
106
  }
107
+ async flushTelemetry(options) {
108
+ await this.#telemetry?.drain(options);
109
+ }
96
110
  getCompiledBundle() {
97
111
  if (!this.#bundle) {
98
112
  throw new Error("Orcha has not been initialized.");
@@ -103,8 +117,8 @@ class OrchaRuntimeCore {
103
117
  return this.#agents.get(name);
104
118
  }
105
119
  }
106
- export function createOrcha(resolveBundle, generateProviderResponse) {
107
- const core = new OrchaRuntimeCore(resolveBundle, generateProviderResponse);
120
+ export function createOrcha(resolveBundle, generateProviderResponse, platform) {
121
+ const core = new OrchaRuntimeCore(resolveBundle, generateProviderResponse, platform);
108
122
  const client = new Proxy(core, {
109
123
  get(target, property) {
110
124
  if (typeof property === "string") {
@@ -0,0 +1,2 @@
1
+ import type { RuntimePlatform } from "./platform.js";
2
+ export declare const browserRuntimePlatform: RuntimePlatform;
@@ -0,0 +1,15 @@
1
+ import { BrowserIndexedDbSessionStore } from "../storage/browser-indexeddb.js";
2
+ import { portableRandomUUID } from "./random-id.js";
3
+ export const browserRuntimePlatform = {
4
+ randomUUID: portableRandomUUID,
5
+ projectRoot(configuration) {
6
+ return configuration.root ?? "/";
7
+ },
8
+ createSessionStore({ configuration, manifest }) {
9
+ const strategy = configuration.storage?.strategy ?? "indexeddb";
10
+ if (strategy !== "indexeddb") {
11
+ throw new Error(`Browser runtimes require storage strategy "indexeddb", received "${strategy}".`);
12
+ }
13
+ return new BrowserIndexedDbSessionStore(manifest.key ?? manifest.name, configuration.storage?.directory ?? "orchajs");
14
+ },
15
+ };
@@ -0,0 +1,2 @@
1
+ import type { RuntimePlatform } from "./platform.js";
2
+ export declare const nodeRuntimePlatform: RuntimePlatform;
@@ -0,0 +1,31 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { syncAmarsiaProject } from "../amarsia/sync.js";
4
+ import { createSessionStore } from "../storage/create-store.js";
5
+ export const nodeRuntimePlatform = {
6
+ randomUUID,
7
+ projectRoot(configuration) {
8
+ return resolve(configuration.root ??
9
+ process.env.ORCHA_PROJECT_ROOT ??
10
+ process.cwd());
11
+ },
12
+ createSessionStore({ configuration, projectRoot, manifest }) {
13
+ return createSessionStore({
14
+ strategy: configuration.storage?.strategy,
15
+ directory: configuration.storage?.directory,
16
+ projectRoot,
17
+ agentName: manifest.key ?? manifest.name,
18
+ });
19
+ },
20
+ syncProject({ configuration, bundle, projectRoot }) {
21
+ if (!configuration.amarsia?.key) {
22
+ return;
23
+ }
24
+ syncAmarsiaProject({
25
+ key: configuration.amarsia.key,
26
+ endpoint: configuration.amarsia.endpoint,
27
+ bundle,
28
+ projectRoot,
29
+ });
30
+ },
31
+ };
@@ -0,0 +1,2 @@
1
+ import type { RuntimePlatform } from "./platform.js";
2
+ export declare const reactNativeRuntimePlatform: RuntimePlatform;
@@ -0,0 +1,15 @@
1
+ import { ReactNativeJsonlSessionStore } from "../storage/react-native-jsonl.js";
2
+ import { portableRandomUUID } from "./random-id.js";
3
+ export const reactNativeRuntimePlatform = {
4
+ randomUUID: portableRandomUUID,
5
+ projectRoot(configuration) {
6
+ return configuration.root ?? "/";
7
+ },
8
+ createSessionStore({ configuration, manifest }) {
9
+ const strategy = configuration.storage?.strategy ?? "react-native";
10
+ if (strategy !== "react-native") {
11
+ throw new Error(`React Native requires storage strategy "react-native", received "${strategy}".`);
12
+ }
13
+ return new ReactNativeJsonlSessionStore(manifest.key ?? manifest.name);
14
+ },
15
+ };
@@ -0,0 +1,15 @@
1
+ import type { CompiledAgentManifest, CompiledBundle, OrchaInitConfiguration, SessionStore } from "../types.js";
2
+ export interface RuntimePlatform {
3
+ randomUUID(): string;
4
+ projectRoot(configuration: OrchaInitConfiguration): string;
5
+ createSessionStore(options: {
6
+ configuration: OrchaInitConfiguration;
7
+ projectRoot: string;
8
+ manifest: CompiledAgentManifest;
9
+ }): SessionStore;
10
+ syncProject?(options: {
11
+ configuration: OrchaInitConfiguration;
12
+ bundle: CompiledBundle;
13
+ projectRoot: string;
14
+ }): void;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function portableRandomUUID(): string;
@@ -0,0 +1,26 @@
1
+ let fallbackSequence = 0;
2
+ export function portableRandomUUID() {
3
+ const crypto = globalThis.crypto;
4
+ if (typeof crypto?.randomUUID === "function") {
5
+ return crypto.randomUUID();
6
+ }
7
+ if (typeof crypto?.getRandomValues === "function") {
8
+ return formatUuid(crypto.getRandomValues(new Uint8Array(16)));
9
+ }
10
+ fallbackSequence = (fallbackSequence + 1) >>> 0;
11
+ const bytes = new Uint8Array(16);
12
+ let state = Date.now() ^
13
+ fallbackSequence ^
14
+ Math.floor(Math.random() * 0x1_0000_0000);
15
+ for (let index = 0; index < bytes.length; index += 1) {
16
+ state = (Math.imul(state, 1_664_525) + 1_013_904_223) >>> 0;
17
+ bytes[index] = state >>> 24;
18
+ }
19
+ return formatUuid(bytes);
20
+ }
21
+ function formatUuid(bytes) {
22
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
23
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
24
+ const value = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
25
+ return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;
26
+ }
@@ -0,0 +1,5 @@
1
+ export { OrchaError } from "./errors.js";
2
+ export type { Orcha, OrchaClient, } from "./runtime/create-orcha.js";
3
+ export { exportSessionJsonl, migrateSessionStore, } from "./storage/migrate.js";
4
+ export type { SessionStoreMigrationResult, } from "./storage/migrate.js";
5
+ export type { ActionConfiguration, AgentTestActionConfiguration, AgentTestActionExpectation, AgentTestActionResponse, AgentTestAssertionResult, AgentTestCaseConfiguration, AgentTestCaseReport, AgentTestCaseSummary, AgentTestExpectation, AgentTestReport, AgentTestTextExpectation, AgentTestValueExpectation, AgentTestWarning, ApiKeyProviderConfiguration, AwsCredentials, AgentContinueInput, AgentConfiguration, AgentContentInput, AgentInput, AgentRegistration, AgentRuntime, ClientCapability, ClientToolCall, CompletedRunResult, CompiledActionManifest, CompiledAgentManifest, CompiledBundle, CompiledEvaluationManifest, CompiledSkillManifest, EvaluationConfiguration, EvaluationMetricConfiguration, EvaluationMetricResult, EvaluationResult, Execution, ExecutionSnapshot, FailedRunResult, FileContent, FilePathContentInput, LocalFileContent, LocalActionRuntimeConfiguration, MessageContent, MessageContentInput, MetadataValue, OrchaErrorCode, OrchaInitConfiguration, PausedRunResult, PaginationOptions, ProjectConfiguration, ProviderConfiguration, ProviderInitConfigurations, ProviderName, BedrockProviderConfiguration, RunError, RunStatus, RunTestsOptions, ResumeInput, RunResult, SessionEvent, SessionEventListOptions, SessionEvents, SessionEventType, SessionHistory, SessionHistoryItem, SessionList, SessionListOptions, SessionMetadata, SessionSnapshot, SessionStatus, SessionStore, SkillConfiguration, SessionUpdate, StreamingRunSnapshot, SubagentConfiguration, TextContent, ToolResult, ToolResumeInput, UrlContentInput, Usage, VertexAIProviderConfiguration, GoogleServiceAccountCredentials, WaitingRunResult, WaitingForSubagentSnapshot, } from "./types.js";
@@ -0,0 +1,2 @@
1
+ export { OrchaError } from "./errors.js";
2
+ export { exportSessionJsonl, migrateSessionStore, } from "./storage/migrate.js";
@@ -1,20 +1,11 @@
1
1
  import type { SessionEvent, SessionStore } from "../types.js";
2
- /**
3
- * Future browser storage strategy.
4
- *
5
- * It will persist the same ordered SessionEvent records used by the JSONL
6
- * strategy in IndexedDB. Import and export will preserve the canonical JSONL
7
- * representation so a session can move between browser, Node, and remote
8
- * stores without changing the runtime or replay engine.
9
- *
10
- * Required before implementation:
11
- * - transactional sequence allocation;
12
- * - multi-tab writer coordination;
13
- * - quota and eviction handling;
14
- * - schema migrations and JSONL import/export.
15
- */
16
2
  export declare class BrowserIndexedDbSessionStore implements SessionStore {
17
- read(_sessionId: string): Promise<SessionEvent[]>;
18
- append(_sessionId: string, _events: SessionEvent[]): Promise<void>;
3
+ #private;
4
+ constructor(namespace: string, databaseName?: string);
5
+ read(sessionId: string): Promise<SessionEvent[]>;
6
+ append(sessionId: string, events: SessionEvent[]): Promise<void>;
19
7
  listSessionIds(): Promise<string[]>;
8
+ subscribe(sessionId: string, listener: (events: SessionEvent[]) => void): () => void;
9
+ subscribeAll(listener: (sessionId: string, events: SessionEvent[]) => void): () => void;
10
+ describeLocation(sessionId: string): Promise<string>;
20
11
  }
@@ -1,25 +1,108 @@
1
- /**
2
- * Future browser storage strategy.
3
- *
4
- * It will persist the same ordered SessionEvent records used by the JSONL
5
- * strategy in IndexedDB. Import and export will preserve the canonical JSONL
6
- * representation so a session can move between browser, Node, and remote
7
- * stores without changing the runtime or replay engine.
8
- *
9
- * Required before implementation:
10
- * - transactional sequence allocation;
11
- * - multi-tab writer coordination;
12
- * - quota and eviction handling;
13
- * - schema migrations and JSONL import/export.
14
- */
1
+ const DATABASE_VERSION = 1;
2
+ const EVENT_STORE = "events";
15
3
  export class BrowserIndexedDbSessionStore {
16
- async read(_sessionId) {
17
- throw new Error("Browser IndexedDB session storage is not implemented yet.");
4
+ #databaseName;
5
+ #namespace;
6
+ #listeners = new Map();
7
+ #allListeners = new Set();
8
+ constructor(namespace, databaseName = "orchajs") {
9
+ this.#namespace = namespace;
10
+ this.#databaseName = databaseName;
18
11
  }
19
- async append(_sessionId, _events) {
20
- throw new Error("Browser IndexedDB session storage is not implemented yet.");
12
+ async read(sessionId) {
13
+ const database = await this.#open();
14
+ const transaction = database.transaction(EVENT_STORE, "readonly");
15
+ const index = transaction.objectStore(EVENT_STORE).index("session");
16
+ const records = await request(index.getAll(IDBKeyRange.only([this.#namespace, sessionId])));
17
+ await transactionComplete(transaction);
18
+ return records
19
+ .sort((left, right) => left.sequence - right.sequence)
20
+ .map((record) => record.event);
21
+ }
22
+ async append(sessionId, events) {
23
+ if (events.length === 0) {
24
+ return;
25
+ }
26
+ const database = await this.#open();
27
+ const transaction = database.transaction(EVENT_STORE, "readwrite");
28
+ const store = transaction.objectStore(EVENT_STORE);
29
+ for (const event of events) {
30
+ store.add({
31
+ id: eventId(this.#namespace, sessionId, event.sequence),
32
+ namespace: this.#namespace,
33
+ sessionId,
34
+ sequence: event.sequence,
35
+ event,
36
+ });
37
+ }
38
+ await transactionComplete(transaction);
39
+ this.#listeners.get(sessionId)?.forEach((listener) => listener(events));
40
+ this.#allListeners.forEach((listener) => listener(sessionId, events));
21
41
  }
22
42
  async listSessionIds() {
23
- throw new Error("Browser IndexedDB session storage is not implemented yet.");
43
+ const database = await this.#open();
44
+ const transaction = database.transaction(EVENT_STORE, "readonly");
45
+ const index = transaction.objectStore(EVENT_STORE).index("namespace");
46
+ const records = await request(index.getAll(IDBKeyRange.only(this.#namespace)));
47
+ await transactionComplete(transaction);
48
+ return [...new Set(records.map((record) => record.sessionId))].sort();
49
+ }
50
+ subscribe(sessionId, listener) {
51
+ const listeners = this.#listeners.get(sessionId) ?? new Set();
52
+ listeners.add(listener);
53
+ this.#listeners.set(sessionId, listeners);
54
+ return () => {
55
+ listeners.delete(listener);
56
+ if (listeners.size === 0) {
57
+ this.#listeners.delete(sessionId);
58
+ }
59
+ };
60
+ }
61
+ subscribeAll(listener) {
62
+ this.#allListeners.add(listener);
63
+ return () => this.#allListeners.delete(listener);
64
+ }
65
+ async describeLocation(sessionId) {
66
+ return `indexeddb://${this.#databaseName}/${this.#namespace}/${sessionId}`;
24
67
  }
68
+ #open() {
69
+ if (typeof indexedDB === "undefined") {
70
+ throw new Error("IndexedDB is unavailable in this runtime.");
71
+ }
72
+ return new Promise((resolve, reject) => {
73
+ const openRequest = indexedDB.open(this.#databaseName, DATABASE_VERSION);
74
+ openRequest.onupgradeneeded = () => {
75
+ const database = openRequest.result;
76
+ if (database.objectStoreNames.contains(EVENT_STORE)) {
77
+ return;
78
+ }
79
+ const store = database.createObjectStore(EVENT_STORE, {
80
+ keyPath: "id",
81
+ });
82
+ store.createIndex("session", ["namespace", "sessionId"]);
83
+ store.createIndex("namespace", "namespace");
84
+ };
85
+ openRequest.onsuccess = () => resolve(openRequest.result);
86
+ openRequest.onerror = () => reject(openRequest.error);
87
+ openRequest.onblocked = () => reject(new Error("Opening the Orcha IndexedDB database was blocked."));
88
+ });
89
+ }
90
+ }
91
+ function eventId(namespace, sessionId, sequence) {
92
+ return `${namespace}\u0000${sessionId}\u0000${sequence
93
+ .toString()
94
+ .padStart(12, "0")}`;
95
+ }
96
+ function request(value) {
97
+ return new Promise((resolve, reject) => {
98
+ value.onsuccess = () => resolve(value.result);
99
+ value.onerror = () => reject(value.error);
100
+ });
101
+ }
102
+ function transactionComplete(transaction) {
103
+ return new Promise((resolve, reject) => {
104
+ transaction.oncomplete = () => resolve();
105
+ transaction.onerror = () => reject(transaction.error);
106
+ transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB transaction aborted."));
107
+ });
25
108
  }
@@ -1,20 +1,11 @@
1
1
  import type { SessionEvent, SessionStore } from "../types.js";
2
- /**
3
- * Future React Native storage strategy.
4
- *
5
- * It will store one JSONL event stream per session inside the application's
6
- * private documents directory. The implementation must not assume Node's
7
- * `fs` module: Expo FileSystem and bare React Native filesystem bindings need
8
- * a small host adapter while retaining the exact SessionStore contract.
9
- *
10
- * Required before implementation:
11
- * - Expo and bare React Native filesystem adapters;
12
- * - atomic append/recovery behavior on iOS and Android;
13
- * - app lifecycle interruption handling;
14
- * - JSONL import/export conformance tests.
15
- */
16
2
  export declare class ReactNativeJsonlSessionStore implements SessionStore {
17
- read(_sessionId: string): Promise<SessionEvent[]>;
18
- append(_sessionId: string, _events: SessionEvent[]): Promise<void>;
3
+ #private;
4
+ constructor(namespace: string);
5
+ read(sessionId: string): Promise<SessionEvent[]>;
6
+ append(sessionId: string, events: SessionEvent[]): Promise<void>;
19
7
  listSessionIds(): Promise<string[]>;
8
+ subscribe(sessionId: string, listener: (events: SessionEvent[]) => void): () => void;
9
+ subscribeAll(listener: (sessionId: string, events: SessionEvent[]) => void): () => void;
10
+ describeLocation(sessionId: string): Promise<string>;
20
11
  }
@@ -1,25 +1,71 @@
1
- /**
2
- * Future React Native storage strategy.
3
- *
4
- * It will store one JSONL event stream per session inside the application's
5
- * private documents directory. The implementation must not assume Node's
6
- * `fs` module: Expo FileSystem and bare React Native filesystem bindings need
7
- * a small host adapter while retaining the exact SessionStore contract.
8
- *
9
- * Required before implementation:
10
- * - Expo and bare React Native filesystem adapters;
11
- * - atomic append/recovery behavior on iOS and Android;
12
- * - app lifecycle interruption handling;
13
- * - JSONL import/export conformance tests.
14
- */
1
+ import AsyncStorage from "@react-native-async-storage/async-storage";
2
+ const writes = new Map();
15
3
  export class ReactNativeJsonlSessionStore {
16
- async read(_sessionId) {
17
- throw new Error("React Native JSONL session storage is not implemented yet.");
4
+ #namespace;
5
+ #listeners = new Map();
6
+ #allListeners = new Set();
7
+ constructor(namespace) {
8
+ this.#namespace = namespace;
18
9
  }
19
- async append(_sessionId, _events) {
20
- throw new Error("React Native JSONL session storage is not implemented yet.");
10
+ async read(sessionId) {
11
+ const contents = await AsyncStorage.getItem(this.#key(sessionId));
12
+ if (!contents) {
13
+ return [];
14
+ }
15
+ return contents
16
+ .split("\n")
17
+ .filter(Boolean)
18
+ .map((line) => JSON.parse(line));
19
+ }
20
+ async append(sessionId, events) {
21
+ if (events.length === 0) {
22
+ return;
23
+ }
24
+ const key = this.#key(sessionId);
25
+ const previous = writes.get(key) ?? Promise.resolve();
26
+ const write = previous.then(async () => {
27
+ const existing = await AsyncStorage.getItem(key);
28
+ const appended = events.map((event) => JSON.stringify(event)).join("\n");
29
+ await AsyncStorage.setItem(key, `${existing ? `${existing.replace(/\n*$/, "")}\n` : ""}${appended}\n`);
30
+ });
31
+ writes.set(key, write);
32
+ try {
33
+ await write;
34
+ }
35
+ finally {
36
+ if (writes.get(key) === write) {
37
+ writes.delete(key);
38
+ }
39
+ }
40
+ this.#listeners.get(sessionId)?.forEach((listener) => listener(events));
41
+ this.#allListeners.forEach((listener) => listener(sessionId, events));
21
42
  }
22
43
  async listSessionIds() {
23
- throw new Error("React Native JSONL session storage is not implemented yet.");
44
+ const prefix = `@orchajs:${this.#namespace}:`;
45
+ return (await AsyncStorage.getAllKeys())
46
+ .filter((key) => key.startsWith(prefix))
47
+ .map((key) => key.slice(prefix.length))
48
+ .sort();
49
+ }
50
+ subscribe(sessionId, listener) {
51
+ const listeners = this.#listeners.get(sessionId) ?? new Set();
52
+ listeners.add(listener);
53
+ this.#listeners.set(sessionId, listeners);
54
+ return () => {
55
+ listeners.delete(listener);
56
+ if (listeners.size === 0) {
57
+ this.#listeners.delete(sessionId);
58
+ }
59
+ };
60
+ }
61
+ subscribeAll(listener) {
62
+ this.#allListeners.add(listener);
63
+ return () => this.#allListeners.delete(listener);
64
+ }
65
+ async describeLocation(sessionId) {
66
+ return `async-storage://${this.#key(sessionId)}`;
67
+ }
68
+ #key(sessionId) {
69
+ return `@orchajs:${this.#namespace}:${sessionId}`;
24
70
  }
25
71
  }
package/dist/types.d.ts CHANGED
@@ -43,7 +43,7 @@ export interface ProjectConfiguration {
43
43
  providers: ResolvedProviderConfigurations;
44
44
  actions?: LocalActionRuntimeConfiguration;
45
45
  storage?: {
46
- strategy?: "node-jsonl" | "sqlite";
46
+ strategy?: "node-jsonl" | "sqlite" | "indexeddb" | "react-native";
47
47
  directory?: string;
48
48
  };
49
49
  }
@@ -54,6 +54,8 @@ export type AgentRegistration = string | {
54
54
  export interface OrchaInitConfiguration {
55
55
  providers: ProviderInitConfigurations;
56
56
  agents: Record<string, AgentRegistration>;
57
+ /** Precompiled registry used by browser, React Native, and custom runtimes. */
58
+ bundle?: CompiledBundle;
57
59
  amarsia?: {
58
60
  key: string;
59
61
  endpoint?: string;
@@ -62,6 +64,57 @@ export interface OrchaInitConfiguration {
62
64
  storage?: ProjectConfiguration["storage"];
63
65
  root?: string;
64
66
  }
67
+ /** Privacy-safe, versioned run fact sent to Amarsia when `amarsia.key` is set. */
68
+ export interface AmarsiaTelemetryRunV1 {
69
+ event_id: string;
70
+ agent_external_id: string;
71
+ session_id: string;
72
+ run_number: number;
73
+ parent_agent_external_id?: string;
74
+ parent_session_id?: string;
75
+ status: string;
76
+ provider: string;
77
+ model: string;
78
+ region?: string;
79
+ output_type?: string;
80
+ stop_reason?: string;
81
+ started_at: string;
82
+ ended_at?: string;
83
+ duration_ms?: number;
84
+ model_duration_ms: number;
85
+ action_duration_ms: number;
86
+ input_tokens: number;
87
+ output_tokens: number;
88
+ reasoning_tokens?: number;
89
+ cache_read_tokens: number;
90
+ cache_write_tokens: number;
91
+ user_message_count: number;
92
+ assistant_message_count: number;
93
+ action_count: number;
94
+ action_success_count: number;
95
+ action_failure_count: number;
96
+ subagent_count: number;
97
+ actions: AmarsiaTelemetryActionV1[];
98
+ evaluation_count: number;
99
+ evaluation_pass_count: number;
100
+ error_code?: string;
101
+ error_message?: string;
102
+ error_retryable?: boolean;
103
+ }
104
+ export interface AmarsiaTelemetryActionV1 {
105
+ call_id: string;
106
+ name: string;
107
+ status: "completed" | "failed";
108
+ started_at: string;
109
+ ended_at: string;
110
+ duration_ms: number;
111
+ error_code?: string;
112
+ error_message?: string;
113
+ }
114
+ export interface AmarsiaTelemetryBatchV1 {
115
+ schema_version: 1;
116
+ runs: AmarsiaTelemetryRunV1[];
117
+ }
65
118
  export interface SubagentConfiguration {
66
119
  maxPerRun?: number;
67
120
  }