@theaiinc/yggdrasil 0.2.3 → 0.3.2

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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -13
  3. package/dist/src/index.d.ts +8 -1
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +6 -0
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/orchestration-controller.d.ts +14 -2
  8. package/dist/src/orchestration-controller.d.ts.map +1 -1
  9. package/dist/src/orchestration-controller.js +762 -45
  10. package/dist/src/orchestration-controller.js.map +1 -1
  11. package/dist/src/services/npm-version-checker.d.ts +42 -0
  12. package/dist/src/services/npm-version-checker.d.ts.map +1 -0
  13. package/dist/src/services/npm-version-checker.js +99 -0
  14. package/dist/src/services/npm-version-checker.js.map +1 -0
  15. package/dist/src/services/realm-lifecycle.d.ts +49 -0
  16. package/dist/src/services/realm-lifecycle.d.ts.map +1 -0
  17. package/dist/src/services/realm-lifecycle.js +154 -0
  18. package/dist/src/services/realm-lifecycle.js.map +1 -0
  19. package/dist/src/services/realm-provisioner.d.ts +45 -0
  20. package/dist/src/services/realm-provisioner.d.ts.map +1 -0
  21. package/dist/src/services/realm-provisioner.js +102 -0
  22. package/dist/src/services/realm-provisioner.js.map +1 -0
  23. package/dist/src/services/realm-registry.d.ts +83 -0
  24. package/dist/src/services/realm-registry.d.ts.map +1 -0
  25. package/dist/src/services/realm-registry.js +136 -0
  26. package/dist/src/services/realm-registry.js.map +1 -0
  27. package/dist/src/services/realm-scheduler.d.ts +47 -0
  28. package/dist/src/services/realm-scheduler.d.ts.map +1 -0
  29. package/dist/src/services/realm-scheduler.js +112 -0
  30. package/dist/src/services/realm-scheduler.js.map +1 -0
  31. package/dist/src/types/index.d.ts +206 -0
  32. package/dist/src/types/index.d.ts.map +1 -1
  33. package/package.json +14 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-provisioner.js","sourceRoot":"","sources":["../../../src/services/realm-provisioner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * RealmRegistry — stores Realm templates advertised by runners and Realm instances.
3
+ *
4
+ * Responsibilities:
5
+ * - Tracks RealmTemplate[] per runner (what CAN be spawned)
6
+ * - Tracks Realm[] (what IS running)
7
+ * - Provides lookup by runner, template type, owner, and pool tag
8
+ *
9
+ * Yggdrasil is stateless-in-memory in v1. A future version may add
10
+ * FileSessionStore or database-backed persistence.
11
+ */
12
+ import type { Realm, RealmTemplate } from '../types/index.js';
13
+ export interface RealmRegistryEntry {
14
+ realm: Realm;
15
+ template: RealmTemplate;
16
+ }
17
+ export declare class RealmRegistry {
18
+ /** Runner ID → realm templates they advertise. */
19
+ private readonly templatesByRunner;
20
+ /** Realm ID → realm instance + its template. */
21
+ private readonly realms;
22
+ /**
23
+ * Register or update realm templates for a runner.
24
+ */
25
+ setTemplates(runnerId: string, templates: RealmTemplate[]): void;
26
+ /**
27
+ * Get templates for a specific runner.
28
+ */
29
+ getTemplates(runnerId: string): RealmTemplate[];
30
+ /**
31
+ * Get all templates across all runners that match a given template type.
32
+ */
33
+ getTemplatesByType(type: string): Array<{
34
+ runnerId: string;
35
+ template: RealmTemplate;
36
+ }>;
37
+ /**
38
+ * Remove templates for a runner (e.g. on deregistration).
39
+ */
40
+ removeTemplates(runnerId: string): void;
41
+ /**
42
+ * Register a new realm instance.
43
+ */
44
+ addRealm(realm: Realm, template: RealmTemplate): void;
45
+ /**
46
+ * Get a realm instance by ID.
47
+ */
48
+ getRealm(realmId: string): Realm | undefined;
49
+ /**
50
+ * Get realm + template by ID.
51
+ */
52
+ getEntry(realmId: string): RealmRegistryEntry | undefined;
53
+ /**
54
+ * Find a running realm by owner ID and template type (persistent realm affinity).
55
+ * This is how ownerId becomes a scheduling primitive.
56
+ */
57
+ findRealmByOwner(ownerId: string, templateType: string): Realm | undefined;
58
+ /**
59
+ * Find an idle realm in a pool (by template type, no owner).
60
+ */
61
+ findPooledRealm(templateType: string, poolTag?: string): Realm | undefined;
62
+ /**
63
+ * List all realm instances.
64
+ */
65
+ listRealms(): Realm[];
66
+ /**
67
+ * List realms for a specific runner.
68
+ */
69
+ listRealmsByRunner(runnerId: string): Realm[];
70
+ /**
71
+ * Update realm state.
72
+ */
73
+ updateRealmState(realmId: string, state: Realm['state']): void;
74
+ /**
75
+ * Remove a realm.
76
+ */
77
+ removeRealm(realmId: string): void;
78
+ /**
79
+ * Clear all state (useful for testing).
80
+ */
81
+ clear(): void;
82
+ }
83
+ //# sourceMappingURL=realm-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-registry.d.ts","sourceRoot":"","sources":["../../../src/services/realm-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAmB,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,qBAAa,aAAa;IACxB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;IAExE,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAIhE;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,IAAI;IAIhE;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAI/C;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAA;KAAE,CAAC;IAYtF;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAIrD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAI5C;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIzD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAa1E;;OAEG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAe1E;;OAEG;IACH,UAAU,IAAI,KAAK,EAAE;IAIrB;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE;IAM7C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAQ9D;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlC;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * RealmRegistry — stores Realm templates advertised by runners and Realm instances.
3
+ *
4
+ * Responsibilities:
5
+ * - Tracks RealmTemplate[] per runner (what CAN be spawned)
6
+ * - Tracks Realm[] (what IS running)
7
+ * - Provides lookup by runner, template type, owner, and pool tag
8
+ *
9
+ * Yggdrasil is stateless-in-memory in v1. A future version may add
10
+ * FileSessionStore or database-backed persistence.
11
+ */
12
+ export class RealmRegistry {
13
+ /** Runner ID → realm templates they advertise. */
14
+ templatesByRunner = new Map();
15
+ /** Realm ID → realm instance + its template. */
16
+ realms = new Map();
17
+ // ── Template management ──────────────────────────────────────────────
18
+ /**
19
+ * Register or update realm templates for a runner.
20
+ */
21
+ setTemplates(runnerId, templates) {
22
+ this.templatesByRunner.set(runnerId, templates);
23
+ }
24
+ /**
25
+ * Get templates for a specific runner.
26
+ */
27
+ getTemplates(runnerId) {
28
+ return this.templatesByRunner.get(runnerId) ?? [];
29
+ }
30
+ /**
31
+ * Get all templates across all runners that match a given template type.
32
+ */
33
+ getTemplatesByType(type) {
34
+ const results = [];
35
+ for (const [runnerId, templates] of this.templatesByRunner.entries()) {
36
+ for (const template of templates) {
37
+ if (template.type === type) {
38
+ results.push({ runnerId, template });
39
+ }
40
+ }
41
+ }
42
+ return results;
43
+ }
44
+ /**
45
+ * Remove templates for a runner (e.g. on deregistration).
46
+ */
47
+ removeTemplates(runnerId) {
48
+ this.templatesByRunner.delete(runnerId);
49
+ }
50
+ // ── Realm instance management ────────────────────────────────────────
51
+ /**
52
+ * Register a new realm instance.
53
+ */
54
+ addRealm(realm, template) {
55
+ this.realms.set(realm.id, { realm, template });
56
+ }
57
+ /**
58
+ * Get a realm instance by ID.
59
+ */
60
+ getRealm(realmId) {
61
+ return this.realms.get(realmId)?.realm;
62
+ }
63
+ /**
64
+ * Get realm + template by ID.
65
+ */
66
+ getEntry(realmId) {
67
+ return this.realms.get(realmId);
68
+ }
69
+ /**
70
+ * Find a running realm by owner ID and template type (persistent realm affinity).
71
+ * This is how ownerId becomes a scheduling primitive.
72
+ */
73
+ findRealmByOwner(ownerId, templateType) {
74
+ for (const [, entry] of this.realms.entries()) {
75
+ if (entry.realm.ownerId === ownerId &&
76
+ entry.template.type === templateType &&
77
+ (entry.realm.state === 'running' || entry.realm.state === 'paused')) {
78
+ return entry.realm;
79
+ }
80
+ }
81
+ return undefined;
82
+ }
83
+ /**
84
+ * Find an idle realm in a pool (by template type, no owner).
85
+ */
86
+ findPooledRealm(templateType, poolTag) {
87
+ for (const [, entry] of this.realms.entries()) {
88
+ if (entry.realm.state === 'running' &&
89
+ entry.template.type === templateType &&
90
+ entry.realm.ownerId === undefined) {
91
+ if (poolTag === undefined || entry.realm.poolTag === poolTag) {
92
+ return entry.realm;
93
+ }
94
+ }
95
+ }
96
+ return undefined;
97
+ }
98
+ /**
99
+ * List all realm instances.
100
+ */
101
+ listRealms() {
102
+ return Array.from(this.realms.values()).map((e) => e.realm);
103
+ }
104
+ /**
105
+ * List realms for a specific runner.
106
+ */
107
+ listRealmsByRunner(runnerId) {
108
+ return Array.from(this.realms.values())
109
+ .filter((e) => e.realm.runnerId === runnerId)
110
+ .map((e) => e.realm);
111
+ }
112
+ /**
113
+ * Update realm state.
114
+ */
115
+ updateRealmState(realmId, state) {
116
+ const entry = this.realms.get(realmId);
117
+ if (entry) {
118
+ entry.realm.state = state;
119
+ entry.realm.updatedAt = new Date().toISOString();
120
+ }
121
+ }
122
+ /**
123
+ * Remove a realm.
124
+ */
125
+ removeRealm(realmId) {
126
+ this.realms.delete(realmId);
127
+ }
128
+ /**
129
+ * Clear all state (useful for testing).
130
+ */
131
+ clear() {
132
+ this.templatesByRunner.clear();
133
+ this.realms.clear();
134
+ }
135
+ }
136
+ //# sourceMappingURL=realm-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-registry.js","sourceRoot":"","sources":["../../../src/services/realm-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * RealmScheduler — makes allocation decisions for session requests.
3
+ *
4
+ * Yggdrasil owns all scheduling policy:
5
+ * - Which realm type?
6
+ * - Which owner?
7
+ * - Reuse allowed?
8
+ * - Spawn allowed?
9
+ * - Priority?
10
+ *
11
+ * Ratatoskr reports facts (CPU, memory, realms, templates).
12
+ * Yggdrasil decides. Ratatoskr may veto ("I cannot do that") but
13
+ * must never suggest alternatives — that is orchestration.
14
+ *
15
+ * Current implementation:
16
+ * Simple first-fit by template match + online status.
17
+ *
18
+ * Future:
19
+ * Resource-aware, owner-aware, cost-aware, affinity-aware,
20
+ * informed by Ratatoskr's /state endpoint.
21
+ */
22
+ import type { CreateSessionRequest, RealmAllocation, SessionCapability, RunnerInfo } from '../types/index.js';
23
+ import { RealmRegistry } from './realm-registry.js';
24
+ export declare class RealmScheduler {
25
+ private readonly registry;
26
+ private readonly getRunner;
27
+ constructor(registry: RealmRegistry, getRunner: (runnerId: string) => RunnerInfo | undefined);
28
+ /**
29
+ * Decide how to allocate a realm for a session request.
30
+ *
31
+ * Yggdrasil decides:
32
+ * - Which runner
33
+ * - Which template
34
+ * - Whether to spawn new or attach to existing
35
+ *
36
+ * Priority order:
37
+ * 1. Attach to existing realm owned by this owner (persistent realm)
38
+ * 2. Attach to a pooled idle realm (pre-warmed)
39
+ * 3. Spawn a new realm on a healthy runner
40
+ */
41
+ schedule(request: CreateSessionRequest): Promise<RealmAllocation>;
42
+ /**
43
+ * Resolve default capabilities for a template type when not explicitly provided.
44
+ */
45
+ static defaultCapabilitiesFor(type: string): SessionCapability[];
46
+ }
47
+ //# sourceMappingURL=realm-scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-scheduler.d.ts","sourceRoot":"","sources":["../../../src/services/realm-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,UAAU,EACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoBpD,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS;IAG1E;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAqDvE;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE;CAGjE"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * RealmScheduler — makes allocation decisions for session requests.
3
+ *
4
+ * Yggdrasil owns all scheduling policy:
5
+ * - Which realm type?
6
+ * - Which owner?
7
+ * - Reuse allowed?
8
+ * - Spawn allowed?
9
+ * - Priority?
10
+ *
11
+ * Ratatoskr reports facts (CPU, memory, realms, templates).
12
+ * Yggdrasil decides. Ratatoskr may veto ("I cannot do that") but
13
+ * must never suggest alternatives — that is orchestration.
14
+ *
15
+ * Current implementation:
16
+ * Simple first-fit by template match + online status.
17
+ *
18
+ * Future:
19
+ * Resource-aware, owner-aware, cost-aware, affinity-aware,
20
+ * informed by Ratatoskr's /state endpoint.
21
+ */
22
+ /**
23
+ * Map from SessionType to the required RealmTemplateType.
24
+ */
25
+ const SESSION_TO_TEMPLATE = {
26
+ 'computer-use': 'ubuntu',
27
+ 'phone-use': 'android',
28
+ };
29
+ /**
30
+ * Default capabilities for a template when none are explicitly advertised.
31
+ */
32
+ const DEFAULT_TEMPLATE_CAPABILITIES = {
33
+ ubuntu: ['observe', 'mouse', 'keyboard', 'scroll', 'clipboard'],
34
+ android: ['observe', 'touch', 'keyboard', 'scroll'],
35
+ browser: ['observe', 'keyboard'],
36
+ windows: ['observe', 'mouse', 'keyboard', 'scroll'],
37
+ };
38
+ export class RealmScheduler {
39
+ registry;
40
+ getRunner;
41
+ constructor(registry, getRunner) {
42
+ this.registry = registry;
43
+ this.getRunner = getRunner;
44
+ }
45
+ /**
46
+ * Decide how to allocate a realm for a session request.
47
+ *
48
+ * Yggdrasil decides:
49
+ * - Which runner
50
+ * - Which template
51
+ * - Whether to spawn new or attach to existing
52
+ *
53
+ * Priority order:
54
+ * 1. Attach to existing realm owned by this owner (persistent realm)
55
+ * 2. Attach to a pooled idle realm (pre-warmed)
56
+ * 3. Spawn a new realm on a healthy runner
57
+ */
58
+ async schedule(request) {
59
+ const templateType = SESSION_TO_TEMPLATE[request.type];
60
+ if (!templateType) {
61
+ throw new Error(`No realm template available for session type: ${request.type}`);
62
+ }
63
+ // Priority 1: Persistent realm affinity by owner
64
+ if (request.ownerId) {
65
+ const existing = this.registry.findRealmByOwner(request.ownerId, templateType);
66
+ if (existing) {
67
+ const entry = this.registry.getEntry(existing.id);
68
+ return {
69
+ runnerId: existing.runnerId,
70
+ template: entry.template,
71
+ action: 'attach',
72
+ realmId: existing.id,
73
+ };
74
+ }
75
+ }
76
+ // Priority 2: Pooled idle realm
77
+ const pooled = this.registry.findPooledRealm(templateType);
78
+ if (pooled) {
79
+ const entry = this.registry.getEntry(pooled.id);
80
+ return {
81
+ runnerId: pooled.runnerId,
82
+ template: entry.template,
83
+ action: 'attach',
84
+ realmId: pooled.id,
85
+ };
86
+ }
87
+ // Priority 3: Find a runner that can host this template type
88
+ const candidates = this.registry.getTemplatesByType(templateType);
89
+ if (candidates.length === 0) {
90
+ throw new Error(`No runner available that can host realm type: ${templateType}`);
91
+ }
92
+ // Pick first healthy runner with capacity
93
+ for (const candidate of candidates) {
94
+ const runner = this.getRunner(candidate.runnerId);
95
+ if (runner && runner.status === 'online') {
96
+ return {
97
+ runnerId: candidate.runnerId,
98
+ template: candidate.template,
99
+ action: 'spawn',
100
+ };
101
+ }
102
+ }
103
+ throw new Error(`No online runner available for realm type: ${templateType}`);
104
+ }
105
+ /**
106
+ * Resolve default capabilities for a template type when not explicitly provided.
107
+ */
108
+ static defaultCapabilitiesFor(type) {
109
+ return DEFAULT_TEMPLATE_CAPABILITIES[type] ?? ['observe'];
110
+ }
111
+ }
112
+ //# sourceMappingURL=realm-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-scheduler.js","sourceRoot":"","sources":["../../../src/services/realm-scheduler.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,194 @@ export interface LoggerConfig {
12
12
  format: 'json' | 'simple';
13
13
  transports: string[];
14
14
  }
15
+ /**
16
+ * Registration payload sent by a Realm instance (via Ratatoskr) to Yggdrasil.
17
+ * Realm announces itself after boot.
18
+ */
19
+ export interface RealmRegistration {
20
+ realmId: string;
21
+ runnerId: string;
22
+ /** The template type this realm was spawned from (e.g. "ubuntu", "android"). */
23
+ template: RealmTemplateType;
24
+ /** Realm software version. */
25
+ version: string;
26
+ /** Capabilities this realm instance provides. */
27
+ capabilities: SessionCapability[];
28
+ /** Live endpoints for observation and input. */
29
+ endpoints: {
30
+ observation: string;
31
+ input: string;
32
+ };
33
+ /** Future: Veil-issued token for authenticating registrations. */
34
+ registrationToken?: string | undefined;
35
+ startedAt: string;
36
+ }
37
+ /**
38
+ * Periodic heartbeat from a Realm instance (via Ratatoskr) to Yggdrasil.
39
+ */
40
+ export interface RealmHeartbeat {
41
+ realmId: string;
42
+ uptime: number;
43
+ healthy: boolean;
44
+ memoryMb?: number | undefined;
45
+ cpuPercent?: number | undefined;
46
+ activeSessions: number;
47
+ }
48
+ /**
49
+ * Deregistration payload sent by a Realm instance on shutdown.
50
+ */
51
+ export interface RealmDeregistration {
52
+ realmId: string;
53
+ reason: 'shutdown' | 'error' | 'replaced';
54
+ }
55
+ /** Supported session types for interaction with execution environments. */
56
+ export type SessionType = 'computer-use' | 'phone-use';
57
+ /** Lifecycle states for a session. */
58
+ export type SessionState = 'creating' | 'active' | 'paused' | 'completed' | 'failed' | 'terminated';
59
+ /** Observation method chosen by Realm — consumers must not depend on a specific implementation. */
60
+ export type ObservationMethod = 'accessibility_tree' | 'dom_snapshot' | 'screenshot' | 'video_stream' | 'hybrid';
61
+ /** Input capabilities a session may expose. */
62
+ export type InputCapability = 'mouse' | 'keyboard' | 'touch' | 'scroll' | 'drag' | 'clipboard';
63
+ /**
64
+ * Capabilities a session may support — used for session contracts and Veil authorization.
65
+ * Broader than InputCapability: includes observation and device-level capabilities.
66
+ */
67
+ export type SessionCapability = 'observe' | 'mouse' | 'keyboard' | 'touch' | 'scroll' | 'drag' | 'clipboard' | 'audio' | 'camera';
68
+ /** Types of execution environments a runner can host. */
69
+ export type RealmTemplateType = 'ubuntu' | 'android' | 'browser' | 'windows';
70
+ /** Lifecycle states for a Realm instance. */
71
+ export type RealmState = 'creating' | 'running' | 'paused' | 'unhealthy' | 'destroyed';
72
+ /**
73
+ * A realm template advertised by a runner via Ratatoskr.
74
+ * Templates describe what kinds of environments a runner CAN spawn,
75
+ * not what is currently running.
76
+ */
77
+ export interface RealmTemplate {
78
+ id: string;
79
+ type: RealmTemplateType;
80
+ /** Capabilities this template provides when a realm is spawned (e.g. ["observe", "mouse", "keyboard"]). */
81
+ capabilities: SessionCapability[];
82
+ }
83
+ /**
84
+ * A running Realm instance — the actual execution environment that sessions attach to.
85
+ *
86
+ * Sessions do NOT run on runners. Sessions run on realms. Realms run on runners.
87
+ */
88
+ export interface Realm {
89
+ id: string;
90
+ templateId: string;
91
+ runnerId: string;
92
+ /** The entity that owns or requested this realm (used for persistent realm affinity). */
93
+ ownerId?: string | undefined;
94
+ state: RealmState;
95
+ endpoints: {
96
+ /** Full URL for observation (e.g. screenshots, a11y tree). */
97
+ observation: string;
98
+ /** Full URL for input (e.g. click, type, scroll). */
99
+ input: string;
100
+ };
101
+ createdAt: string;
102
+ updatedAt: string;
103
+ /** Timestamp of the last heartbeat received from this realm. */
104
+ lastHeartbeat?: string | undefined;
105
+ /** Future: Veil-issued token for authenticating realm operations. */
106
+ registrationToken?: string | undefined;
107
+ /** Pool tag — if set, this realm can be reused for sessions matching the same template+owner. */
108
+ poolTag?: string | undefined;
109
+ }
110
+ /**
111
+ * Result of a scheduling decision. The scheduler returns an allocation;
112
+ * the provisioner acts on it.
113
+ */
114
+ export interface RealmAllocation {
115
+ runnerId: string;
116
+ template: RealmTemplate;
117
+ /** Whether to spawn a new realm or attach to an existing one. */
118
+ action: 'spawn' | 'attach';
119
+ /** Set when attaching to an existing realm. */
120
+ realmId?: string | undefined;
121
+ }
122
+ /**
123
+ * Descriptor for an active session.
124
+ *
125
+ * Yggdrasil is the **control plane** — it creates/terminates/pauses/resumes sessions.
126
+ * Realm is the **data plane** — observation and input go DIRECTLY to Realm endpoints,
127
+ * NOT through Yggdrasil.
128
+ *
129
+ * Consumers (Cognition via ComputerUseRuntime) use observationEndpoint and
130
+ * inputEndpoint to talk to Realm directly. Yggdrasil never proxies observe/input calls.
131
+ */
132
+ export interface SessionDescriptor {
133
+ id: string;
134
+ type: SessionType;
135
+ state: SessionState;
136
+ /** Full Realm URL for observation (e.g. screenshots, a11y tree, DOM). Consumers talk to Realm directly. */
137
+ observationEndpoint: string;
138
+ /** Full Realm URL for input (e.g. click, type, scroll). Consumers talk to Realm directly. */
139
+ inputEndpoint: string;
140
+ /** Capabilities this session supports (e.g. ["mouse", "keyboard"]). */
141
+ capabilities: SessionCapability[];
142
+ /** The observation method Realm chose (internal detail — informational only). */
143
+ observationMethod: ObservationMethod;
144
+ /** Realm ID backing this session. */
145
+ realmId: string;
146
+ /** Identity of the entity that owns this session. */
147
+ ownerId?: string | undefined;
148
+ /** Identities of participants allowed to interact with this session. */
149
+ participantIds?: string[] | undefined;
150
+ createdAt: string;
151
+ updatedAt: string;
152
+ metadata?: Record<string, unknown> | undefined;
153
+ }
154
+ /** Request to create a new interaction session. */
155
+ export interface CreateSessionRequest {
156
+ type: SessionType;
157
+ ownerId?: string | undefined;
158
+ participantIds?: string[] | undefined;
159
+ /** Requested capabilities for this session. If omitted, type defaults apply. */
160
+ capabilities?: SessionCapability[] | undefined;
161
+ realmId?: string | undefined;
162
+ metadata?: Record<string, unknown> | undefined;
163
+ }
164
+ /** Response from creating a session. */
165
+ export interface CreateSessionResponse {
166
+ sessionId: string;
167
+ descriptor: SessionDescriptor;
168
+ }
169
+ /** Observation payload returned by a session's observe endpoint. */
170
+ export interface SessionObservation {
171
+ /** Base64-encoded screenshot (when method is screenshot or hybrid). */
172
+ screenshot?: string | undefined;
173
+ /** Accessibility tree snapshot (when method is accessibility_tree or hybrid). */
174
+ accessibilityTree?: unknown;
175
+ /** DOM snapshot (when method is dom_snapshot or hybrid). */
176
+ domSnapshot?: unknown;
177
+ /** Whether PII redaction was applied. */
178
+ piiRedacted?: boolean | undefined;
179
+ /** Timestamp of the observation. */
180
+ timestamp: string;
181
+ /** Structured data for JSON-based observation (e.g. UI element tree). */
182
+ data?: Record<string, unknown> | undefined;
183
+ }
184
+ /** Input action sent to a session. */
185
+ export interface SessionInput {
186
+ type: InputCapability;
187
+ params: Record<string, unknown>;
188
+ }
189
+ /** Result of an input action. */
190
+ export interface SessionInputResult {
191
+ success: boolean;
192
+ error?: string | undefined;
193
+ }
194
+ /** Session health reported by Ratatoskr to Yggdrasil. */
195
+ export interface SessionHealth {
196
+ sessionId: string;
197
+ state: SessionState;
198
+ realmId: string;
199
+ lastObservationAt?: string | undefined;
200
+ lastInputAt?: string | undefined;
201
+ errorCount: number;
202
+ }
15
203
  export interface SystemResources {
16
204
  cpu: {
17
205
  load1: number;
@@ -30,10 +218,16 @@ export interface SystemResources {
30
218
  }
31
219
  export interface PendingUpdate {
32
220
  version: string;
221
+ /** Shell command to execute for the update (e.g. 'npm update -g @theaiinc/yggdrasil-ratatoskr'). */
33
222
  command?: string;
223
+ /** URL to download a new binary/package from. */
34
224
  downloadUrl?: string;
225
+ /** New API key for Yggdrasil authentication. Runner applies this and starts using it immediately. */
226
+ apiKey?: string;
227
+ /** Arbitrary metadata (e.g. Docker image tag, commit hash). */
35
228
  metadata?: Record<string, unknown>;
36
229
  }
230
+ export type UpdateStatus = 'idle' | 'pending' | 'applying' | 'failed' | 'applied';
37
231
  export interface RunnerTask {
38
232
  taskId: string;
39
233
  type: string;
@@ -49,12 +243,18 @@ export interface RunnerInfo {
49
243
  endpoint: string;
50
244
  version: string;
51
245
  capabilities: string[];
246
+ /** Realm templates this runner can spawn. */
247
+ realmTemplates: RealmTemplate[];
52
248
  labels: Record<string, string>;
53
249
  lastHeartbeat: Date;
54
250
  status: 'online' | 'offline';
55
251
  resources?: SystemResources;
56
252
  tasks: RunnerTask[];
57
253
  pendingUpdate?: PendingUpdate;
254
+ /** Latest update status reported by the runner via heartbeat. */
255
+ updateStatus?: UpdateStatus;
256
+ /** Tail of the runner's update log (last N chars). */
257
+ updateLog?: string;
58
258
  }
59
259
  export interface RegisterRunnerPayload {
60
260
  runnerId?: string;
@@ -62,6 +262,8 @@ export interface RegisterRunnerPayload {
62
262
  endpoint?: string;
63
263
  version?: string;
64
264
  capabilities?: string[];
265
+ /** Realm templates this runner can spawn. */
266
+ realmTemplates?: RealmTemplate[];
65
267
  labels?: Record<string, string>;
66
268
  metadata?: Record<string, unknown>;
67
269
  resources?: SystemResources;
@@ -73,6 +275,10 @@ export interface HeartbeatPayload {
73
275
  status?: string;
74
276
  resources?: SystemResources;
75
277
  tasks?: RunnerTask[];
278
+ /** Update status reported by the runner. */
279
+ updateStatus?: UpdateStatus;
280
+ /** Tail of the runner's update log. */
281
+ updateLog?: string;
76
282
  }
77
283
  export interface HeartbeatResponse {
78
284
  status: string;