@moxxy/sdk 0.25.0 → 0.27.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.
- package/dist/channel.d.ts +13 -0
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +19 -0
- package/dist/channel.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/event-store.d.ts +5 -1
- package/dist/event-store.d.ts.map +1 -1
- package/dist/event-store.js +24 -1
- package/dist/event-store.js.map +1 -1
- package/dist/events.d.ts +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/first-party.d.ts +17 -0
- package/dist/first-party.d.ts.map +1 -0
- package/dist/first-party.js +19 -0
- package/dist/first-party.js.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/isolation.d.ts +14 -0
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +75 -0
- package/dist/isolation.js.map +1 -1
- package/dist/mode.d.ts +7 -0
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/plugin.d.ts +10 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/reflector.d.ts +58 -0
- package/dist/reflector.d.ts.map +1 -0
- package/dist/reflector.js +2 -0
- package/dist/reflector.js.map +1 -0
- package/dist/schemas.d.ts +215 -8
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +37 -0
- package/dist/schemas.js.map +1 -1
- package/dist/session-like.d.ts +120 -1
- package/dist/session-like.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/channel.ts +25 -0
- package/src/errors.ts +1 -0
- package/src/event-store.ts +16 -1
- package/src/events.ts +1 -0
- package/src/exit-after-pair.test.ts +32 -0
- package/src/first-party.test.ts +26 -0
- package/src/first-party.ts +19 -0
- package/src/index.ts +21 -4
- package/src/isolation-aggregate.test.ts +69 -0
- package/src/isolation.ts +67 -0
- package/src/mode.ts +7 -0
- package/src/plugin.ts +10 -1
- package/src/reflector.ts +61 -0
- package/src/schemas.ts +41 -0
- package/src/session-like.ts +114 -1
package/src/reflector.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { EventLogReader } from './log.js';
|
|
2
|
+
import type { SessionId, TurnId } from './ids.js';
|
|
3
|
+
import type { ServiceRegistry } from './services.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The learning-loop block — a swappable strategy that watches a finished turn
|
|
7
|
+
* and *proposes* (never silently writes) memory/skill improvements. It is the
|
|
8
|
+
* def-only sibling of the other single-active registries (compactor, cache
|
|
9
|
+
* strategy, …) but NULLABLE: core seeds NO floor, so reflection is entirely
|
|
10
|
+
* opt-in — a session with no registered reflector simply never reflects.
|
|
11
|
+
*
|
|
12
|
+
* The trust boundary is the "propose, don't write" contract. A reflector reads
|
|
13
|
+
* the turn's events and returns {@link ReflectionProposal}s; the driver that
|
|
14
|
+
* hosts it delivers those as a one-time nudge on the next provider call, phrased
|
|
15
|
+
* so the *model* may choose to call `memory_save` / `synthesize_skill` — which
|
|
16
|
+
* still go through the existing permission prompts. Nothing a reflector returns
|
|
17
|
+
* mutates memory or skills on its own.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The context handed to {@link ReflectorDef.reflect}. Scoped to one finished
|
|
22
|
+
* turn: `log.byTurn(turnId)` yields exactly that turn's events. `services`
|
|
23
|
+
* exposes the host's inter-plugin registry (e.g. `services.get('providers')`
|
|
24
|
+
* for a side-channel LLM pass); `signal` bounds the whole reflection (a timeout
|
|
25
|
+
* and/or session shutdown) so a slow provider can never wedge it.
|
|
26
|
+
*/
|
|
27
|
+
export interface ReflectContext {
|
|
28
|
+
readonly sessionId: SessionId;
|
|
29
|
+
readonly turnId: TurnId;
|
|
30
|
+
readonly cwd: string;
|
|
31
|
+
readonly log: EventLogReader;
|
|
32
|
+
readonly services: ServiceRegistry;
|
|
33
|
+
readonly signal: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One improvement a reflector suggests. `kind` picks the target surface
|
|
38
|
+
* (`'memory'` → a fact worth `memory_save`; `'skill'` → a repeated procedure
|
|
39
|
+
* worth `synthesize_skill`); `title` is a short label; `nudge` is a
|
|
40
|
+
* one-paragraph suggestion addressed to the assistant. A proposal is a HINT,
|
|
41
|
+
* not a command — the model decides whether to act, and any resulting write
|
|
42
|
+
* still hits its own permission prompt.
|
|
43
|
+
*/
|
|
44
|
+
export interface ReflectionProposal {
|
|
45
|
+
readonly kind: 'memory' | 'skill';
|
|
46
|
+
readonly title: string;
|
|
47
|
+
/** One-paragraph suggestion addressed to the assistant. */
|
|
48
|
+
readonly nudge: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A registered reflector backend. `reflect` inspects the just-finished turn and
|
|
53
|
+
* returns 0 or more proposals (an empty array = "nothing worth suggesting").
|
|
54
|
+
* It MUST be side-effect-free with respect to memory/skills — it only reads and
|
|
55
|
+
* proposes — and MUST honor `ctx.signal` (abort/timeout).
|
|
56
|
+
*/
|
|
57
|
+
export interface ReflectorDef {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly displayName?: string;
|
|
60
|
+
reflect(ctx: ReflectContext): Promise<ReadonlyArray<ReflectionProposal>>;
|
|
61
|
+
}
|
package/src/schemas.ts
CHANGED
|
@@ -16,6 +16,7 @@ const pluginKindSchema = z.enum([
|
|
|
16
16
|
'command',
|
|
17
17
|
'transcriber',
|
|
18
18
|
'synthesizer',
|
|
19
|
+
'reflector',
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
export const requirementSchema = z.object({
|
|
@@ -89,11 +90,51 @@ export const pluginManifestSchema = z.object({
|
|
|
89
90
|
* requirements may be authored; per-tool/per-transcriber/per-anything
|
|
90
91
|
* runtime declarations were removed in favor of static analysis.
|
|
91
92
|
*/
|
|
93
|
+
/**
|
|
94
|
+
* One field of a plugin's declarative setup step (`package.json#moxxy.setup`).
|
|
95
|
+
* Declarative-only so EVERY frontend (the init wizard, the TUI, desktop
|
|
96
|
+
* onboarding) can render it without executing plugin code:
|
|
97
|
+
* - `secret` values land in the VAULT (never plaintext config); the plugin's
|
|
98
|
+
* `options.<key>` gets a `${vault:<name>}` ref, resolved at boot.
|
|
99
|
+
* - other kinds land at `plugins.packages.<pkg>.options.<key>` in the user
|
|
100
|
+
* config through the shared schema-validated writer.
|
|
101
|
+
*/
|
|
102
|
+
export const pluginSetupFieldSchema = z.object({
|
|
103
|
+
key: z.string().min(1).regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, 'key must be an identifier'),
|
|
104
|
+
label: z.string().min(1),
|
|
105
|
+
description: z.string().optional(),
|
|
106
|
+
kind: z.enum(['secret', 'string', 'boolean', 'select']),
|
|
107
|
+
/** Vault entry name for `secret` fields. Default: `<PKG>_<KEY>` upper-snake. */
|
|
108
|
+
vaultKey: z.string().min(1).optional(),
|
|
109
|
+
/** Choices for `select` fields. */
|
|
110
|
+
options: z.array(z.string().min(1)).optional(),
|
|
111
|
+
/** Required fields block completion; optional ones may stay unset. */
|
|
112
|
+
required: z.boolean().optional(),
|
|
113
|
+
placeholder: z.string().optional(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A plugin's declarative configuration step, walked by `moxxy init` (and
|
|
118
|
+
* surfaced after an on-demand install). `required: true` means the plugin is
|
|
119
|
+
* left DISABLED until its required fields are provided — the author's way to
|
|
120
|
+
* say "this cannot work unconfigured".
|
|
121
|
+
*/
|
|
122
|
+
export const pluginSetupSchema = z.object({
|
|
123
|
+
title: z.string().min(1),
|
|
124
|
+
description: z.string().optional(),
|
|
125
|
+
required: z.boolean().optional(),
|
|
126
|
+
fields: z.array(pluginSetupFieldSchema).min(1),
|
|
127
|
+
});
|
|
128
|
+
|
|
92
129
|
export const moxxyPackageSchema = z.object({
|
|
93
130
|
plugin: pluginManifestSchema.optional(),
|
|
94
131
|
requirements: z.array(requirementSchema).optional(),
|
|
132
|
+
/** Declarative setup step users walk through in init / post-install. */
|
|
133
|
+
setup: pluginSetupSchema.optional(),
|
|
95
134
|
});
|
|
96
135
|
|
|
136
|
+
export type PluginSetupField = z.infer<typeof pluginSetupFieldSchema>;
|
|
137
|
+
export type PluginSetupSpec = z.infer<typeof pluginSetupSchema>;
|
|
97
138
|
export type SkillFrontmatterInput = z.infer<typeof skillFrontmatterSchema>;
|
|
98
139
|
export type PluginManifestInput = z.infer<typeof pluginManifestSchema>;
|
|
99
140
|
export type MoxxyPackageInput = z.infer<typeof moxxyPackageSchema>;
|
package/src/session-like.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { MoxxyEvent, TriggerOrigin, UserPromptAttachment } from './events.js';
|
|
2
2
|
import type { SessionId, TurnId } from './ids.js';
|
|
3
|
+
import type { CapabilitySpec } from './isolation.js';
|
|
3
4
|
import type { EventLogReader } from './log.js';
|
|
4
5
|
import type { ApprovalResolver, ModeBadge } from './mode.js';
|
|
5
6
|
import type { PermissionResolver } from './permission.js';
|
|
6
|
-
import type { ModelDescriptor } from './provider.js';
|
|
7
|
+
import type { ModelDescriptor, ProviderKeyValidation } from './provider.js';
|
|
8
|
+
import type { PluginSetupSpec } from './schemas.js';
|
|
7
9
|
import type { ToolCompactPresentation } from './tool.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -281,6 +283,9 @@ export interface InstallablePluginView {
|
|
|
281
283
|
readonly installSpec: string;
|
|
282
284
|
readonly kind?: string;
|
|
283
285
|
readonly startCommand?: string;
|
|
286
|
+
/** Registry contributions the package provides (category + name) — lets
|
|
287
|
+
* surfaces offer install-on-first-use for a missing capability. */
|
|
288
|
+
readonly provides?: ReadonlyArray<{ readonly category: string; readonly name: string }>;
|
|
284
289
|
}
|
|
285
290
|
|
|
286
291
|
/** One loaded plugin in {@link PluginsAdminView.loaded}, grouped by `kinds`. */
|
|
@@ -350,6 +355,100 @@ export interface PluginsAdminView {
|
|
|
350
355
|
* (`setActive`). Rejects an unknown category or an unregistered name.
|
|
351
356
|
*/
|
|
352
357
|
setCategoryDefault(category: string, name: string): Promise<void>;
|
|
358
|
+
/**
|
|
359
|
+
* Install a plugin (npm into `~/.moxxy/plugins`), persist its enable, and
|
|
360
|
+
* hot-reload the plugin host so its contributions register live. Accepts a
|
|
361
|
+
* catalog id, a package name, or a full npm spec. Resolves with the spec
|
|
362
|
+
* that installed plus the diff of contribution names that registered,
|
|
363
|
+
* keyed by kind (`tools`, `providers`, `modes`, …). Optional capability
|
|
364
|
+
* per the seam convention: a `RemoteSession` leaves it undefined and the
|
|
365
|
+
* picker falls back to printing the `moxxy plugins install` command.
|
|
366
|
+
*/
|
|
367
|
+
install?(idOrSpec: string): Promise<{
|
|
368
|
+
readonly installed: string;
|
|
369
|
+
readonly registered: Readonly<Partial<Record<string, ReadonlyArray<string>>>>;
|
|
370
|
+
/**
|
|
371
|
+
* Combined capability surface of the tools this install registered —
|
|
372
|
+
* the package's blast radius, so a channel can render post-install
|
|
373
|
+
* consent (third-party packages) or an info line (first-party). Absent
|
|
374
|
+
* when the install registered no tools or the host cannot introspect
|
|
375
|
+
* isolation specs.
|
|
376
|
+
*/
|
|
377
|
+
readonly capabilities?: {
|
|
378
|
+
/** Tools that declared an isolation spec. */
|
|
379
|
+
readonly declared: number;
|
|
380
|
+
/** Tools the install registered. */
|
|
381
|
+
readonly total: number;
|
|
382
|
+
/** Widest-wins union of the declared specs. */
|
|
383
|
+
readonly surface: CapabilitySpec;
|
|
384
|
+
/** Tools with NO declaration: their surface is unknown, not empty. */
|
|
385
|
+
readonly undeclaredTools?: ReadonlyArray<string>;
|
|
386
|
+
};
|
|
387
|
+
/** Present when the package declares a `moxxy.setup` step to complete. */
|
|
388
|
+
readonly needsSetup?: { readonly title: string; readonly required: boolean };
|
|
389
|
+
}>;
|
|
390
|
+
/**
|
|
391
|
+
* The package's declarative setup step (`moxxy.setup`), read from its
|
|
392
|
+
* installed package.json — plain data, safe to render anywhere. Null when
|
|
393
|
+
* absent. Powers the post-install dialog and `/setup`.
|
|
394
|
+
*/
|
|
395
|
+
setupSpec?(packageName: string): Promise<PluginSetupSpec | null>;
|
|
396
|
+
/**
|
|
397
|
+
* Persist collected setup values: secrets → vault + `${vault:NAME}` option
|
|
398
|
+
* ref; other kinds → `plugins.packages.<pkg>.options.<key>`. A complete
|
|
399
|
+
* required setup re-enables the package; an incomplete one disables it
|
|
400
|
+
* (mirrors the init wizard). Optional capability per the seam convention.
|
|
401
|
+
*/
|
|
402
|
+
applySetup?(
|
|
403
|
+
packageName: string,
|
|
404
|
+
values: Readonly<Record<string, string | boolean>>,
|
|
405
|
+
): Promise<{ readonly complete: boolean; readonly missing: ReadonlyArray<string> }>;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** IO a channel supplies to drive an interactive OAuth flow inside its own
|
|
409
|
+
* UI — mirrors `ProviderAuthContext` minus vault/headless (the host adds
|
|
410
|
+
* those). `write` receives the flow's progress lines (sign-in URL, status);
|
|
411
|
+
* `prompt` renders a single-line input (paste-back codes), masked for
|
|
412
|
+
* secrets. */
|
|
413
|
+
export interface ProviderConnectIo {
|
|
414
|
+
readonly write: (chunk: string) => void;
|
|
415
|
+
readonly prompt?: (question: string, opts?: { readonly mask?: boolean }) => Promise<string>;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The slice of provider onboarding a channel needs to connect a provider
|
|
420
|
+
* WITHOUT leaving the session: install it if it's catalog-only, collect +
|
|
421
|
+
* validate + store an API key, or drive an OAuth sign-in. Present on a local
|
|
422
|
+
* Session (wired by the CLI, which owns vault + catalog); a `RemoteSession`
|
|
423
|
+
* leaves {@link SessionLike.providerSetup} undefined and the UI falls back
|
|
424
|
+
* to pointing at `moxxy init` / `moxxy login`.
|
|
425
|
+
*/
|
|
426
|
+
export interface ProviderSetupView {
|
|
427
|
+
/**
|
|
428
|
+
* How this provider authenticates: from its registered def, else the
|
|
429
|
+
* first-party catalog; null when the id is unknown to both.
|
|
430
|
+
*/
|
|
431
|
+
authKind(providerId: string): 'apiKey' | 'oauth' | 'none' | null;
|
|
432
|
+
/**
|
|
433
|
+
* Ensure the provider's package is registered — a catalog-only provider is
|
|
434
|
+
* npm-installed + enabled + hot-reloaded. Resolves false when the provider
|
|
435
|
+
* still isn't registered afterwards (unknown id, bad install).
|
|
436
|
+
*/
|
|
437
|
+
ensureInstalled(providerId: string): Promise<boolean>;
|
|
438
|
+
/** Validate a key against the provider's own `validateKey` (if it has one). */
|
|
439
|
+
testKey(providerId: string, key: string): Promise<ProviderKeyValidation>;
|
|
440
|
+
/** Store the key in the vault under the provider's canonical key name and
|
|
441
|
+
* mark the provider ready for this session. */
|
|
442
|
+
saveKey(providerId: string, key: string): Promise<void>;
|
|
443
|
+
/**
|
|
444
|
+
* Drive the provider's OAuth flow. `io` routes the flow's output/prompts
|
|
445
|
+
* into the calling channel's UI; when omitted the host's default terminal
|
|
446
|
+
* prompting applies (the clack path `moxxy init`/`moxxy login` use).
|
|
447
|
+
*/
|
|
448
|
+
loginOAuth(
|
|
449
|
+
providerId: string,
|
|
450
|
+
io?: ProviderConnectIo,
|
|
451
|
+
): Promise<{ readonly accountId?: string | null; readonly expiresAt?: number }>;
|
|
353
452
|
}
|
|
354
453
|
|
|
355
454
|
/**
|
|
@@ -406,4 +505,18 @@ export interface SessionLike {
|
|
|
406
505
|
workflows?: WorkflowsView;
|
|
407
506
|
/** Plugin-management slice backing the `/plugins` picker. */
|
|
408
507
|
pluginsAdmin?: PluginsAdminView;
|
|
508
|
+
/** Provider onboarding slice backing in-channel connect (key entry / OAuth). */
|
|
509
|
+
providerSetup?: ProviderSetupView;
|
|
510
|
+
/**
|
|
511
|
+
* Re-read the merged config from disk and live-apply the safe subset —
|
|
512
|
+
* backs the TUI /settings panel's write-then-apply. Structurally matches
|
|
513
|
+
* @moxxy/config's ConfigApplyResult (the SDK stays config-free). Absent on
|
|
514
|
+
* a RemoteSession: writes still land, but apply waits for a restart.
|
|
515
|
+
*/
|
|
516
|
+
configAdmin?: {
|
|
517
|
+
apply(): Promise<{
|
|
518
|
+
readonly applied: ReadonlyArray<string>;
|
|
519
|
+
readonly pending: ReadonlyArray<string>;
|
|
520
|
+
}>;
|
|
521
|
+
};
|
|
409
522
|
}
|