@ikenga/contract 0.3.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/README.md +38 -0
- package/dist/artifact.d.ts +1249 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +164 -0
- package/dist/artifact.js.map +1 -0
- package/dist/engine.d.ts +65 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +7 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/iyke.d.ts +21 -0
- package/dist/iyke.d.ts.map +1 -0
- package/dist/iyke.js +38 -0
- package/dist/iyke.js.map +1 -0
- package/dist/manifest.d.ts +917 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +158 -0
- package/dist/manifest.js.map +1 -0
- package/dist/rpc.d.ts +130 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +8 -0
- package/dist/rpc.js.map +1 -0
- package/dist/scopes.d.ts +11 -0
- package/dist/scopes.d.ts.map +1 -0
- package/dist/scopes.js +56 -0
- package/dist/scopes.js.map +1 -0
- package/package.json +67 -0
- package/schemas/artifact/v0.json +638 -0
- package/src/artifact-fixtures/ceo-overview.json +56 -0
- package/src/artifact-fixtures/cfo-daily.json +42 -0
- package/src/artifact-fixtures/hello-world.json +25 -0
- package/src/artifact.test.ts +44 -0
- package/src/artifact.ts +197 -0
- package/src/engine.ts +57 -0
- package/src/index.ts +9 -0
- package/src/iyke.ts +43 -0
- package/src/manifest.ts +185 -0
- package/src/rpc.ts +97 -0
- package/src/scopes.ts +71 -0
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Shell ↔ pkg RPC. Same envelope on both transports:
|
|
2
|
+
// - iframe pkgs: postMessage over MessageChannel
|
|
3
|
+
// - mounted pkgs: direct function call on the host bus
|
|
4
|
+
//
|
|
5
|
+
// Methods are namespaced by capability area. The kernel enforces scopes
|
|
6
|
+
// declared in the pkg manifest before dispatching.
|
|
7
|
+
|
|
8
|
+
export const CONTRACT_VERSION = 1 as const;
|
|
9
|
+
|
|
10
|
+
// ---------- Envelope ----------
|
|
11
|
+
|
|
12
|
+
export interface RpcRequest<TParams = unknown> {
|
|
13
|
+
v: typeof CONTRACT_VERSION;
|
|
14
|
+
id: string;
|
|
15
|
+
method: string;
|
|
16
|
+
params: TParams;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RpcResponseOk<TResult = unknown> {
|
|
20
|
+
v: typeof CONTRACT_VERSION;
|
|
21
|
+
id: string;
|
|
22
|
+
result: TResult;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RpcResponseErr {
|
|
26
|
+
v: typeof CONTRACT_VERSION;
|
|
27
|
+
id: string;
|
|
28
|
+
error: { code: RpcErrorCode; message: string; data?: unknown };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type RpcResponse<T = unknown> = RpcResponseOk<T> | RpcResponseErr;
|
|
32
|
+
export type RpcMessage = RpcRequest | RpcResponse;
|
|
33
|
+
|
|
34
|
+
export type RpcErrorCode =
|
|
35
|
+
| 'method_not_found'
|
|
36
|
+
| 'invalid_params'
|
|
37
|
+
| 'scope_denied'
|
|
38
|
+
| 'pkg_not_authenticated'
|
|
39
|
+
| 'shell_unavailable'
|
|
40
|
+
| 'internal_error';
|
|
41
|
+
|
|
42
|
+
// ---------- Notifications (one-way, pkg → shell or shell → pkg) ----------
|
|
43
|
+
|
|
44
|
+
export interface RpcNotification<TParams = unknown> {
|
|
45
|
+
v: typeof CONTRACT_VERSION;
|
|
46
|
+
notify: string;
|
|
47
|
+
params: TParams;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------- Method catalogue ----------
|
|
51
|
+
//
|
|
52
|
+
// Adding a method is a contract change. Removals require a deprecation
|
|
53
|
+
// period across two contract minor versions.
|
|
54
|
+
|
|
55
|
+
export interface RpcMethods {
|
|
56
|
+
// identity
|
|
57
|
+
'identity.whoami': { req: void; res: { user_id: string; tenant_id: string | null } };
|
|
58
|
+
|
|
59
|
+
// pkg → engine
|
|
60
|
+
'engine.start_session': {
|
|
61
|
+
req: { systemPrompt?: string; toolAllowList?: string[] };
|
|
62
|
+
res: { sessionId: string };
|
|
63
|
+
};
|
|
64
|
+
'engine.stream': {
|
|
65
|
+
req: { sessionId: string; input: string };
|
|
66
|
+
res: { ok: true }; // events delivered via 'engine.event' notifications
|
|
67
|
+
};
|
|
68
|
+
'engine.cancel': { req: { sessionId: string }; res: { ok: true } };
|
|
69
|
+
|
|
70
|
+
// shell chrome
|
|
71
|
+
'shell.notify': { req: { title: string; body?: string; level?: 'info' | 'warn' | 'error' }; res: void };
|
|
72
|
+
'shell.open_pane': {
|
|
73
|
+
req: { route?: string; pkg_id?: string; split?: 'horizontal' | 'vertical' };
|
|
74
|
+
res: { pane_id: string };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// tasks (capability: tasks:read / tasks:write)
|
|
78
|
+
'tasks.list': { req: { status?: string; owner?: string }; res: unknown[] };
|
|
79
|
+
'tasks.create': { req: { subject: string; description?: string }; res: { id: string } };
|
|
80
|
+
|
|
81
|
+
// email drafts
|
|
82
|
+
'email_drafts.list': { req: { status?: string }; res: unknown[] };
|
|
83
|
+
'email_drafts.create': { req: { subject: string; body_html: string; to: string[] }; res: { id: string } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Notifications: shell → pkg
|
|
87
|
+
export interface ShellToPkgNotifications {
|
|
88
|
+
'engine.event': { sessionId: string; event: import('./engine.js').EngineEvent };
|
|
89
|
+
'shell.theme_changed': { theme: 'light' | 'dark' };
|
|
90
|
+
'shell.pane_focused': { focused: boolean };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------- Helpers ----------
|
|
94
|
+
|
|
95
|
+
export type RpcMethodName = keyof RpcMethods;
|
|
96
|
+
export type RpcParams<M extends RpcMethodName> = RpcMethods[M]['req'];
|
|
97
|
+
export type RpcResult<M extends RpcMethodName> = RpcMethods[M]['res'];
|
package/src/scopes.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Capability scopes a pkg can request in its manifest. The kernel enforces
|
|
2
|
+
// these at the IPC boundary — scope-denied requests fail with
|
|
3
|
+
// `RpcErrorCode.scope_denied` before reaching the handler.
|
|
4
|
+
//
|
|
5
|
+
// Scope syntax: `<resource>:<action>` or `<resource>:<action>:<qualifier>`
|
|
6
|
+
//
|
|
7
|
+
// Pattern wildcards are allowed in qualifiers only:
|
|
8
|
+
// "fs:read:projects/*" ✓ (file under /projects/)
|
|
9
|
+
// "tasks:*" ✗ (action wildcards forbidden — be explicit)
|
|
10
|
+
|
|
11
|
+
export interface ScopeDef {
|
|
12
|
+
scope: string;
|
|
13
|
+
description: string;
|
|
14
|
+
/** Whether this scope requires explicit user consent at install time. */
|
|
15
|
+
sensitive?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SCOPE_CATALOGUE: ScopeDef[] = [
|
|
19
|
+
// identity
|
|
20
|
+
{ scope: 'identity:read', description: 'Read the current user / tenant identity.' },
|
|
21
|
+
|
|
22
|
+
// tasks
|
|
23
|
+
{ scope: 'tasks:read', description: 'List and read tasks.' },
|
|
24
|
+
{ scope: 'tasks:write', description: 'Create, update, complete tasks.' },
|
|
25
|
+
|
|
26
|
+
// contacts
|
|
27
|
+
{ scope: 'contacts:read', description: 'Read the contacts directory.' },
|
|
28
|
+
{ scope: 'contacts:write', description: 'Create or update contacts.', sensitive: true },
|
|
29
|
+
|
|
30
|
+
// email
|
|
31
|
+
{ scope: 'email_drafts:read', description: 'Read drafts in the email queue.' },
|
|
32
|
+
{ scope: 'email_drafts:write', description: 'Create or modify drafts.' },
|
|
33
|
+
{ scope: 'email:send', description: 'Send email through the shell delivery layer.', sensitive: true },
|
|
34
|
+
|
|
35
|
+
// social
|
|
36
|
+
{ scope: 'social:read', description: 'Read queued and posted social items.' },
|
|
37
|
+
{ scope: 'social:publish', description: 'Publish to connected social accounts.', sensitive: true },
|
|
38
|
+
|
|
39
|
+
// filesystem (shell-mediated)
|
|
40
|
+
{ scope: 'fs:read', description: 'Read files inside the shell-managed sandbox.' },
|
|
41
|
+
{ scope: 'fs:write', description: 'Write files inside the shell-managed sandbox.', sensitive: true },
|
|
42
|
+
|
|
43
|
+
// engine
|
|
44
|
+
{ scope: 'engine:invoke', description: 'Start sessions and stream from the active AI engine.' },
|
|
45
|
+
{ scope: 'engine:register_mcp', description: 'Register additional MCP servers with the engine.', sensitive: true },
|
|
46
|
+
|
|
47
|
+
// shell chrome
|
|
48
|
+
{ scope: 'shell:notify', description: 'Show notifications in the shell UI.' },
|
|
49
|
+
{ scope: 'shell:open_pane', description: 'Open or split panes in the shell layout.' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const SCOPE_INDEX = new Map(SCOPE_CATALOGUE.map((s) => [s.scope, s]));
|
|
53
|
+
|
|
54
|
+
export function isKnownScope(scope: string): boolean {
|
|
55
|
+
if (SCOPE_INDEX.has(scope)) return true;
|
|
56
|
+
// Allow qualified variants (`fs:read:projects/*`) only if the unqualified
|
|
57
|
+
// form is in the catalogue.
|
|
58
|
+
const parts = scope.split(':');
|
|
59
|
+
if (parts.length < 3) return false;
|
|
60
|
+
return SCOPE_INDEX.has(`${parts[0]}:${parts[1]}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isSensitiveScope(scope: string): boolean {
|
|
64
|
+
const def = SCOPE_INDEX.get(scope);
|
|
65
|
+
if (def) return !!def.sensitive;
|
|
66
|
+
const parts = scope.split(':');
|
|
67
|
+
if (parts.length < 3) return false;
|
|
68
|
+
return !!SCOPE_INDEX.get(`${parts[0]}:${parts[1]}`)?.sensitive;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type Scope = string;
|