narraeon 0.1.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.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +164 -0
  3. package/README.zh-CN.md +164 -0
  4. package/dist/node/cli/NarraeonCli.js +163 -0
  5. package/dist/node/cli/main.js +38 -0
  6. package/dist/node/protocol/appPreferences.js +5 -0
  7. package/dist/node/protocol/contentTree.js +14 -0
  8. package/dist/node/protocol/modelConnections.js +110 -0
  9. package/dist/node/protocol/playerViews.js +1 -0
  10. package/dist/node/protocol/v1.js +278 -0
  11. package/dist/node/runtime/V1Runtime.js +625 -0
  12. package/dist/node/runtime/artifact/FileNativeArtifactStore.js +1914 -0
  13. package/dist/node/runtime/config/AppPreferencesStore.js +92 -0
  14. package/dist/node/runtime/config/appPaths.js +19 -0
  15. package/dist/node/runtime/content/ContentLimits.js +1 -0
  16. package/dist/node/runtime/content/ContentTreeFile.js +1 -0
  17. package/dist/node/runtime/content/ContentWorkspace.js +601 -0
  18. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +721 -0
  19. package/dist/node/runtime/content/FileNativeContentTree.js +512 -0
  20. package/dist/node/runtime/extension/FrontendExtensionBundle.js +229 -0
  21. package/dist/node/runtime/extension/PlayerViewPanelProjector.js +121 -0
  22. package/dist/node/runtime/model/AiFailureLog.js +332 -0
  23. package/dist/node/runtime/model/FileNativeModelAdapters.js +1246 -0
  24. package/dist/node/runtime/model/ModelConnectionStore.js +378 -0
  25. package/dist/node/runtime/model/ModelHost.js +89 -0
  26. package/dist/node/runtime/model/ModelHostStream.js +367 -0
  27. package/dist/node/runtime/model/ProviderExchangeState.js +1 -0
  28. package/dist/node/runtime/model/ProviderStream.js +90 -0
  29. package/dist/node/runtime/model/SettingAuthorStream.js +118 -0
  30. package/dist/node/runtime/play/FileNativeContinuityCorrection.js +304 -0
  31. package/dist/node/runtime/play/FileNativePlayPresetStore.js +1856 -0
  32. package/dist/node/runtime/play/PlayCallChain.js +1280 -0
  33. package/dist/node/runtime/play/PlayDocumentTools.js +1121 -0
  34. package/dist/node/runtime/play/PlayFollowupRequests.js +183 -0
  35. package/dist/node/runtime/play/PlayPresetWorkbench.js +185 -0
  36. package/dist/node/runtime/prompt/FileNativePromptCompiler.js +1425 -0
  37. package/dist/node/runtime/prompt/FileNativeToolRegistry.js +192 -0
  38. package/dist/node/runtime/prompt/MaterialSelection.js +95 -0
  39. package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +1726 -0
  40. package/dist/node/runtime/world/FileNativeWorldStore.js +2028 -0
  41. package/dist/node/runtime/world/PlayerViewRenderer.js +322 -0
  42. package/dist/node/runtime/world/WorldDocumentStore.js +2808 -0
  43. package/dist/node/runtime/world/WorldOperationCoordinator.js +490 -0
  44. package/dist/node/runtime/world/WorldStateRevision.js +75 -0
  45. package/dist/node/server/createRuntime.js +6 -0
  46. package/dist/node/server/createServer.js +218 -0
  47. package/dist/node/server/main.js +25 -0
  48. package/dist/node/server/startWebServer.js +28 -0
  49. package/dist/node/shared/default-play-prompts.js +85 -0
  50. package/dist/node/shared/default-preset-host.js +538 -0
  51. package/dist/node/shared/default-setting-improvement-prompt.js +77 -0
  52. package/dist/node/shared/first-party-action-choices.js +164 -0
  53. package/dist/node/shared/first-party-generic-panels.js +105 -0
  54. package/dist/node/shared/first-party-play-preset-templates.js +41 -0
  55. package/dist/node/shared/first-party-player-view.js +179 -0
  56. package/dist/node/shared/play-preset-prompt-roles.js +4 -0
  57. package/dist/web/assets/index-CRGTZ9Ak.css +1 -0
  58. package/dist/web/assets/index-DDx1itzT.js +1192 -0
  59. package/dist/web/index.html +14 -0
  60. package/package.json +86 -0
@@ -0,0 +1,183 @@
1
+ import { errorDescription, } from "../model/AiFailureLog.js";
2
+ /**
3
+ * Run every follow-up declared by the frozen play preset exactly once after the
4
+ * main model exchange has fully settled.
5
+ *
6
+ * Each request is `prefix + its own prompt` — the prompt replaces rather than
7
+ * appends, so all requests share one identical stable prefix (the main chain
8
+ * itself) and stay invisible to each other. Results only reach the artifact
9
+ * store; nothing here is written back into the chain transcript, so the next
10
+ * player message follows the narrative directly.
11
+ *
12
+ * Follow-ups run strictly after Authority has accepted the exchange. A failure
13
+ * here cannot damage committed world state: it is recorded, the remaining
14
+ * requests still run, and the chain returns to the player either way.
15
+ */
16
+ export async function runPlayFollowupRequests(input) {
17
+ if (input.followups.length === 0)
18
+ return { outcomes: [] };
19
+ await input.artifacts.beginOperation(input.context);
20
+ await input.artifacts.markCoreCommitted(input.context, input.head);
21
+ await input.artifacts.beginExtension(input.context);
22
+ const prefix = structuredClone(input.prefix);
23
+ const outcomes = [];
24
+ const completed = [];
25
+ let failure;
26
+ for (const followup of input.followups) {
27
+ if (input.signal?.aborted === true) {
28
+ failure ??= "The follow-up request was cancelled.";
29
+ break;
30
+ }
31
+ const outcome = await runOne(input, followup, prefix);
32
+ outcomes.push(outcome);
33
+ input.observer?.onOutcome?.(outcome);
34
+ if (outcome.failure === undefined)
35
+ completed.push(followup.id);
36
+ else
37
+ failure ??= outcome.failure;
38
+ }
39
+ const extension = failure === undefined
40
+ ? await input.artifacts.completeExtension(input.context.operationId, completed)
41
+ : await input.artifacts.failExtension(input.context.operationId, "recovery_required", failure, completed);
42
+ return { outcomes, extension };
43
+ }
44
+ async function runOne(input, followup, prefix) {
45
+ const outcome = {
46
+ id: followup.id,
47
+ displayName: followup.displayName,
48
+ text: "",
49
+ toolCalls: [],
50
+ };
51
+ const requestContext = {
52
+ ...input.context,
53
+ requestId: followup.id,
54
+ requestAttempt: 1,
55
+ maxArtifactBytes: followup.maxArtifactBytes,
56
+ declarations: structuredClone(followup.artifacts),
57
+ };
58
+ let responseDiagnostics;
59
+ try {
60
+ await input.artifacts.beginRequestAttempt(requestContext);
61
+ const response = await input.modelHost.exchange({
62
+ bootstrap: structuredClone(input.bootstrap),
63
+ tools: structuredClone(followup.tools),
64
+ toolUniverse: structuredClone(followup.tools),
65
+ allowedTools: [...followup.allowedTools],
66
+ toolStrategy: input.toolStrategy,
67
+ // The prompt replaces the previous followup's prompt instead of being
68
+ // appended after it: every request sees exactly the main-chain prefix.
69
+ appended: [
70
+ ...structuredClone(prefix),
71
+ {
72
+ kind: "prompt_delta",
73
+ logicalMessages: structuredClone(followup.logicalMessages),
74
+ },
75
+ ],
76
+ requestId: followup.id,
77
+ operationId: input.context.operationId,
78
+ requestAttempt: 1,
79
+ exchange: 1,
80
+ maxOutputTokens: input.maxOutputTokens,
81
+ });
82
+ responseDiagnostics = response.diagnostics;
83
+ if (response.diagnostics !== undefined)
84
+ await input.failureLog?.recordExchangeIfActive(response.diagnostics);
85
+ outcome.text = response.text ?? "";
86
+ if (response.reasoningContent !== undefined)
87
+ outcome.reasoning = response.reasoningContent;
88
+ if (response.usage !== undefined)
89
+ outcome.usage = {
90
+ inputTokens: response.usage.inputTokens,
91
+ outputTokens: response.usage.outputTokens,
92
+ };
93
+ // One request, one pass: tool results are recorded for the trace but never
94
+ // fed back for another exchange.
95
+ for (const call of response.toolCalls ?? []) {
96
+ const result = await executeArtifactCall(input, requestContext, call);
97
+ outcome.toolCalls.push({
98
+ callId: call.id,
99
+ name: call.name,
100
+ arguments: structuredClone(call.arguments),
101
+ ok: result.ok,
102
+ markdown: result.markdown,
103
+ });
104
+ }
105
+ const failedTools = outcome.toolCalls.filter(({ ok }) => !ok);
106
+ if (failedTools.length > 0 && response.diagnostics !== undefined)
107
+ await input.failureLog?.recordFailure({
108
+ exchange: response.diagnostics,
109
+ failures: [
110
+ {
111
+ kind: "tool_execution",
112
+ message: `The artifact tool for follow-up request ${followup.id} was rejected.`,
113
+ details: { calls: structuredClone(failedTools) },
114
+ },
115
+ ],
116
+ });
117
+ const missing = followup.artifacts
118
+ .filter(({ required }) => required)
119
+ .filter(({ name }) => !outcome.toolCalls.some(({ ok, arguments: args }) => ok && args?.output === name))
120
+ .map(({ name }) => name);
121
+ if (missing.length > 0)
122
+ outcome.failure = `Follow-up request ${followup.id} did not submit required artifacts: ${missing.join(", ")}`;
123
+ if (outcome.failure !== undefined && response.diagnostics !== undefined)
124
+ await input.failureLog?.recordFailure({
125
+ exchange: response.diagnostics,
126
+ failures: [
127
+ {
128
+ kind: "format_validation",
129
+ message: outcome.failure,
130
+ details: { missing },
131
+ },
132
+ ],
133
+ });
134
+ else if (failedTools.length === 0 && response.diagnostics !== undefined)
135
+ await input.failureLog?.resolve({
136
+ exchange: response.diagnostics,
137
+ message: `Follow-up request ${followup.id} recovered during a later model exchange.`,
138
+ });
139
+ }
140
+ catch (error) {
141
+ outcome.failure =
142
+ error instanceof Error
143
+ ? error.message
144
+ : `Follow-up request ${followup.id} failed.`;
145
+ if (responseDiagnostics !== undefined)
146
+ await input.failureLog?.recordFailure({
147
+ exchange: responseDiagnostics,
148
+ failures: [
149
+ {
150
+ kind: "runtime_post_processing",
151
+ message: outcome.failure,
152
+ error: errorDescription(error),
153
+ },
154
+ ],
155
+ });
156
+ }
157
+ return outcome;
158
+ }
159
+ async function executeArtifactCall(input, context, call) {
160
+ if (call.name !== "artifact_emit" && call.name !== "artifact_clear")
161
+ return {
162
+ ok: false,
163
+ markdown: `# Runtime tool rejected\n\nThe follow-up request did not provide ${call.name}.`,
164
+ };
165
+ const args = call.arguments;
166
+ if (typeof args !== "object" ||
167
+ args === null ||
168
+ Array.isArray(args) ||
169
+ typeof args.output !== "string")
170
+ return {
171
+ ok: false,
172
+ markdown: "# Runtime argument error\n\nThe artifact tool requires an output name.",
173
+ };
174
+ const output = args.output;
175
+ return call.name === "artifact_emit"
176
+ ? input.artifacts.emit({
177
+ context,
178
+ output,
179
+ payload: args.payload,
180
+ toolCallId: call.id,
181
+ })
182
+ : input.artifacts.clear({ context, output, toolCallId: call.id });
183
+ }
@@ -0,0 +1,185 @@
1
+ import { parsePlayPresetRegexAsset, toPlayPresetStructuredEditor, } from "./FileNativePlayPresetStore.js";
2
+ /**
3
+ * Read-only workbench seam. It resolves the same frozen file-native binding
4
+ * used by PromptCompiler and FrontendExtensionBundle, but never calls a model,
5
+ * writes a world, or executes author JavaScript.
6
+ */
7
+ export function buildPlayPresetWorkbenchSnapshot(binding) {
8
+ const scriptsEnabled = binding.scriptsEnabled !== false;
9
+ const structure = toPlayPresetStructuredEditor(binding.definition);
10
+ const staticErrors = [];
11
+ const artifactPreviews = [];
12
+ for (const followup of binding.definition.followups)
13
+ for (const declaration of followup.artifacts) {
14
+ const diagnostics = [];
15
+ let regex = [];
16
+ if (declaration.regex !== undefined) {
17
+ try {
18
+ const source = binding.files[declaration.regex];
19
+ if (source === undefined)
20
+ throw new Error("The regex resource does not exist");
21
+ regex = parsePlayPresetRegexAsset(source, declaration.regex);
22
+ }
23
+ catch (error) {
24
+ const message = error instanceof Error
25
+ ? error.message
26
+ : "The regex resource is invalid";
27
+ diagnostics.push(message);
28
+ staticErrors.push({
29
+ code: "regex_invalid",
30
+ message,
31
+ location: declaration.regex,
32
+ });
33
+ }
34
+ }
35
+ const rawPayload = samplePayload(declaration.payloadContract, declaration.contentType);
36
+ const rawText = declaration.contentType === "application/json"
37
+ ? JSON.stringify(rawPayload, null, 2)
38
+ : declaration.contentType === "text/markdown"
39
+ ? "# Example follow-up artifact\n\nThis is a local workbench preview sample."
40
+ : declaration.contentType === "text/html"
41
+ ? "<p>This is a local HTML workbench sample.</p>"
42
+ : "This is a local text workbench sample.";
43
+ let renderer;
44
+ if (declaration.renderer !== undefined) {
45
+ const source = binding.files[declaration.renderer];
46
+ if (source === undefined) {
47
+ const message = `Renderer resource does not exist: ${declaration.renderer}`;
48
+ diagnostics.push(message);
49
+ staticErrors.push({
50
+ code: "renderer_missing",
51
+ message,
52
+ location: declaration.renderer,
53
+ });
54
+ }
55
+ else {
56
+ const scriptSources = (declaration.scripts ?? []).flatMap((path) => {
57
+ const script = binding.files[path];
58
+ if (script === undefined) {
59
+ diagnostics.push(`Script resource does not exist: ${path}`);
60
+ staticErrors.push({
61
+ code: "script_missing",
62
+ message: `Script resource does not exist: ${path}`,
63
+ location: path,
64
+ });
65
+ return [];
66
+ }
67
+ return [script];
68
+ });
69
+ const assetSources = (declaration.assets ?? []).flatMap((path) => {
70
+ const asset = binding.files[path];
71
+ if (asset === undefined) {
72
+ diagnostics.push(`Asset resource does not exist: ${path}`);
73
+ staticErrors.push({
74
+ code: "asset_missing",
75
+ message: `Asset resource does not exist: ${path}`,
76
+ location: path,
77
+ });
78
+ return [];
79
+ }
80
+ return [{ id: path, source: asset }];
81
+ });
82
+ renderer = {
83
+ mode: declaration.rendererMode ?? "document",
84
+ ...(declaration.rendererRevision === undefined
85
+ ? {}
86
+ : { revision: declaration.rendererRevision }),
87
+ document: source,
88
+ scripts: scriptsEnabled ? scriptSources : [],
89
+ assets: assetSources,
90
+ trustedLocalCode: scriptsEnabled &&
91
+ (declaration.contentType === "text/html" ||
92
+ (declaration.scripts?.length ?? 0) > 0),
93
+ };
94
+ }
95
+ }
96
+ artifactPreviews.push({
97
+ requestId: followup.id,
98
+ output: declaration.name,
99
+ declaration: structuredClone(declaration),
100
+ rawPayload,
101
+ rawText,
102
+ regex,
103
+ ...(renderer === undefined ? {} : { renderer }),
104
+ activeProjection: {
105
+ status: "active",
106
+ channel: declaration.channel,
107
+ ...(declaration.key === undefined ? {} : { key: declaration.key }),
108
+ strategy: declaration.strategy,
109
+ save: declaration.save,
110
+ },
111
+ clear: {
112
+ supported: true,
113
+ invalidation: declaration.invalidation,
114
+ description: declaration.invalidation === "never"
115
+ ? "Declared as never; the preview displays the policy without clearing automatically."
116
+ : "clear is an explicit projection event and does not modify the raw payload.",
117
+ },
118
+ simulation: simulateArtifactProjection(declaration),
119
+ diagnostics,
120
+ });
121
+ }
122
+ return {
123
+ id: binding.id,
124
+ name: binding.name,
125
+ revision: binding.revision,
126
+ structure,
127
+ artifactPreviews,
128
+ staticErrors,
129
+ trustedLocalCode: artifactPreviews.some(({ renderer }) => renderer?.trustedLocalCode === true),
130
+ scriptsEnabled,
131
+ };
132
+ }
133
+ /**
134
+ * Deterministic, local-only projection preview. It mirrors the production
135
+ * lifecycle vocabulary without touching a world or artifact store.
136
+ */
137
+ export function simulateArtifactProjection(declaration) {
138
+ const identity = `${declaration.name} → ${declaration.channel}${declaration.key === undefined ? "" : `/${declaration.key}`}`;
139
+ const invalidation = declaration.invalidation;
140
+ return {
141
+ emitted: { status: "active", identity },
142
+ explicitClear: { status: "cleared", identity },
143
+ invalidation: {
144
+ policy: invalidation,
145
+ status: invalidation === "never"
146
+ ? "active"
147
+ : invalidation === "explicit_clear"
148
+ ? "cleared"
149
+ : "superseded",
150
+ reason: invalidation === "never"
151
+ ? "never: no automatic invalidation event; remains active after emit."
152
+ : invalidation === "explicit_clear"
153
+ ? "explicit_clear: becomes cleared after a clear event."
154
+ : `${invalidation}: becomes superseded after the lifecycle event.`,
155
+ },
156
+ };
157
+ }
158
+ function samplePayload(contract, contentType) {
159
+ if (contract === undefined) {
160
+ if (contentType === "text/html")
161
+ return "<p>This is a local HTML workbench sample.</p>";
162
+ if (contentType === "text/markdown")
163
+ return "# Example follow-up artifact\n\nThis is a local workbench preview sample.";
164
+ if (contentType === "text/plain")
165
+ return "This is a local text workbench sample.";
166
+ return { sample: "workbench" };
167
+ }
168
+ switch (contract.type) {
169
+ case "object":
170
+ return Object.fromEntries(Object.entries(contract.properties ?? {})
171
+ .filter(([key]) => contract.required?.includes(key) ?? true)
172
+ .map(([key, child]) => [key, samplePayload(child)]));
173
+ case "array":
174
+ return [samplePayload(contract.items)];
175
+ case "string":
176
+ return "Example text";
177
+ case "number":
178
+ case "integer":
179
+ return 1;
180
+ case "boolean":
181
+ return true;
182
+ case "null":
183
+ return null;
184
+ }
185
+ }