sh3-core 0.24.0 → 0.25.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.
- package/dist/BrandSlot.svelte +62 -3
- package/dist/BrandSlot.test.js +52 -0
- package/dist/apps/types.d.ts +8 -0
- package/dist/artifact.d.ts +7 -0
- package/dist/build.d.ts +8 -0
- package/dist/build.js +17 -7
- package/dist/build.test.js +27 -1
- package/dist/layout/store.svelte.js +1 -1
- package/dist/overlays/presets.d.ts +17 -2
- package/dist/overlays/presets.js +28 -2
- package/dist/overlays/presets.test.js +29 -0
- package/dist/platform/localSidecar.d.ts +7 -0
- package/dist/platform/localSidecar.js +24 -0
- package/dist/platform/localSidecar.test.d.ts +1 -0
- package/dist/platform/localSidecar.test.js +39 -0
- package/dist/registry/installer.js +50 -10
- package/dist/registry/installer.test.d.ts +1 -0
- package/dist/registry/installer.test.js +146 -0
- package/dist/registry/types.d.ts +19 -0
- package/dist/runtime/runVerb.test.js +87 -0
- package/dist/sh3core-shard/folderActions.d.ts +15 -0
- package/dist/sh3core-shard/folderActions.js +109 -0
- package/dist/sh3core-shard/folderActions.test.d.ts +1 -0
- package/dist/sh3core-shard/folderActions.test.js +43 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +2 -0
- package/dist/shards/lifecycle.svelte.d.ts +8 -0
- package/dist/shards/lifecycle.svelte.js +17 -0
- package/dist/shell-shard/verbs/xfer.js +66 -4
- package/dist/shell-shard/verbs/xfer.test.js +74 -0
- package/dist/verbs/types.d.ts +49 -12
- package/dist/verbs/types.test.d.ts +1 -0
- package/dist/verbs/types.test.js +43 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -104,4 +104,78 @@ describe('xfer verb', () => {
|
|
|
104
104
|
expect(transferBetweenScopes).toHaveBeenCalledTimes(2);
|
|
105
105
|
expect(transferBetweenScopes).toHaveBeenCalledWith('proj-abc', 'notes', 'ideas/a.md', 'user-me', 'notes', 'ideas/a.md', expect.objectContaining({ delete: true }));
|
|
106
106
|
});
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Bug repro — xfer -R discards dst directory; dst with trailing slash should
|
|
109
|
+
// preserve it; substring-prefix filter false-matches.
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
it('-R preserves dst directory when transferring into a different folder', async () => {
|
|
112
|
+
// User's exact scenario: a single file under svg-designer goes into a
|
|
113
|
+
// mount subdirectory. Previously dst.path was discarded and the file
|
|
114
|
+
// landed at `mounts/sh3_dirt.svg`, which the mount resolver interpreted
|
|
115
|
+
// as `mountId=sh3_dirt.svg` → 500.
|
|
116
|
+
const transferBetweenScopes = vi.fn(async () => { });
|
|
117
|
+
const allDocs = [
|
|
118
|
+
{ shardId: 'svg-designer', path: 'sh3_dirt.svg', size: 1, lastModified: 0 },
|
|
119
|
+
];
|
|
120
|
+
const docs = makeDocs({
|
|
121
|
+
transferBetweenScopes,
|
|
122
|
+
listDocumentsIn: vi.fn(async () => allDocs),
|
|
123
|
+
});
|
|
124
|
+
const sh3 = makeSh3(personalScope);
|
|
125
|
+
const { ctx } = makeCtx(docs, sh3);
|
|
126
|
+
await xferVerb.run(ctx, ['-R', '-C', '@me:svg-designer/sh3_dirt.svg', 'mounts/square-survivor/']);
|
|
127
|
+
expect(transferBetweenScopes).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'svg-designer', 'sh3_dirt.svg', 'user-me', 'mounts', 'square-survivor/sh3_dirt.svg', expect.objectContaining({ delete: false }));
|
|
129
|
+
});
|
|
130
|
+
it('-R rebases doc paths under a folder prefix into the dst directory', async () => {
|
|
131
|
+
const transferBetweenScopes = vi.fn(async () => { });
|
|
132
|
+
const allDocs = [
|
|
133
|
+
{ shardId: 'notes', path: 'ideas/a.md', size: 1, lastModified: 0 },
|
|
134
|
+
{ shardId: 'notes', path: 'ideas/sub/b.md', size: 1, lastModified: 0 },
|
|
135
|
+
];
|
|
136
|
+
const docs = makeDocs({
|
|
137
|
+
transferBetweenScopes,
|
|
138
|
+
listDocumentsIn: vi.fn(async () => allDocs),
|
|
139
|
+
});
|
|
140
|
+
const sh3 = makeSh3(personalScope);
|
|
141
|
+
const { ctx } = makeCtx(docs, sh3);
|
|
142
|
+
await xferVerb.run(ctx, ['-R', '@me:notes/ideas', '@me:notes/archived/']);
|
|
143
|
+
expect(transferBetweenScopes).toHaveBeenCalledTimes(2);
|
|
144
|
+
expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'ideas/a.md', 'user-me', 'notes', 'archived/a.md', expect.anything());
|
|
145
|
+
expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'ideas/sub/b.md', 'user-me', 'notes', 'archived/sub/b.md', expect.anything());
|
|
146
|
+
});
|
|
147
|
+
it('-R filter respects folder boundary — does not match substring prefixes', async () => {
|
|
148
|
+
const transferBetweenScopes = vi.fn(async () => { });
|
|
149
|
+
const allDocs = [
|
|
150
|
+
{ shardId: 'notes', path: 'notes/draft.md', size: 1, lastModified: 0 },
|
|
151
|
+
{ shardId: 'notes', path: 'notesheet.md', size: 1, lastModified: 0 },
|
|
152
|
+
];
|
|
153
|
+
const docs = makeDocs({
|
|
154
|
+
transferBetweenScopes,
|
|
155
|
+
listDocumentsIn: vi.fn(async () => allDocs),
|
|
156
|
+
});
|
|
157
|
+
const sh3 = makeSh3(personalScope);
|
|
158
|
+
const { ctx } = makeCtx(docs, sh3);
|
|
159
|
+
await xferVerb.run(ctx, ['-R', '@me:notes/notes', '@me:notes/archived/']);
|
|
160
|
+
// Only notes/draft.md matches the folder prefix `notes`. notesheet.md
|
|
161
|
+
// shares the substring but is a sibling file, not a child.
|
|
162
|
+
expect(transferBetweenScopes).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'notes/draft.md', 'user-me', 'notes', 'archived/draft.md', expect.anything());
|
|
164
|
+
});
|
|
165
|
+
it('non-recursive: trailing-slash dst appends source filename', async () => {
|
|
166
|
+
const transferBetweenScopes = vi.fn(async () => { });
|
|
167
|
+
const docs = makeDocs({ transferBetweenScopes });
|
|
168
|
+
const sh3 = makeSh3(personalScope);
|
|
169
|
+
const { ctx } = makeCtx(docs, sh3);
|
|
170
|
+
await xferVerb.run(ctx, ['@me:notes/foo.md', '@me:notes/archived/']);
|
|
171
|
+
expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'foo.md', 'user-me', 'notes', 'archived/foo.md', expect.anything());
|
|
172
|
+
});
|
|
173
|
+
it('non-recursive: literal dst path is used verbatim (no trailing slash)', async () => {
|
|
174
|
+
const transferBetweenScopes = vi.fn(async () => { });
|
|
175
|
+
const docs = makeDocs({ transferBetweenScopes });
|
|
176
|
+
const sh3 = makeSh3(personalScope);
|
|
177
|
+
const { ctx } = makeCtx(docs, sh3);
|
|
178
|
+
await xferVerb.run(ctx, ['@me:notes/foo.md', '@me:notes/archived/renamed.md']);
|
|
179
|
+
expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'foo.md', 'user-me', 'notes', 'archived/renamed.md', expect.anything());
|
|
180
|
+
});
|
|
107
181
|
});
|
package/dist/verbs/types.d.ts
CHANGED
|
@@ -209,14 +209,14 @@ export interface VerbContext {
|
|
|
209
209
|
signal?: AbortSignal;
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
212
|
-
* Portable JSON Schema subset accepted by sh3-core for `Verb.schema.input
|
|
213
|
-
* Documented as the intersection of what
|
|
214
|
-
* tool-call APIs accept natively. sh3-core
|
|
215
|
-
* actual schema stays within this subset —
|
|
216
|
-
* `$ref`, or other Draft 2020-12 features
|
|
217
|
-
* risk. The type is intentionally
|
|
218
|
-
* and `items` so authors can express
|
|
219
|
-
* fighting the type system.
|
|
212
|
+
* Portable JSON Schema subset accepted by sh3-core for `Verb.schema.input`
|
|
213
|
+
* and `Verb.schema.output`. Documented as the intersection of what
|
|
214
|
+
* Anthropic, OpenAI, and Gemini tool-call APIs accept natively. sh3-core
|
|
215
|
+
* does NOT validate that the actual schema stays within this subset —
|
|
216
|
+
* authors who reach for `oneOf`, `$ref`, or other Draft 2020-12 features
|
|
217
|
+
* do so at their own portability risk. The type is intentionally
|
|
218
|
+
* `unknown`-permissive on `properties` and `items` so authors can express
|
|
219
|
+
* object/array shapes without fighting the type system.
|
|
220
220
|
*/
|
|
221
221
|
export interface PortableJSONSchema {
|
|
222
222
|
type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
|
|
@@ -229,14 +229,39 @@ export interface PortableJSONSchema {
|
|
|
229
229
|
required?: string[];
|
|
230
230
|
/** Item schema for `type: 'array'`. */
|
|
231
231
|
items?: PortableJSONSchema;
|
|
232
|
+
/**
|
|
233
|
+
* JSON Schema `format` hint. sh3-core blesses one value:
|
|
234
|
+
* - 'sh3-document' — runtime payload is an SH3 document handle
|
|
235
|
+
* ({ shardId: string; path: string }). Combine with type: 'object'.
|
|
236
|
+
* Other `format` values pass through untouched (portability with
|
|
237
|
+
* JSON Schema validators and tool-call APIs is preserved).
|
|
238
|
+
*/
|
|
239
|
+
format?: string;
|
|
232
240
|
}
|
|
233
241
|
/**
|
|
234
|
-
* Optional schema attached to a verb.
|
|
235
|
-
*
|
|
236
|
-
*
|
|
242
|
+
* Optional schema attached to a verb. `input` is consumed by sh3-ai's
|
|
243
|
+
* tool-call dispatcher and by sh3-pipeline's verb-adapter. `output`
|
|
244
|
+
* describes the shape of the verb's return value (the `result` field
|
|
245
|
+
* of `Sh3Api.runVerb`'s Promise resolution) and is consumed by
|
|
246
|
+
* sh3-pipeline to derive output ports.
|
|
237
247
|
*/
|
|
238
248
|
export interface VerbSchema {
|
|
239
249
|
input: PortableJSONSchema;
|
|
250
|
+
/**
|
|
251
|
+
* Shape of the verb's return value. When omitted, callers treat the
|
|
252
|
+
* verb as side-effect-only and read the scrollback instead.
|
|
253
|
+
*
|
|
254
|
+
* - {type:'object', properties:{…}} → result is an object whose
|
|
255
|
+
* own properties match the schema. Each top-level property is
|
|
256
|
+
* one logical output (one port for sh3-pipeline).
|
|
257
|
+
* - any other type → result IS that value.
|
|
258
|
+
*
|
|
259
|
+
* Document outputs are declared as
|
|
260
|
+
* { type: 'object', format: 'sh3-document',
|
|
261
|
+
* properties: { shardId: {type:'string'}, path: {type:'string'} } }
|
|
262
|
+
* or inlined inside a parent object's properties.
|
|
263
|
+
*/
|
|
264
|
+
output?: PortableJSONSchema;
|
|
240
265
|
}
|
|
241
266
|
export interface Verb {
|
|
242
267
|
name: string;
|
|
@@ -271,7 +296,19 @@ export interface Verb {
|
|
|
271
296
|
* whether to read it or fall back to `args[]`.
|
|
272
297
|
*/
|
|
273
298
|
schema?: VerbSchema;
|
|
274
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Returns the verb's structured result. The return value MUST match
|
|
301
|
+
* the shape declared by `schema.output` when both are present:
|
|
302
|
+
* - schema.output {type:'object', properties:{…}} → return object with those keys
|
|
303
|
+
* - schema.output primitive → return that primitive
|
|
304
|
+
* - schema.output undefined → return value is ignored
|
|
305
|
+
*
|
|
306
|
+
* Verbs that don't declare schema.output may return undefined; the
|
|
307
|
+
* Sh3Api.runVerb resolution surfaces it as `result: undefined`.
|
|
308
|
+
* Existing verbs that returned Promise<void> remain assignable —
|
|
309
|
+
* `void` is a subtype of `unknown` for return-type widening.
|
|
310
|
+
*/
|
|
311
|
+
run(ctx: VerbContext, args: string[]): Promise<unknown>;
|
|
275
312
|
}
|
|
276
313
|
export type Resolution = {
|
|
277
314
|
kind: 'local';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expectTypeOf } from 'vitest';
|
|
2
|
+
describe('VerbSchema with output', () => {
|
|
3
|
+
it('accepts output as PortableJSONSchema', () => {
|
|
4
|
+
const schema = {
|
|
5
|
+
input: { type: 'object', properties: { topic: { type: 'string' } } },
|
|
6
|
+
output: { type: 'object', properties: { answer: { type: 'string' } } },
|
|
7
|
+
};
|
|
8
|
+
expectTypeOf(schema.output).toEqualTypeOf();
|
|
9
|
+
});
|
|
10
|
+
it('accepts format on PortableJSONSchema', () => {
|
|
11
|
+
const docSchema = {
|
|
12
|
+
type: 'object',
|
|
13
|
+
format: 'sh3-document',
|
|
14
|
+
properties: {
|
|
15
|
+
shardId: { type: 'string' },
|
|
16
|
+
path: { type: 'string' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
expectTypeOf(docSchema.format).toEqualTypeOf();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe('Verb.run return type', () => {
|
|
23
|
+
it('accepts a verb returning Promise<unknown>', () => {
|
|
24
|
+
const v = {
|
|
25
|
+
name: 'demo',
|
|
26
|
+
summary: 's',
|
|
27
|
+
async run() {
|
|
28
|
+
return { answer: 'ok' };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
expectTypeOf(v.run).returns.toEqualTypeOf();
|
|
32
|
+
});
|
|
33
|
+
it('accepts a legacy verb returning Promise<void>', () => {
|
|
34
|
+
const v = {
|
|
35
|
+
name: 'legacy',
|
|
36
|
+
summary: 's',
|
|
37
|
+
async run() {
|
|
38
|
+
// returns nothing
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
expectTypeOf(v.run).returns.toEqualTypeOf();
|
|
42
|
+
});
|
|
43
|
+
});
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.25.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.25.1';
|