@aotui/mobile-ai-native 0.1.0-alpha.0 → 0.1.0-alpha.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.
- package/GUIDE.md +79 -109
- package/LICENSE +201 -0
- package/README.md +106 -40
- package/dist/core/action/createActionRuntime.d.ts +7 -7
- package/dist/core/action/createActionRuntime.js +71 -18
- package/dist/core/action/defineAction.d.ts +4 -5
- package/dist/core/action/defineViewTypeTool.d.ts +5 -0
- package/dist/core/action/defineViewTypeTool.js +3 -0
- package/dist/core/effect/types.d.ts +21 -1
- package/dist/core/ref/ref-index.d.ts +1 -1
- package/dist/core/snapshot/createSnapshotBundle.d.ts +7 -4
- package/dist/core/snapshot/createSnapshotBundle.js +110 -3
- package/dist/core/snapshot/createSnapshotRegistry.d.ts +4 -0
- package/dist/core/snapshot/createSnapshotRegistry.js +52 -0
- package/dist/core/state/createStore.d.ts +1 -1
- package/dist/core/trace/createTraceStore.d.ts +2 -0
- package/dist/core/trace/createTraceStore.js +38 -0
- package/dist/core/trace/types.d.ts +21 -0
- package/dist/core/trace/types.js +1 -0
- package/dist/core/types.d.ts +61 -10
- package/dist/demo/inbox/InboxGUI.js +5 -3
- package/dist/demo/inbox/InboxTUI.d.ts +3 -1
- package/dist/demo/inbox/InboxTUI.js +75 -9
- package/dist/demo/inbox/actions.d.ts +3 -3
- package/dist/demo/inbox/actions.js +15 -4
- package/dist/demo/inbox/createInboxApp.d.ts +3 -6
- package/dist/demo/inbox/createInboxApp.js +11 -11
- package/dist/demo/inbox/effects.d.ts +4 -6
- package/dist/demo/inbox/effects.js +5 -0
- package/dist/demo/inbox/state.d.ts +3 -0
- package/dist/demo/inbox/state.js +16 -0
- package/dist/index.d.ts +25 -10
- package/dist/index.js +20 -10
- package/dist/projection/gui/AppProvider.d.ts +8 -12
- package/dist/projection/gui/AppProvider.js +77 -2
- package/dist/projection/gui/hooks.d.ts +2 -5
- package/dist/projection/gui/hooks.js +3 -12
- package/dist/projection/react/AppRuntimeProvider.d.ts +10 -0
- package/dist/projection/react/AppRuntimeProvider.js +15 -0
- package/dist/projection/react/createReactAppRuntime.d.ts +35 -0
- package/dist/projection/react/createReactAppRuntime.js +80 -0
- package/dist/projection/react/hooks.d.ts +8 -0
- package/dist/projection/react/hooks.js +18 -0
- package/dist/projection/tui/View.d.ts +8 -0
- package/dist/projection/tui/View.js +5 -0
- package/dist/projection/tui/createSnapshotAssembler.d.ts +2 -0
- package/dist/projection/tui/createSnapshotAssembler.js +14 -0
- package/dist/projection/tui/renderSnapshotDocument.d.ts +2 -0
- package/dist/projection/tui/renderSnapshotDocument.js +3 -0
- package/dist/projection/tui/renderTUI.d.ts +2 -2
- package/dist/projection/tui/renderTUI.js +15 -6
- package/dist/projection/tui/renderViewFragment.d.ts +10 -0
- package/dist/projection/tui/renderViewFragment.js +15 -0
- package/dist/ref/RefContext.d.ts +1 -1
- package/dist/ref/useArrayRef.js +1 -1
- package/dist/ref/useDataRef.js +1 -1
- package/dist/tool/createToolBridge.d.ts +4 -9
- package/dist/tool/createToolBridge.js +55 -11
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +8 -9
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { ActionDefinition } from "./defineAction";
|
|
2
|
-
import type {
|
|
1
|
+
import type { ActionDefinition } from "./defineAction.js";
|
|
2
|
+
import type { EffectMap } from "../effect/types.js";
|
|
3
|
+
import type { ActionResult, Store, ToolDefinition, TraceStore } from "../types.js";
|
|
3
4
|
export declare function createActionRuntime<State, Event>(config: {
|
|
4
5
|
store: Store<State, Event>;
|
|
5
6
|
actions: Array<ActionDefinition<State, Event, any>>;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}, input: any) => Promise<void> | void>;
|
|
7
|
+
traceStore?: TraceStore;
|
|
8
|
+
effects?: EffectMap<State, Event>;
|
|
9
|
+
getRelevantViewTypes?: () => readonly string[];
|
|
10
10
|
}): {
|
|
11
11
|
executeAction: (name: string, input: Record<string, unknown>) => Promise<ActionResult>;
|
|
12
|
-
listVisibleTools: () => ToolDefinition[];
|
|
12
|
+
listVisibleTools: (relevantViewTypes?: readonly string[]) => ToolDefinition[];
|
|
13
13
|
};
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
import { createTraceStore } from "../trace/createTraceStore.js";
|
|
1
2
|
export function createActionRuntime(config) {
|
|
2
3
|
const actionsByName = new Map(config.actions.map((action) => [action.name, action]));
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const traceStore = config.traceStore ?? createTraceStore();
|
|
5
|
+
function isActionRelevant(action) {
|
|
6
|
+
const scopedAction = action;
|
|
7
|
+
const relevantViewTypes = config.getRelevantViewTypes?.() ?? [];
|
|
8
|
+
if (!scopedAction.viewType) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return relevantViewTypes.includes(scopedAction.viewType);
|
|
12
|
+
}
|
|
13
|
+
function recordTrace(actionName, status, summary) {
|
|
14
|
+
traceStore.record({
|
|
15
|
+
actionName,
|
|
16
|
+
status,
|
|
17
|
+
summary,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
9
20
|
async function executeAction(name, input) {
|
|
10
21
|
const action = actionsByName.get(name);
|
|
11
22
|
if (!action) {
|
|
@@ -17,7 +28,8 @@ export function createActionRuntime(config) {
|
|
|
17
28
|
},
|
|
18
29
|
};
|
|
19
30
|
}
|
|
20
|
-
if (!action
|
|
31
|
+
if (!isActionRelevant(action) ||
|
|
32
|
+
!action.visibility(config.store.getState())) {
|
|
21
33
|
return {
|
|
22
34
|
success: false,
|
|
23
35
|
error: {
|
|
@@ -36,31 +48,72 @@ export function createActionRuntime(config) {
|
|
|
36
48
|
},
|
|
37
49
|
};
|
|
38
50
|
}
|
|
51
|
+
let latestSummary = `Started action ${name}`;
|
|
52
|
+
recordTrace(name, "started", latestSummary);
|
|
53
|
+
const trace = {
|
|
54
|
+
update(summary) {
|
|
55
|
+
latestSummary = summary;
|
|
56
|
+
recordTrace(name, "updated", summary);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
39
59
|
const ctx = {
|
|
40
60
|
getState: () => config.store.getState(),
|
|
41
61
|
emit: (event) => config.store.emit(event),
|
|
42
|
-
runEffect:
|
|
62
|
+
runEffect: (name, input) => {
|
|
43
63
|
const effect = config.effects?.[name];
|
|
44
64
|
if (!effect) {
|
|
45
|
-
return;
|
|
65
|
+
return Promise.resolve(undefined);
|
|
46
66
|
}
|
|
47
|
-
|
|
67
|
+
return Promise.resolve(effect({
|
|
48
68
|
getState: () => config.store.getState(),
|
|
49
69
|
emit: (event) => config.store.emit(event),
|
|
50
|
-
|
|
70
|
+
trace,
|
|
71
|
+
}, input));
|
|
51
72
|
},
|
|
52
73
|
trace,
|
|
53
74
|
};
|
|
54
|
-
|
|
75
|
+
try {
|
|
76
|
+
const result = await action.handler(ctx, parsed.data);
|
|
77
|
+
if (result.success) {
|
|
78
|
+
latestSummary = result.message ?? latestSummary;
|
|
79
|
+
recordTrace(name, "succeeded", latestSummary);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
latestSummary =
|
|
83
|
+
result.error?.message ?? result.message ?? `Action ${name} failed`;
|
|
84
|
+
recordTrace(name, "failed", latestSummary);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
latestSummary =
|
|
90
|
+
error instanceof Error ? error.message : `Action ${name} failed`;
|
|
91
|
+
recordTrace(name, "failed", latestSummary);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
55
94
|
}
|
|
56
|
-
function listVisibleTools() {
|
|
95
|
+
function listVisibleTools(relevantViewTypes = config.getRelevantViewTypes?.() ?? []) {
|
|
57
96
|
const state = config.store.getState();
|
|
97
|
+
const relevantViewTypeSet = new Set(relevantViewTypes);
|
|
58
98
|
return config.actions
|
|
59
|
-
.filter((action) =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
99
|
+
.filter((action) => {
|
|
100
|
+
const scopedAction = action;
|
|
101
|
+
const isViewTypeRelevant = !scopedAction.viewType ||
|
|
102
|
+
relevantViewTypeSet.has(scopedAction.viewType);
|
|
103
|
+
return isViewTypeRelevant && action.visibility(state);
|
|
104
|
+
})
|
|
105
|
+
.map((action) => {
|
|
106
|
+
const scopedAction = action;
|
|
107
|
+
return {
|
|
108
|
+
name: action.name,
|
|
109
|
+
description: action.description,
|
|
110
|
+
inputSchema: action.schema,
|
|
111
|
+
meta: action.meta ?? {},
|
|
112
|
+
...(scopedAction.viewType
|
|
113
|
+
? { viewType: scopedAction.viewType }
|
|
114
|
+
: {}),
|
|
115
|
+
};
|
|
116
|
+
});
|
|
64
117
|
}
|
|
65
118
|
return {
|
|
66
119
|
executeAction,
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import type { ZodType } from "zod";
|
|
2
|
-
import type {
|
|
2
|
+
import type { EffectResult } from "../effect/types.js";
|
|
3
|
+
import type { ActionResult, ToolMetadata } from "../types.js";
|
|
3
4
|
export type ActionContext<State, Event> = {
|
|
4
5
|
getState(): State;
|
|
5
6
|
emit(event: Event): void;
|
|
6
|
-
runEffect(name: string, input: unknown): Promise<
|
|
7
|
+
runEffect(name: string, input: unknown): Promise<EffectResult>;
|
|
7
8
|
trace: {
|
|
8
|
-
start(summary: string): void;
|
|
9
9
|
update(summary: string): void;
|
|
10
|
-
success(summary?: string): void;
|
|
11
|
-
fail(summary: string): void;
|
|
12
10
|
};
|
|
13
11
|
};
|
|
14
12
|
export type ActionDefinition<State, Event, Input> = {
|
|
15
13
|
name: string;
|
|
16
14
|
description: string;
|
|
17
15
|
schema: ZodType<Input>;
|
|
16
|
+
meta?: ToolMetadata;
|
|
18
17
|
visibility(state: State): boolean;
|
|
19
18
|
handler(ctx: ActionContext<State, Event>, input: Input): Promise<ActionResult> | ActionResult;
|
|
20
19
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ActionDefinition } from "./defineAction.js";
|
|
2
|
+
export type ViewTypeToolActionDefinition<State, Event, Input> = ActionDefinition<State, Event, Input> & {
|
|
3
|
+
readonly viewType: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function defineViewTypeTool<State, Event, Input>(tool: ViewTypeToolActionDefinition<State, Event, Input>): ViewTypeToolActionDefinition<State, Event, Input>;
|
|
@@ -1 +1,21 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type EffectTrace = {
|
|
2
|
+
update(summary: string): void;
|
|
3
|
+
};
|
|
4
|
+
export type EffectFailure = {
|
|
5
|
+
success: false;
|
|
6
|
+
error: {
|
|
7
|
+
code: string;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type EffectSuccess = {
|
|
12
|
+
success: true;
|
|
13
|
+
};
|
|
14
|
+
export type EffectResult = EffectSuccess | EffectFailure | void;
|
|
15
|
+
export type EffectContext<State, Event> = {
|
|
16
|
+
getState(): State;
|
|
17
|
+
emit(event: Event): void;
|
|
18
|
+
trace: EffectTrace;
|
|
19
|
+
};
|
|
20
|
+
export type EffectHandler<State, Event, Input> = (ctx: EffectContext<State, Event>, input: Input) => Promise<EffectResult> | EffectResult;
|
|
21
|
+
export type EffectMap<State, Event> = Record<string, EffectHandler<State, Event, any>>;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { SnapshotBundle, ToolDefinition, RefIndexEntry } from "../types";
|
|
1
|
+
import type { SnapshotBundle, ToolDefinition, RefIndexEntry, ViewFragment } from "../types.js";
|
|
2
|
+
export declare function hardenSnapshotBundle(snapshot: SnapshotBundle): SnapshotBundle;
|
|
2
3
|
export declare function createSnapshotBundle(input: {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
markup?: string;
|
|
5
|
+
views?: readonly ViewFragment[];
|
|
6
|
+
tui?: string;
|
|
7
|
+
refIndex: Readonly<Record<string, RefIndexEntry>>;
|
|
8
|
+
visibleTools: readonly ToolDefinition[];
|
|
6
9
|
}): SnapshotBundle;
|
|
@@ -1,12 +1,119 @@
|
|
|
1
1
|
let snapshotCounter = 0;
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const prototype = Object.getPrototypeOf(value);
|
|
7
|
+
return prototype === Object.prototype || prototype === null;
|
|
8
|
+
}
|
|
9
|
+
function cloneReadonlyValue(value, seen = new WeakMap()) {
|
|
10
|
+
if (value === null) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const valueType = typeof value;
|
|
14
|
+
if (valueType === "string" ||
|
|
15
|
+
valueType === "boolean") {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
if (valueType === "number") {
|
|
19
|
+
if (!Number.isFinite(value)) {
|
|
20
|
+
throw new Error("Snapshot values must be plain JSON-like data; received a non-finite number.");
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
if (valueType === "undefined" ||
|
|
25
|
+
valueType === "function" ||
|
|
26
|
+
valueType === "symbol" ||
|
|
27
|
+
valueType === "bigint") {
|
|
28
|
+
throw new Error("Snapshot values must be plain JSON-like data; received an unsupported primitive value.");
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
if (seen.has(value)) {
|
|
32
|
+
return seen.get(value);
|
|
33
|
+
}
|
|
34
|
+
const clone = [];
|
|
35
|
+
seen.set(value, clone);
|
|
36
|
+
for (const item of value) {
|
|
37
|
+
clone.push(cloneReadonlyValue(item, seen));
|
|
38
|
+
}
|
|
39
|
+
return Object.freeze(clone);
|
|
40
|
+
}
|
|
41
|
+
if (isPlainObject(value)) {
|
|
42
|
+
if (seen.has(value)) {
|
|
43
|
+
return seen.get(value);
|
|
44
|
+
}
|
|
45
|
+
const clone = Object.create(null);
|
|
46
|
+
seen.set(value, clone);
|
|
47
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
48
|
+
clone[key] = cloneReadonlyValue(nestedValue, seen);
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze(clone);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "object" && value !== null) {
|
|
53
|
+
throw new Error("Snapshot values must be plain JSON-like data; received a non-plain object.");
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function hardenRefIndex(refIndex) {
|
|
58
|
+
const clonedEntries = Object.create(null);
|
|
59
|
+
for (const [refId, entry] of Object.entries(refIndex)) {
|
|
60
|
+
clonedEntries[refId] = Object.freeze({
|
|
61
|
+
type: entry.type,
|
|
62
|
+
value: cloneReadonlyValue(entry.value),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return Object.freeze(clonedEntries);
|
|
66
|
+
}
|
|
67
|
+
function hardenVisibleTools(visibleTools) {
|
|
68
|
+
const clonedTools = visibleTools.map((tool) => Object.freeze({
|
|
69
|
+
name: tool.name,
|
|
70
|
+
description: tool.description,
|
|
71
|
+
inputSchema: tool.inputSchema,
|
|
72
|
+
meta: cloneReadonlyValue(tool.meta),
|
|
73
|
+
...(tool.viewType ? { viewType: tool.viewType } : {}),
|
|
74
|
+
}));
|
|
75
|
+
return Object.freeze(clonedTools);
|
|
76
|
+
}
|
|
77
|
+
function validateSnapshotCoherence(snapshot) {
|
|
78
|
+
if (snapshot.views.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const derivedMarkup = snapshot.views.map((view) => view.markup).join("");
|
|
82
|
+
if (snapshot.markup !== derivedMarkup) {
|
|
83
|
+
throw new Error("Snapshot markup must match the provided view fragments from the same render tick.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function hardenSnapshotBundle(snapshot) {
|
|
87
|
+
validateSnapshotCoherence(snapshot);
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
snapshotId: snapshot.snapshotId,
|
|
90
|
+
generatedAt: snapshot.generatedAt,
|
|
91
|
+
markup: snapshot.markup,
|
|
92
|
+
views: Object.freeze(snapshot.views.map((view) => Object.freeze({
|
|
93
|
+
id: view.id,
|
|
94
|
+
type: view.type,
|
|
95
|
+
name: view.name,
|
|
96
|
+
markup: view.markup,
|
|
97
|
+
}))),
|
|
98
|
+
tui: snapshot.tui,
|
|
99
|
+
refIndex: hardenRefIndex(snapshot.refIndex),
|
|
100
|
+
visibleTools: hardenVisibleTools(snapshot.visibleTools),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
2
103
|
export function createSnapshotBundle(input) {
|
|
3
104
|
const generatedAt = Date.now();
|
|
4
105
|
snapshotCounter += 1;
|
|
5
|
-
|
|
106
|
+
const views = input.views ?? [];
|
|
107
|
+
const derivedMarkup = views.length > 0 ? views.map((view) => view.markup).join("") : undefined;
|
|
108
|
+
const markup = input.markup ?? derivedMarkup ?? input.tui ?? "";
|
|
109
|
+
const tui = input.tui ?? markup;
|
|
110
|
+
return hardenSnapshotBundle({
|
|
6
111
|
snapshotId: `snap_${generatedAt}_${snapshotCounter}`,
|
|
7
112
|
generatedAt,
|
|
8
|
-
|
|
113
|
+
markup,
|
|
114
|
+
views,
|
|
115
|
+
tui,
|
|
9
116
|
refIndex: input.refIndex,
|
|
10
117
|
visibleTools: input.visibleTools,
|
|
11
|
-
};
|
|
118
|
+
});
|
|
12
119
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { hardenSnapshotBundle } from "./createSnapshotBundle.js";
|
|
2
|
+
function evictOldSnapshots(snapshots, maxEntries) {
|
|
3
|
+
while (snapshots.size > maxEntries) {
|
|
4
|
+
const oldestSnapshotId = snapshots.keys().next().value;
|
|
5
|
+
if (!oldestSnapshotId) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
snapshots.delete(oldestSnapshotId);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function createSnapshotRegistry(config) {
|
|
12
|
+
const maxEntries = Math.max(1, config?.maxEntries ?? 2);
|
|
13
|
+
const snapshots = new Map();
|
|
14
|
+
const seenSnapshotIds = new Set();
|
|
15
|
+
function createEntry(snapshot, status) {
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
snapshot,
|
|
18
|
+
status,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
create(snapshot) {
|
|
23
|
+
const hardenedSnapshot = hardenSnapshotBundle(snapshot);
|
|
24
|
+
if (seenSnapshotIds.has(hardenedSnapshot.snapshotId)) {
|
|
25
|
+
throw new Error(`Snapshot ${hardenedSnapshot.snapshotId} is already registered`);
|
|
26
|
+
}
|
|
27
|
+
seenSnapshotIds.add(hardenedSnapshot.snapshotId);
|
|
28
|
+
snapshots.set(hardenedSnapshot.snapshotId, createEntry(hardenedSnapshot, "active"));
|
|
29
|
+
evictOldSnapshots(snapshots, maxEntries);
|
|
30
|
+
return hardenedSnapshot;
|
|
31
|
+
},
|
|
32
|
+
lookup(snapshotId) {
|
|
33
|
+
return snapshots.get(snapshotId);
|
|
34
|
+
},
|
|
35
|
+
markStale(snapshotId) {
|
|
36
|
+
const entry = snapshots.get(snapshotId);
|
|
37
|
+
if (!entry) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
snapshots.set(snapshotId, createEntry(entry.snapshot, "stale"));
|
|
41
|
+
},
|
|
42
|
+
markAllStale() {
|
|
43
|
+
for (const snapshotId of snapshots.keys()) {
|
|
44
|
+
const entry = snapshots.get(snapshotId);
|
|
45
|
+
if (!entry || entry.status === "stale") {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
snapshots.set(snapshotId, createEntry(entry.snapshot, "stale"));
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function createTraceStore() {
|
|
2
|
+
let nextTraceId = 1;
|
|
3
|
+
let state = {
|
|
4
|
+
entries: [],
|
|
5
|
+
recent: null,
|
|
6
|
+
};
|
|
7
|
+
const listeners = new Set();
|
|
8
|
+
function notify() {
|
|
9
|
+
listeners.forEach((listener) => listener());
|
|
10
|
+
}
|
|
11
|
+
function record(entry) {
|
|
12
|
+
const record = {
|
|
13
|
+
id: `trace_${nextTraceId++}`,
|
|
14
|
+
actionName: entry.actionName,
|
|
15
|
+
status: entry.status,
|
|
16
|
+
summary: entry.summary,
|
|
17
|
+
recordedAt: Date.now(),
|
|
18
|
+
};
|
|
19
|
+
state = {
|
|
20
|
+
entries: [...state.entries, record],
|
|
21
|
+
recent: record,
|
|
22
|
+
};
|
|
23
|
+
notify();
|
|
24
|
+
return record;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
getState() {
|
|
28
|
+
return state;
|
|
29
|
+
},
|
|
30
|
+
subscribe(listener) {
|
|
31
|
+
listeners.add(listener);
|
|
32
|
+
return () => {
|
|
33
|
+
listeners.delete(listener);
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
record,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type TraceStatus = "started" | "updated" | "succeeded" | "failed";
|
|
2
|
+
export type TraceRecord = {
|
|
3
|
+
id: string;
|
|
4
|
+
actionName: string;
|
|
5
|
+
status: TraceStatus;
|
|
6
|
+
summary: string;
|
|
7
|
+
recordedAt: number;
|
|
8
|
+
};
|
|
9
|
+
export type TraceState = {
|
|
10
|
+
entries: TraceRecord[];
|
|
11
|
+
recent: TraceRecord | null;
|
|
12
|
+
};
|
|
13
|
+
export type TraceStore = {
|
|
14
|
+
getState(): TraceState;
|
|
15
|
+
subscribe(listener: () => void): () => void;
|
|
16
|
+
record(entry: {
|
|
17
|
+
actionName: string;
|
|
18
|
+
status: TraceStatus;
|
|
19
|
+
summary: string;
|
|
20
|
+
}): TraceRecord;
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,26 +1,77 @@
|
|
|
1
|
+
import type { ComponentChild } from "preact";
|
|
2
|
+
import type { ZodTypeAny } from "zod";
|
|
3
|
+
export type { TraceRecord, TraceState, TraceStatus, TraceStore, } from "./trace/types.js";
|
|
1
4
|
export type StateReducer<State, Event> = (state: State, event: Event) => State;
|
|
5
|
+
export type StoreListener = () => void;
|
|
6
|
+
export type StoreUnsubscribe = () => void;
|
|
2
7
|
export type Store<State, Event> = {
|
|
3
8
|
getState(): State;
|
|
4
9
|
emit(event: Event): void;
|
|
5
|
-
subscribe(listener:
|
|
10
|
+
subscribe(listener: StoreListener): StoreUnsubscribe;
|
|
6
11
|
};
|
|
7
12
|
export type RefIndexEntry = {
|
|
8
|
-
type: string;
|
|
9
|
-
value: unknown;
|
|
13
|
+
readonly type: string;
|
|
14
|
+
readonly value: unknown;
|
|
10
15
|
};
|
|
16
|
+
export type ToolMetadata = Readonly<Record<string, unknown>>;
|
|
11
17
|
export type ToolDefinition = {
|
|
12
|
-
name: string;
|
|
13
|
-
description: string;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly inputSchema: ZodTypeAny;
|
|
21
|
+
readonly meta: ToolMetadata;
|
|
22
|
+
readonly viewType?: string;
|
|
23
|
+
};
|
|
24
|
+
export type ViewTypeToolDefinition = ToolDefinition & {
|
|
25
|
+
readonly viewType: string;
|
|
26
|
+
};
|
|
27
|
+
export type ViewFragment = {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly type: string;
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly markup: string;
|
|
32
|
+
};
|
|
33
|
+
export type StaticViewCatalogEntry = {
|
|
34
|
+
readonly type: string;
|
|
35
|
+
readonly description: string;
|
|
36
|
+
readonly enterFrom?: readonly string[];
|
|
37
|
+
readonly actions: readonly string[];
|
|
38
|
+
};
|
|
39
|
+
export type MountedViewDescriptor<State = unknown> = {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly type: string;
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly render: (state: State) => ComponentChild;
|
|
44
|
+
};
|
|
45
|
+
export type SnapshotAssemblerInput<State = unknown> = {
|
|
46
|
+
readonly rootView: ViewFragment;
|
|
47
|
+
readonly mountedViews: readonly ViewFragment[];
|
|
48
|
+
readonly refIndex: Readonly<Record<string, RefIndexEntry>>;
|
|
49
|
+
readonly visibleTools: readonly ToolDefinition[];
|
|
50
|
+
readonly tui?: string;
|
|
14
51
|
};
|
|
15
52
|
export type SnapshotBundle = {
|
|
16
|
-
snapshotId: string;
|
|
17
|
-
generatedAt: number;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
53
|
+
readonly snapshotId: string;
|
|
54
|
+
readonly generatedAt: number;
|
|
55
|
+
readonly markup: string;
|
|
56
|
+
readonly views: readonly ViewFragment[];
|
|
57
|
+
readonly tui: string;
|
|
58
|
+
readonly refIndex: Readonly<Record<string, RefIndexEntry>>;
|
|
59
|
+
readonly visibleTools: readonly ToolDefinition[];
|
|
60
|
+
};
|
|
61
|
+
export type SnapshotStatus = "active" | "stale";
|
|
62
|
+
export type SnapshotRegistryEntry = {
|
|
63
|
+
readonly snapshot: SnapshotBundle;
|
|
64
|
+
readonly status: SnapshotStatus;
|
|
65
|
+
};
|
|
66
|
+
export type SnapshotRegistry = {
|
|
67
|
+
create(snapshot: SnapshotBundle): SnapshotBundle;
|
|
68
|
+
lookup(snapshotId: string): SnapshotRegistryEntry | undefined;
|
|
69
|
+
markStale(snapshotId: string): void;
|
|
70
|
+
markAllStale?(): void;
|
|
21
71
|
};
|
|
22
72
|
export type ActionResult<T = unknown> = {
|
|
23
73
|
success: boolean;
|
|
74
|
+
mutated?: boolean;
|
|
24
75
|
message?: string;
|
|
25
76
|
data?: T;
|
|
26
77
|
error?: {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
|
-
import {
|
|
3
|
+
import { useRuntimeState } from "../../projection/react/hooks.js";
|
|
4
4
|
export function InboxGUI() {
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const recentTrace = useRuntimeState((state) => state.shell.recentTrace ?? "");
|
|
6
|
+
const openedMessageId = useRuntimeState((state) => state.inbox.openedMessageId ?? "");
|
|
7
|
+
const items = useRuntimeState((state) => state.inbox.items);
|
|
8
|
+
return (_jsxs("gui-screen", { children: [_jsx("gui-trace", { children: recentTrace }), _jsx("gui-opened", { children: openedMessageId }), items.map((item) => (_jsx("gui-item", { children: item.subject }, item.id)))] }));
|
|
7
9
|
}
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ToolDefinition } from "../../core/types.js";
|
|
2
|
+
import type { InboxState } from "./state.js";
|
|
3
|
+
export declare function createInboxSnapshotBundle(state: InboxState, visibleTools: readonly ToolDefinition[]): import("../..").SnapshotBundle;
|