@shogo-ai/core 1.9.13 → 2.4.1

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.
@@ -53,6 +53,37 @@ interface StackRegistryEntry {
53
53
  * bundled Vite template; everything else seeds itself.
54
54
  */
55
55
  seedsOwnTemplate?: boolean;
56
+ /**
57
+ * Metal VM class this stack requires. Mirrors `TechStackMeta.runtime.vmClass`
58
+ * (see `packages/agent-runtime/src/workspace-defaults.ts`) so `apps/api` —
59
+ * which does not bundle `tech-stacks/` and cannot read `stack.json` at
60
+ * runtime — can decide placement/gating without it. Omitted = `'standard'`,
61
+ * the default every non-Docker stack uses.
62
+ */
63
+ vmClass?: 'standard' | 'docker';
64
+ /**
65
+ * Smallest instance size (`apps/api/src/config/instance-sizes.ts`
66
+ * `InstanceSizeName`) this stack should be billed/provisioned at. Mirrors
67
+ * `TechStackMeta.runtime.minimumInstanceSize`. Unlike the mobile floor
68
+ * (`applyTechStackFloor`), this floor is BILLED, not just a free headroom
69
+ * bump — see `applyDockerStackFloor`.
70
+ */
71
+ minimumInstanceSize?: 'micro' | 'small' | 'medium' | 'large' | 'xlarge';
72
+ /**
73
+ * Ports the stack's services expose, mirroring
74
+ * `TechStackMeta.runtime.ports` (see `workspace-defaults.ts`) for the same
75
+ * reason `vmClass`/`minimumInstanceSize` are mirrored: apps/api can't read
76
+ * `stack.json` at runtime, but needs this to validate `PATCH .../ports/:port`
77
+ * (a project can only toggle visibility on a port its stack actually
78
+ * declares — never an arbitrary guest port) and to seed
79
+ * `Project.settings.exposedPorts` defaults.
80
+ */
81
+ ports?: Array<{
82
+ port: number;
83
+ label?: string;
84
+ protocol: 'http' | 'tcp';
85
+ defaultVisibility: 'tunnel' | 'preview';
86
+ }>;
56
87
  }
57
88
  /**
58
89
  * Canonical registry of every first-party tech stack we ship. Kept here in
@@ -84,5 +115,21 @@ declare function usesMetroBundler(techStackId: string | null | undefined): boole
84
115
  * adding a new stack doesn't accidentally break the default behaviour.
85
116
  */
86
117
  declare function stackSeedsItself(techStackId: string | null | undefined): boolean;
118
+ /**
119
+ * True for any stack whose `vmClass` is `'docker'` — i.e. it needs the
120
+ * Docker-capable metal VM class (dockerd + a persistent data volume), not
121
+ * the default standard project VM. Callers must still check the platform
122
+ * gate (`runtime.docker_class_enabled`) before honouring this.
123
+ */
124
+ declare function isDockerTechStack(techStackId: string | null | undefined): boolean;
125
+ /** The stack's declared minimum instance size, or `null` if it doesn't set one. */
126
+ declare function getMinimumInstanceSize(techStackId: string | null | undefined): StackRegistryEntry['minimumInstanceSize'] | null;
127
+ /**
128
+ * The stack's declared ports (empty array if it declares none). This is the
129
+ * ONLY allowlist for both the client-side tunnel and the public per-port
130
+ * preview — a project can toggle visibility on a declared port, but can
131
+ * never expose a port its stack doesn't list.
132
+ */
133
+ declare function getDeclaredPorts(techStackId: string | null | undefined): NonNullable<StackRegistryEntry['ports']>;
87
134
 
88
- export { type StackRegistryEntry, type StackTarget, TECH_STACK_REGISTRY, getStackEntry, isMobileTechStack, stackSeedsItself, usesMetroBundler };
135
+ export { type StackRegistryEntry, type StackTarget, TECH_STACK_REGISTRY, getDeclaredPorts, getMinimumInstanceSize, getStackEntry, isDockerTechStack, isMobileTechStack, stackSeedsItself, usesMetroBundler };
@@ -53,6 +53,37 @@ interface StackRegistryEntry {
53
53
  * bundled Vite template; everything else seeds itself.
54
54
  */
55
55
  seedsOwnTemplate?: boolean;
56
+ /**
57
+ * Metal VM class this stack requires. Mirrors `TechStackMeta.runtime.vmClass`
58
+ * (see `packages/agent-runtime/src/workspace-defaults.ts`) so `apps/api` —
59
+ * which does not bundle `tech-stacks/` and cannot read `stack.json` at
60
+ * runtime — can decide placement/gating without it. Omitted = `'standard'`,
61
+ * the default every non-Docker stack uses.
62
+ */
63
+ vmClass?: 'standard' | 'docker';
64
+ /**
65
+ * Smallest instance size (`apps/api/src/config/instance-sizes.ts`
66
+ * `InstanceSizeName`) this stack should be billed/provisioned at. Mirrors
67
+ * `TechStackMeta.runtime.minimumInstanceSize`. Unlike the mobile floor
68
+ * (`applyTechStackFloor`), this floor is BILLED, not just a free headroom
69
+ * bump — see `applyDockerStackFloor`.
70
+ */
71
+ minimumInstanceSize?: 'micro' | 'small' | 'medium' | 'large' | 'xlarge';
72
+ /**
73
+ * Ports the stack's services expose, mirroring
74
+ * `TechStackMeta.runtime.ports` (see `workspace-defaults.ts`) for the same
75
+ * reason `vmClass`/`minimumInstanceSize` are mirrored: apps/api can't read
76
+ * `stack.json` at runtime, but needs this to validate `PATCH .../ports/:port`
77
+ * (a project can only toggle visibility on a port its stack actually
78
+ * declares — never an arbitrary guest port) and to seed
79
+ * `Project.settings.exposedPorts` defaults.
80
+ */
81
+ ports?: Array<{
82
+ port: number;
83
+ label?: string;
84
+ protocol: 'http' | 'tcp';
85
+ defaultVisibility: 'tunnel' | 'preview';
86
+ }>;
56
87
  }
57
88
  /**
58
89
  * Canonical registry of every first-party tech stack we ship. Kept here in
@@ -84,5 +115,21 @@ declare function usesMetroBundler(techStackId: string | null | undefined): boole
84
115
  * adding a new stack doesn't accidentally break the default behaviour.
85
116
  */
86
117
  declare function stackSeedsItself(techStackId: string | null | undefined): boolean;
118
+ /**
119
+ * True for any stack whose `vmClass` is `'docker'` — i.e. it needs the
120
+ * Docker-capable metal VM class (dockerd + a persistent data volume), not
121
+ * the default standard project VM. Callers must still check the platform
122
+ * gate (`runtime.docker_class_enabled`) before honouring this.
123
+ */
124
+ declare function isDockerTechStack(techStackId: string | null | undefined): boolean;
125
+ /** The stack's declared minimum instance size, or `null` if it doesn't set one. */
126
+ declare function getMinimumInstanceSize(techStackId: string | null | undefined): StackRegistryEntry['minimumInstanceSize'] | null;
127
+ /**
128
+ * The stack's declared ports (empty array if it declares none). This is the
129
+ * ONLY allowlist for both the client-side tunnel and the public per-port
130
+ * preview — a project can toggle visibility on a declared port, but can
131
+ * never expose a port its stack doesn't list.
132
+ */
133
+ declare function getDeclaredPorts(techStackId: string | null | undefined): NonNullable<StackRegistryEntry['ports']>;
87
134
 
88
- export { type StackRegistryEntry, type StackTarget, TECH_STACK_REGISTRY, getStackEntry, isMobileTechStack, stackSeedsItself, usesMetroBundler };
135
+ export { type StackRegistryEntry, type StackTarget, TECH_STACK_REGISTRY, getDeclaredPorts, getMinimumInstanceSize, getStackEntry, isDockerTechStack, isMobileTechStack, stackSeedsItself, usesMetroBundler };
@@ -1,13 +1,19 @@
1
1
  import {
2
2
  TECH_STACK_REGISTRY,
3
+ getDeclaredPorts,
4
+ getMinimumInstanceSize,
3
5
  getStackEntry,
6
+ isDockerTechStack,
4
7
  isMobileTechStack,
5
8
  stackSeedsItself,
6
9
  usesMetroBundler
7
- } from "./chunk-KTDDFWXM.js";
10
+ } from "./chunk-NTAY4E43.js";
8
11
  export {
9
12
  TECH_STACK_REGISTRY,
13
+ getDeclaredPorts,
14
+ getMinimumInstanceSize,
10
15
  getStackEntry,
16
+ isDockerTechStack,
11
17
  isMobileTechStack,
12
18
  stackSeedsItself,
13
19
  usesMetroBundler
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogo-ai/core",
3
- "version": "1.9.13",
3
+ "version": "2.4.1",
4
4
  "description": "Shogo Core — generic utilities (logger, instrumentation, stream-buffer, chat-message). Zero required peers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -64,8 +64,11 @@
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@opentelemetry/api": ">=1.9.0",
67
+ "@opentelemetry/api-logs": ">=0.213.0",
67
68
  "@opentelemetry/sdk-node": ">=0.213.0",
68
69
  "@opentelemetry/exporter-trace-otlp-http": ">=0.213.0",
70
+ "@opentelemetry/exporter-logs-otlp-http": ">=0.213.0",
71
+ "@opentelemetry/sdk-logs": ">=0.213.0",
69
72
  "@opentelemetry/sdk-trace-base": ">=2.5.0",
70
73
  "@opentelemetry/resources": ">=2.5.0",
71
74
  "@opentelemetry/semantic-conventions": ">=1.39.0"
@@ -74,12 +77,21 @@
74
77
  "@opentelemetry/api": {
75
78
  "optional": true
76
79
  },
80
+ "@opentelemetry/api-logs": {
81
+ "optional": true
82
+ },
77
83
  "@opentelemetry/sdk-node": {
78
84
  "optional": true
79
85
  },
80
86
  "@opentelemetry/exporter-trace-otlp-http": {
81
87
  "optional": true
82
88
  },
89
+ "@opentelemetry/exporter-logs-otlp-http": {
90
+ "optional": true
91
+ },
92
+ "@opentelemetry/sdk-logs": {
93
+ "optional": true
94
+ },
83
95
  "@opentelemetry/sdk-trace-base": {
84
96
  "optional": true
85
97
  },
@@ -92,9 +104,12 @@
92
104
  },
93
105
  "devDependencies": {
94
106
  "@opentelemetry/api": "^1.9.0",
107
+ "@opentelemetry/api-logs": "^0.213.0",
95
108
  "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
109
+ "@opentelemetry/exporter-logs-otlp-http": "^0.213.0",
96
110
  "@opentelemetry/resources": "^2.5.1",
97
111
  "@opentelemetry/sdk-node": "^0.213.0",
112
+ "@opentelemetry/sdk-logs": "^0.213.0",
98
113
  "@opentelemetry/sdk-trace-base": "^2.5.1",
99
114
  "@opentelemetry/semantic-conventions": "^1.39.0",
100
115
  "@types/node": "^25.5.0",
@@ -1,236 +0,0 @@
1
- // src/stream-buffer.ts
2
- var CLEANUP_INTERVAL_MS = 6e4;
3
- var MAX_BUFFER_AGE_MS = 30 * 6e4;
4
- var COMPLETED_GRACE_MS = 3e4;
5
- var StreamBufferStore = class {
6
- constructor() {
7
- this.buffers = /* @__PURE__ */ new Map();
8
- this.cleanupTimer = null;
9
- this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
10
- const handle = this.cleanupTimer;
11
- if (handle && typeof handle.unref === "function") {
12
- handle.unref();
13
- }
14
- }
15
- /**
16
- * Create (or replace) a buffer for the given key. Any existing active
17
- * buffer is completed and discarded. Returns a writer bound to this
18
- * specific buffer instance, including the new `turnId`.
19
- */
20
- create(key, opts) {
21
- const existing = this.buffers.get(key);
22
- if (existing && existing.status === "active") {
23
- this.completeBuffer(existing, "replaced");
24
- }
25
- const now = Date.now();
26
- const turnId = opts?.turnId || generateTurnId();
27
- const buf = {
28
- turnId,
29
- frames: [],
30
- subscribers: /* @__PURE__ */ new Set(),
31
- status: "active",
32
- terminal: null,
33
- createdAt: now,
34
- completedAt: null,
35
- lastEventAt: now,
36
- nextSeq: 1
37
- };
38
- this.buffers.set(key, buf);
39
- return {
40
- get turnId() {
41
- return buf.turnId;
42
- },
43
- get lastSeq() {
44
- return buf.nextSeq - 1;
45
- },
46
- append: (chunk) => {
47
- if (buf.status !== "active") return -1;
48
- const seq = buf.nextSeq++;
49
- buf.frames.push({ seq, chunk });
50
- buf.lastEventAt = Date.now();
51
- for (const ctrl of buf.subscribers) {
52
- try {
53
- ctrl.enqueue(chunk);
54
- } catch {
55
- buf.subscribers.delete(ctrl);
56
- }
57
- }
58
- return seq;
59
- },
60
- complete: (reason) => {
61
- this.completeBuffer(buf, reason);
62
- },
63
- fail: (error) => {
64
- this.failBuffer(buf, error);
65
- }
66
- };
67
- }
68
- /**
69
- * Append a chunk by key (loose, untyped writer). Returns the seq assigned,
70
- * or -1 if the buffer is unknown/inactive.
71
- */
72
- append(key, chunk) {
73
- const buf = this.buffers.get(key);
74
- if (!buf || buf.status !== "active") return -1;
75
- const seq = buf.nextSeq++;
76
- buf.frames.push({ seq, chunk });
77
- buf.lastEventAt = Date.now();
78
- for (const ctrl of buf.subscribers) {
79
- try {
80
- ctrl.enqueue(chunk);
81
- } catch {
82
- buf.subscribers.delete(ctrl);
83
- }
84
- }
85
- return seq;
86
- }
87
- /** Mark a buffer as completed by key. Optional terminal reason. */
88
- complete(key, reason) {
89
- const buf = this.buffers.get(key);
90
- if (!buf) return;
91
- this.completeBuffer(buf, reason);
92
- }
93
- /**
94
- * Abort a stream: complete subscribers and remove the buffer entirely.
95
- * Future resume/replay requests for this key will return null (→ 204).
96
- */
97
- abort(key) {
98
- const buf = this.buffers.get(key);
99
- if (!buf) return;
100
- this.completeBuffer(buf, "aborted");
101
- buf.status = "aborted";
102
- this.buffers.delete(key);
103
- }
104
- has(key) {
105
- return this.buffers.has(key);
106
- }
107
- /** Read-only metadata about a buffered turn. Useful for resume responses. */
108
- snapshot(key) {
109
- const buf = this.buffers.get(key);
110
- if (!buf) return null;
111
- return {
112
- turnId: buf.turnId,
113
- status: buf.status,
114
- lastSeq: buf.nextSeq - 1,
115
- terminal: buf.terminal,
116
- createdAt: buf.createdAt,
117
- completedAt: buf.completedAt,
118
- lastEventAt: buf.lastEventAt
119
- };
120
- }
121
- /**
122
- * Create a ReadableStream that first replays all buffered chunks past
123
- * `opts.fromSeq` (default: 0 → replay everything), then subscribes for
124
- * any further live chunks until the stream completes.
125
- *
126
- * Returns null if no buffer exists for the key.
127
- */
128
- createReplayStream(key, opts = {}) {
129
- const buf = this.buffers.get(key);
130
- if (!buf) return null;
131
- const fromSeq = Math.max(0, opts.fromSeq ?? 0);
132
- let subscribedController = null;
133
- return new ReadableStream({
134
- start(controller) {
135
- for (const frame of buf.frames) {
136
- if (frame.seq <= fromSeq) continue;
137
- try {
138
- controller.enqueue(frame.chunk);
139
- } catch {
140
- return;
141
- }
142
- }
143
- if (buf.status !== "active") {
144
- try {
145
- controller.close();
146
- } catch {
147
- }
148
- return;
149
- }
150
- subscribedController = controller;
151
- buf.subscribers.add(controller);
152
- },
153
- cancel() {
154
- if (subscribedController) {
155
- buf.subscribers.delete(subscribedController);
156
- subscribedController = null;
157
- }
158
- }
159
- });
160
- }
161
- cleanup() {
162
- const now = Date.now();
163
- for (const [key, buf] of this.buffers) {
164
- if (buf.status !== "active" && buf.completedAt && now - buf.completedAt > COMPLETED_GRACE_MS) {
165
- this.buffers.delete(key);
166
- } else if (now - buf.createdAt > MAX_BUFFER_AGE_MS && buf.subscribers.size === 0) {
167
- this.completeBuffer(buf, "expired");
168
- this.buffers.delete(key);
169
- }
170
- }
171
- }
172
- dispose() {
173
- if (this.cleanupTimer) {
174
- clearInterval(this.cleanupTimer);
175
- this.cleanupTimer = null;
176
- }
177
- for (const buf of this.buffers.values()) {
178
- this.completeBuffer(buf, "disposed");
179
- }
180
- this.buffers.clear();
181
- }
182
- completeBuffer(buf, reason) {
183
- if (buf.status !== "active") return;
184
- buf.status = "completed";
185
- buf.terminal = { reason };
186
- buf.completedAt = Date.now();
187
- buf.lastEventAt = buf.completedAt;
188
- for (const ctrl of buf.subscribers) {
189
- try {
190
- ctrl.close();
191
- } catch {
192
- }
193
- }
194
- buf.subscribers.clear();
195
- }
196
- failBuffer(buf, error) {
197
- if (buf.status !== "active") return;
198
- buf.status = "failed";
199
- buf.terminal = { error };
200
- buf.completedAt = Date.now();
201
- buf.lastEventAt = buf.completedAt;
202
- for (const ctrl of buf.subscribers) {
203
- try {
204
- ctrl.close();
205
- } catch {
206
- }
207
- }
208
- buf.subscribers.clear();
209
- }
210
- };
211
- function generateTurnId() {
212
- try {
213
- if (typeof globalThis.crypto?.randomUUID === "function") {
214
- return globalThis.crypto.randomUUID();
215
- }
216
- } catch {
217
- }
218
- return `turn_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
219
- }
220
- function createBufferingTransform(store, key) {
221
- return new TransformStream({
222
- transform(chunk, controller) {
223
- store.append(key, chunk);
224
- controller.enqueue(chunk);
225
- },
226
- flush() {
227
- store.complete(key);
228
- }
229
- });
230
- }
231
-
232
- export {
233
- StreamBufferStore,
234
- createBufferingTransform
235
- };
236
- //# sourceMappingURL=chunk-ELMG6OXT.js.map
@@ -1,126 +0,0 @@
1
- // src/file-types.ts
2
- var BINARY_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
3
- // Images
4
- "png",
5
- "jpg",
6
- "jpeg",
7
- "gif",
8
- "webp",
9
- "bmp",
10
- "ico",
11
- "avif",
12
- "apng",
13
- "heic",
14
- "heif",
15
- "tiff",
16
- "tif",
17
- "jxl",
18
- "cur",
19
- // Archives
20
- "zip",
21
- "gz",
22
- "tar",
23
- "tgz",
24
- "bz2",
25
- "xz",
26
- "7z",
27
- "rar",
28
- "zst",
29
- "lz4",
30
- // Audio / video / docs
31
- "mp3",
32
- "mp4",
33
- "m4a",
34
- "m4v",
35
- "mov",
36
- "avi",
37
- "mkv",
38
- "webm",
39
- "wav",
40
- "flac",
41
- "ogg",
42
- "oga",
43
- "ogv",
44
- "aac",
45
- "opus",
46
- "pdf",
47
- // Fonts
48
- "woff",
49
- "woff2",
50
- "ttf",
51
- "otf",
52
- "eot",
53
- // Native / packed
54
- "exe",
55
- "dll",
56
- "so",
57
- "dylib",
58
- "bin",
59
- "class",
60
- "jar",
61
- "wasm",
62
- // Databases
63
- "db",
64
- "sqlite",
65
- "sqlite3",
66
- "mdb",
67
- "lmdb",
68
- // Misc binary
69
- "pack",
70
- "idx",
71
- "psd",
72
- "ai",
73
- "sketch",
74
- "fig",
75
- "blend",
76
- "obj",
77
- "fbx",
78
- "pyc",
79
- "pyo",
80
- "pyd",
81
- // 3D / scene / point cloud (binary variants — `.usda` and `.gltf` are
82
- // text and intentionally NOT listed; users can still edit those)
83
- "usd",
84
- "usdc",
85
- "usdz",
86
- "glb",
87
- "stl",
88
- "ply",
89
- // Robotics logs
90
- "bag",
91
- "mcap",
92
- // ML weights / saved models / serialized arrays
93
- "pt",
94
- "pth",
95
- "ckpt",
96
- "safetensors",
97
- "onnx",
98
- "pb",
99
- "tflite",
100
- "mlmodel",
101
- "npy",
102
- "npz",
103
- "joblib",
104
- "pkl",
105
- // Tabular / scientific containers
106
- "parquet",
107
- "arrow",
108
- "feather",
109
- "h5",
110
- "hdf5",
111
- "nc",
112
- "tfrecord"
113
- ]);
114
- function isBinaryFilePath(path) {
115
- const dot = path.lastIndexOf(".");
116
- if (dot < 0) return false;
117
- const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
118
- if (dot < slash) return false;
119
- return BINARY_FILE_EXTENSIONS.has(path.slice(dot + 1).toLowerCase());
120
- }
121
-
122
- export {
123
- BINARY_FILE_EXTENSIONS,
124
- isBinaryFilePath
125
- };
126
- //# sourceMappingURL=chunk-HKX3MEJN.js.map
@@ -1,39 +0,0 @@
1
- // src/tech-stack-registry.ts
2
- var TECH_STACK_REGISTRY = {
3
- // Web (Vite-based) — share a bundled Vite template, so apps/api seeds them.
4
- "react-app": { id: "react-app", target: "web" },
5
- "threejs-game": { id: "threejs-game", target: "web" },
6
- "phaser-game": { id: "phaser-game", target: "web" },
7
- // Mobile (Metro/Expo) — agent-runtime owns the seed.
8
- "expo-app": { id: "expo-app", target: "mobile", metro: true, seedsOwnTemplate: true },
9
- "expo-three": { id: "expo-three", target: "mobile", metro: true, seedsOwnTemplate: true },
10
- "react-native": { id: "react-native", target: "mobile", metro: true, seedsOwnTemplate: true },
11
- // Data / scripting
12
- "python-data": { id: "python-data", target: "data", seedsOwnTemplate: true },
13
- // Native (full game engines)
14
- "unity-game": { id: "unity-game", target: "native", seedsOwnTemplate: true },
15
- // Bare / no-stack
16
- none: { id: "none", target: "none", seedsOwnTemplate: true }
17
- };
18
- function getStackEntry(techStackId) {
19
- if (!techStackId) return null;
20
- return TECH_STACK_REGISTRY[techStackId] ?? null;
21
- }
22
- function isMobileTechStack(techStackId) {
23
- return getStackEntry(techStackId)?.target === "mobile";
24
- }
25
- function usesMetroBundler(techStackId) {
26
- return getStackEntry(techStackId)?.metro === true;
27
- }
28
- function stackSeedsItself(techStackId) {
29
- return getStackEntry(techStackId)?.seedsOwnTemplate === true;
30
- }
31
-
32
- export {
33
- TECH_STACK_REGISTRY,
34
- getStackEntry,
35
- isMobileTechStack,
36
- usesMetroBundler,
37
- stackSeedsItself
38
- };
39
- //# sourceMappingURL=chunk-KTDDFWXM.js.map