@jameslovespancakes/pi-plus 1.0.11 → 1.0.13

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 (133) hide show
  1. package/README.md +67 -22
  2. package/config/skills/workflow-code-review-actions/SKILL.md +44 -0
  3. package/package.json +9 -4
  4. package/src/core/accounts/oauth-pool.ts +137 -0
  5. package/src/core/accounts/registry.ts +12 -25
  6. package/src/core/accounts/routing.ts +144 -0
  7. package/src/core/anthropic/client-identity.ts +9 -64
  8. package/src/core/anthropic/identity.ts +54 -0
  9. package/src/core/anthropic/oauth.ts +3 -0
  10. package/src/core/anthropic/quota.ts +232 -78
  11. package/src/core/anthropic/routing.ts +40 -115
  12. package/src/core/anthropic/store.ts +11 -10
  13. package/src/core/catalog/quality.ts +33 -15
  14. package/src/core/codex/quota.ts +46 -8
  15. package/src/core/codex/store.ts +29 -16
  16. package/src/core/config.ts +16 -17
  17. package/src/core/store.ts +13 -12
  18. package/src/domains/agents/format.ts +97 -0
  19. package/src/domains/agents/index.ts +58 -47
  20. package/src/domains/compact/archive.ts +140 -0
  21. package/src/domains/compact/chunking.ts +166 -0
  22. package/src/domains/compact/index.ts +452 -0
  23. package/src/domains/compact/jev.ts +242 -0
  24. package/src/domains/compact/policy.ts +255 -0
  25. package/src/domains/compact/types.ts +79 -0
  26. package/src/domains/models/provider-picker.ts +1 -1
  27. package/src/domains/setup/index.ts +23 -23
  28. package/src/domains/subscriptions/accounts-picker.ts +61 -40
  29. package/src/domains/subscriptions/accounts.ts +53 -36
  30. package/src/domains/subscriptions/footer.ts +48 -29
  31. package/src/domains/subscriptions/index.ts +11 -27
  32. package/src/domains/subscriptions/provider.ts +79 -103
  33. package/src/domains/subscriptions/providers/anthropic.ts +42 -22
  34. package/src/domains/subscriptions/providers/codex.ts +146 -113
  35. package/src/domains/subscriptions/providers/hosted.ts +18 -0
  36. package/src/domains/subscriptions/providers/oauth-pool.ts +325 -0
  37. package/src/domains/subscriptions/routing.ts +19 -11
  38. package/src/domains/workflows/LICENSE.md +21 -0
  39. package/src/domains/workflows/index.ts +836 -0
  40. package/src/domains/workflows/runtime/advisory-challenge.ts +75 -0
  41. package/src/domains/workflows/runtime/advisory-evidence.ts +90 -0
  42. package/src/domains/workflows/runtime/advisory-schema.ts +83 -0
  43. package/src/domains/workflows/runtime/agent-attempt.ts +177 -0
  44. package/src/domains/workflows/runtime/agent-failure.ts +14 -0
  45. package/src/domains/workflows/runtime/agent-limits.ts +66 -0
  46. package/src/domains/workflows/runtime/agent-replay.ts +399 -0
  47. package/src/domains/workflows/runtime/agent-retry.ts +116 -0
  48. package/src/domains/workflows/runtime/agent-runner-types.ts +96 -0
  49. package/src/domains/workflows/runtime/agent-runner.ts +225 -0
  50. package/src/domains/workflows/runtime/agent-session-identity.ts +256 -0
  51. package/src/domains/workflows/runtime/agent-session-providers.ts +50 -0
  52. package/src/domains/workflows/runtime/agent-session.ts +382 -0
  53. package/src/domains/workflows/runtime/agent-skills.ts +270 -0
  54. package/src/domains/workflows/runtime/agent-workspace.ts +79 -0
  55. package/src/domains/workflows/runtime/background-workflow-tool.ts +75 -0
  56. package/src/domains/workflows/runtime/background-workflows.ts +492 -0
  57. package/src/domains/workflows/runtime/budget.ts +53 -0
  58. package/src/domains/workflows/runtime/cancellation.ts +87 -0
  59. package/src/domains/workflows/runtime/command-completions.ts +36 -0
  60. package/src/domains/workflows/runtime/concurrency.ts +403 -0
  61. package/src/domains/workflows/runtime/debug.ts +3 -0
  62. package/src/domains/workflows/runtime/diff-capture.ts +81 -0
  63. package/src/domains/workflows/runtime/discovery.ts +137 -0
  64. package/src/domains/workflows/runtime/dynamax-shortcuts.ts +122 -0
  65. package/src/domains/workflows/runtime/dynamax.ts +330 -0
  66. package/src/domains/workflows/runtime/engine.ts +579 -0
  67. package/src/domains/workflows/runtime/filesystem-error.ts +4 -0
  68. package/src/domains/workflows/runtime/finalizers.ts +66 -0
  69. package/src/domains/workflows/runtime/identity-canonicalization.ts +138 -0
  70. package/src/domains/workflows/runtime/identity-fingerprint.ts +15 -0
  71. package/src/domains/workflows/runtime/inline-workflow.ts +403 -0
  72. package/src/domains/workflows/runtime/journal.ts +313 -0
  73. package/src/domains/workflows/runtime/model-profiles.ts +310 -0
  74. package/src/domains/workflows/runtime/options.ts +157 -0
  75. package/src/domains/workflows/runtime/perf.ts +146 -0
  76. package/src/domains/workflows/runtime/pi-compat.ts +32 -0
  77. package/src/domains/workflows/runtime/process-runner.ts +260 -0
  78. package/src/domains/workflows/runtime/progress-types.ts +59 -0
  79. package/src/domains/workflows/runtime/progress.ts +400 -0
  80. package/src/domains/workflows/runtime/provider-usage-limit.ts +191 -0
  81. package/src/domains/workflows/runtime/replay-path-identity.ts +48 -0
  82. package/src/domains/workflows/runtime/research-contract.ts +89 -0
  83. package/src/domains/workflows/runtime/research-evidence.ts +289 -0
  84. package/src/domains/workflows/runtime/resume-context.ts +751 -0
  85. package/src/domains/workflows/runtime/review/code-review-orchestration.ts +24 -0
  86. package/src/domains/workflows/runtime/review/github-pr-comments.ts +249 -0
  87. package/src/domains/workflows/runtime/review/patch-validation.ts +120 -0
  88. package/src/domains/workflows/runtime/review/review-actions.ts +180 -0
  89. package/src/domains/workflows/runtime/review/review-budget.ts +45 -0
  90. package/src/domains/workflows/runtime/review/review-fix-workflow.ts +153 -0
  91. package/src/domains/workflows/runtime/review/review-format.ts +133 -0
  92. package/src/domains/workflows/runtime/review/review-handoff.ts +38 -0
  93. package/src/domains/workflows/runtime/review/review-issues.ts +87 -0
  94. package/src/domains/workflows/runtime/review/review-report.ts +42 -0
  95. package/src/domains/workflows/runtime/review/review-results-flow.ts +52 -0
  96. package/src/domains/workflows/runtime/review/review-results-viewer.ts +312 -0
  97. package/src/domains/workflows/runtime/review/review-session-coordinator.ts +153 -0
  98. package/src/domains/workflows/runtime/review/review-snapshot.ts +294 -0
  99. package/src/domains/workflows/runtime/review-diff-target.ts +130 -0
  100. package/src/domains/workflows/runtime/session-identity.ts +6 -0
  101. package/src/domains/workflows/runtime/structured-output.ts +32 -0
  102. package/src/domains/workflows/runtime/tool-capabilities.ts +44 -0
  103. package/src/domains/workflows/runtime/tool-source-identity.ts +150 -0
  104. package/src/domains/workflows/runtime/tree-fingerprint.ts +404 -0
  105. package/src/domains/workflows/runtime/types.ts +255 -0
  106. package/src/domains/workflows/runtime/ui/display-text.ts +28 -0
  107. package/src/domains/workflows/runtime/ui/dynamax-editor-decoration.ts +220 -0
  108. package/src/domains/workflows/runtime/ui/workflow-format.ts +165 -0
  109. package/src/domains/workflows/runtime/ui/workflow-inspector.ts +435 -0
  110. package/src/domains/workflows/runtime/ui/workflow-result-renderer.ts +171 -0
  111. package/src/domains/workflows/runtime/ui/workflow-viewer-layout.ts +51 -0
  112. package/src/domains/workflows/runtime/ui/workflow-widget.ts +78 -0
  113. package/src/domains/workflows/runtime/unknown-error.ts +8 -0
  114. package/src/domains/workflows/runtime/usage.ts +341 -0
  115. package/src/domains/workflows/runtime/workflow-advisory-utils.ts +245 -0
  116. package/src/domains/workflows/runtime/workflow-execution.ts +121 -0
  117. package/src/domains/workflows/runtime/workflow-module.ts +75 -0
  118. package/src/domains/workflows/runtime/workflow-run-background.ts +72 -0
  119. package/src/domains/workflows/runtime/workflow-run-controller.ts +342 -0
  120. package/src/domains/workflows/runtime/workflow-run-history.ts +164 -0
  121. package/src/domains/workflows/runtime/workflow-run-record.ts +636 -0
  122. package/src/domains/workflows/runtime/workflow-run-store.ts +176 -0
  123. package/src/domains/workflows/runtime/workflow-usage-limit-scheduler.ts +80 -0
  124. package/src/domains/workflows/runtime/workflows.ts +40 -0
  125. package/src/domains/workflows/runtime/worktree.ts +615 -0
  126. package/src/domains/workflows/workflows/code-review.ts +232 -0
  127. package/src/domains/workflows/workflows/diagnose.ts +154 -0
  128. package/src/domains/workflows/workflows/perf-review.ts +149 -0
  129. package/src/domains/workflows/workflows/refactor-scout.ts +143 -0
  130. package/src/domains/workflows/workflows/research.ts +169 -0
  131. package/src/services/usage-service.ts +6 -30
  132. package/src/ui/usage-bars.ts +1 -2
  133. package/src/core/codex/oauth.ts +0 -129
@@ -0,0 +1,138 @@
1
+ export type IdentityCanonicalization =
2
+ | { readonly kind: "verified"; readonly value: string }
3
+ | { readonly kind: "unverifiable"; readonly reason: string };
4
+
5
+ export interface IdentityCanonicalizationOptions {
6
+ readonly maxBytes?: number;
7
+ readonly maxDepth?: number;
8
+ readonly maxNodes?: number;
9
+ }
10
+
11
+ const DEFAULT_MAX_BYTES = 1 << 20;
12
+ const DEFAULT_MAX_DEPTH = 64;
13
+ const DEFAULT_MAX_NODES = 16_384;
14
+
15
+ /**
16
+ * Produce a deterministic, bounded identity for JSON-like data.
17
+ *
18
+ * Replay inputs are not trusted: schemas and tool metadata can contain cycles,
19
+ * accessors, proxies, or values JSON cannot represent. Those inputs disable
20
+ * replay instead of throwing or being silently collapsed to the same key.
21
+ */
22
+ export function canonicalizeIdentity(
23
+ value: unknown,
24
+ options: IdentityCanonicalizationOptions = {},
25
+ ): IdentityCanonicalization {
26
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
27
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
28
+ const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;
29
+ const active = new WeakSet<object>();
30
+ const chunks: string[] = [];
31
+ let bytes = 0;
32
+ let nodes = 0;
33
+
34
+ const append = (chunk: string): void => {
35
+ bytes += Buffer.byteLength(chunk);
36
+ if (bytes > maxBytes) throw new IdentityCanonicalizationError(`identity exceeded ${maxBytes} bytes`);
37
+ chunks.push(chunk);
38
+ };
39
+
40
+ const visit = (current: unknown, depth: number): void => {
41
+ nodes += 1;
42
+ if (nodes > maxNodes) throw new IdentityCanonicalizationError(`identity exceeded ${maxNodes} values`);
43
+ if (depth > maxDepth) throw new IdentityCanonicalizationError(`identity exceeded ${maxDepth} levels`);
44
+
45
+ if (current === null) {
46
+ append("n;");
47
+ return;
48
+ }
49
+ switch (typeof current) {
50
+ case "undefined":
51
+ append("u;");
52
+ return;
53
+ case "boolean":
54
+ append(current ? "b1;" : "b0;");
55
+ return;
56
+ case "number":
57
+ if (!Number.isFinite(current)) throw new IdentityCanonicalizationError("identity contains a non-finite number");
58
+ append(`d${Object.is(current, -0) ? "-0" : String(current)};`);
59
+ return;
60
+ case "string":
61
+ append(`s${Buffer.byteLength(current)}:${current};`);
62
+ return;
63
+ case "bigint":
64
+ case "function":
65
+ case "symbol":
66
+ throw new IdentityCanonicalizationError(`identity contains unsupported ${typeof current} data`);
67
+ case "object":
68
+ break;
69
+ }
70
+
71
+ if (active.has(current)) throw new IdentityCanonicalizationError("identity contains a cycle");
72
+ active.add(current);
73
+ try {
74
+ const prototype = Object.getPrototypeOf(current);
75
+ if (Array.isArray(current)) {
76
+ if (prototype !== Array.prototype) throw new IdentityCanonicalizationError("identity contains an array with a custom prototype");
77
+ const descriptors = Object.getOwnPropertyDescriptors(current);
78
+ const ownKeys = Reflect.ownKeys(current);
79
+ if (ownKeys.some((key) => typeof key === "symbol")) {
80
+ throw new IdentityCanonicalizationError("identity contains symbol keys");
81
+ }
82
+ if (ownKeys.some((key) => typeof key === "string" && key !== "length" && !isArrayIndex(key, current.length))) {
83
+ throw new IdentityCanonicalizationError("identity array contains custom properties");
84
+ }
85
+ append(`a${current.length}[`);
86
+ for (let index = 0; index < current.length; index++) {
87
+ const descriptor = descriptors[String(index)];
88
+ if (!descriptor) {
89
+ append("h;");
90
+ continue;
91
+ }
92
+ if (!("value" in descriptor)) throw new IdentityCanonicalizationError("identity contains an array accessor");
93
+ visit(descriptor.value, depth + 1);
94
+ }
95
+ append("];");
96
+ return;
97
+ }
98
+
99
+ if (prototype !== Object.prototype && prototype !== null) {
100
+ throw new IdentityCanonicalizationError("identity contains an object with a custom prototype");
101
+ }
102
+ const descriptors = Object.getOwnPropertyDescriptors(current);
103
+ const ownKeys = Reflect.ownKeys(current);
104
+ if (ownKeys.some((key) => typeof key === "symbol")) {
105
+ throw new IdentityCanonicalizationError("identity contains symbol keys");
106
+ }
107
+ const keys = Object.keys(descriptors).sort();
108
+ append(`o${keys.length}{`);
109
+ for (const key of keys) {
110
+ const descriptor = descriptors[key]!;
111
+ if (!("value" in descriptor)) throw new IdentityCanonicalizationError(`identity property ${key} is an accessor`);
112
+ append(`k${Buffer.byteLength(key)}:${key};`);
113
+ append(`p${descriptor.enumerable ? 1 : 0}${descriptor.configurable ? 1 : 0}${descriptor.writable ? 1 : 0};`);
114
+ visit(descriptor.value, depth + 1);
115
+ }
116
+ append("};");
117
+ } finally {
118
+ active.delete(current);
119
+ }
120
+ };
121
+
122
+ try {
123
+ visit(value, 0);
124
+ return { kind: "verified", value: chunks.join("") };
125
+ } catch (error) {
126
+ return {
127
+ kind: "unverifiable",
128
+ reason: error instanceof IdentityCanonicalizationError ? error.message : "identity inspection failed",
129
+ };
130
+ }
131
+ }
132
+
133
+ class IdentityCanonicalizationError extends Error {}
134
+
135
+ function isArrayIndex(key: string, length: number): boolean {
136
+ const index = Number(key);
137
+ return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
138
+ }
@@ -0,0 +1,15 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalizeIdentity } from "./identity-canonicalization.ts";
3
+
4
+ export function hashIdentity(value: unknown): string {
5
+ const canonical = canonicalizeIdentity(value);
6
+ if (canonical.kind === "unverifiable") throw new Error(canonical.reason);
7
+ return `sha256:${createHash("sha256").update(canonical.value).digest("hex")}`;
8
+ }
9
+
10
+ export function inspectableFunctionSource(value: unknown, label: string): string {
11
+ if (typeof value !== "function") throw new Error(`${label} is not a function`);
12
+ const source = Function.prototype.toString.call(value);
13
+ if (source.length === 0 || source.includes("[native code]")) throw new Error(`${label} has no inspectable source`);
14
+ return source;
15
+ }
@@ -0,0 +1,403 @@
1
+ import { Type } from "typebox";
2
+ import { createHash } from "node:crypto";
3
+ import type { LoadedWorkflow, WorkflowApi } from "./types.ts";
4
+ import { unknownErrorMessage } from "./unknown-error.ts";
5
+ import { parseWorkflowMeta } from "./workflow-module.ts";
6
+
7
+ /**
8
+ * v1 inline workflow source contract:
9
+ * - The script starts with `export const meta = { ... };` where the object is a pure literal.
10
+ * - The script contains exactly one `export default async ...` workflow function.
11
+ * - Inline scripts must not use `import` or any exports other than `meta` and the default function.
12
+ * - `Type` is injected lexically by the host; scripts must use that `Type`, not import typebox.
13
+ */
14
+
15
+ export class InlineWorkflowCompileError extends Error {
16
+ constructor(message: string) {
17
+ super(message);
18
+ this.name = "InlineWorkflowCompileError";
19
+ }
20
+ }
21
+
22
+ export interface InlineMetaLiteral {
23
+ readonly literal: string;
24
+ readonly value: unknown;
25
+ readonly endOffset: number;
26
+ }
27
+
28
+ type InlineWorkflowExecutor = (api: WorkflowApi, typebox: typeof Type) => Promise<unknown>;
29
+ type AsyncFunctionConstructor = new (...args: string[]) => InlineWorkflowExecutor;
30
+
31
+ const AsyncFunction = Object.getPrototypeOf(async function inlineWorkflowCompilerSentinel() {}).constructor as AsyncFunctionConstructor;
32
+
33
+ export function compileInlineWorkflow(source: string): LoadedWorkflow {
34
+ rejectForbiddenModuleSyntax(source);
35
+ const metaLiteral = extractMetaLiteral(source);
36
+ const parsedMeta = parseWorkflowMeta(metaLiteral.value);
37
+ if ("reason" in parsedMeta) throw new InlineWorkflowCompileError(parsedMeta.reason);
38
+
39
+ const defaultExpression = extractDefaultWorkflowExpression(source, metaLiteral.endOffset);
40
+ const executor = compileExecutor(defaultExpression);
41
+ return {
42
+ meta: parsedMeta.meta,
43
+ default: (api) => executor(api, Type),
44
+ source: { kind: "fingerprint", fingerprint: createHash("sha256").update(source).digest("hex") },
45
+ };
46
+ }
47
+
48
+ export function extractMetaLiteral(source: string): InlineMetaLiteral {
49
+ const start = skipWhitespace(source, 0);
50
+ const prefix = /export\s+const\s+meta\s*=/y;
51
+ prefix.lastIndex = start;
52
+ const match = prefix.exec(source);
53
+ if (!match || match.index !== start) {
54
+ throw new InlineWorkflowCompileError("inline workflow must start with `export const meta = { ... }`;");
55
+ }
56
+
57
+ const objectStart = skipWhitespace(source, prefix.lastIndex);
58
+ if (source[objectStart] !== "{") {
59
+ throw new InlineWorkflowCompileError("inline workflow meta must be an object literal");
60
+ }
61
+
62
+ const parser = new LiteralParser(source, objectStart);
63
+ const value = parser.parseValue();
64
+ const literalEnd = parser.offset;
65
+ let endOffset = skipWhitespace(source, literalEnd);
66
+ if (source[endOffset] === ";") endOffset++;
67
+
68
+ return { literal: source.slice(objectStart, literalEnd), value, endOffset };
69
+ }
70
+
71
+ function rejectForbiddenModuleSyntax(source: string): void {
72
+ if (/^\s*import\s/m.test(source)) {
73
+ throw new InlineWorkflowCompileError("inline workflows must not contain import statements; use injected Type instead");
74
+ }
75
+ if (/\bimport\s*\(/.test(source)) {
76
+ throw new InlineWorkflowCompileError("inline workflows must not use dynamic import()");
77
+ }
78
+ }
79
+
80
+ function extractDefaultWorkflowExpression(source: string, startOffset: number): string {
81
+ const moduleSource = source.slice(startOffset).trim();
82
+ if (!moduleSource) throw new InlineWorkflowCompileError("inline workflow must export a default async function");
83
+ if (!moduleSource.startsWith("export default")) {
84
+ throw new InlineWorkflowCompileError("inline workflow default export must directly follow the meta declaration");
85
+ }
86
+
87
+ const defaultPrefix = /^export\s+default\s+/;
88
+ const match = defaultPrefix.exec(moduleSource);
89
+ if (!match) throw new InlineWorkflowCompileError("inline workflow must export a default async function");
90
+
91
+ const expressionSource = moduleSource.slice(match[0].length).trimStart();
92
+ const bodyOpen = findDefaultBodyOpen(expressionSource);
93
+ const bodyClose = findMatchingDelimiter(expressionSource, bodyOpen, "{", "}");
94
+ const expression = expressionSource.slice(0, bodyClose + 1);
95
+ const remainder = expressionSource.slice(bodyClose + 1).trim();
96
+ if (remainder !== "" && remainder !== ";") {
97
+ throw new InlineWorkflowCompileError("inline workflow must not contain code after the default export");
98
+ }
99
+ return expression;
100
+ }
101
+
102
+ function findDefaultBodyOpen(expressionSource: string): number {
103
+ const functionMatch = /^async\s+function(?:\s+[A-Za-z_$][\w$]*)?\s*/.exec(expressionSource);
104
+ if (functionMatch) {
105
+ const paramsOpen = skipWhitespace(expressionSource, functionMatch[0].length);
106
+ if (expressionSource[paramsOpen] !== "(") {
107
+ throw new InlineWorkflowCompileError("default async function must declare parameters with parentheses");
108
+ }
109
+ const paramsClose = findMatchingDelimiter(expressionSource, paramsOpen, "(", ")");
110
+ const bodyOpen = skipWhitespace(expressionSource, paramsClose + 1);
111
+ if (expressionSource[bodyOpen] !== "{") {
112
+ throw new InlineWorkflowCompileError("default async function must use a block body");
113
+ }
114
+ return bodyOpen;
115
+ }
116
+
117
+ const arrowMatch = /^async\s*/.exec(expressionSource);
118
+ if (arrowMatch) {
119
+ const paramsOpen = skipWhitespace(expressionSource, arrowMatch[0].length);
120
+ if (expressionSource[paramsOpen] !== "(") {
121
+ throw new InlineWorkflowCompileError("default async arrow workflow must declare parameters with parentheses");
122
+ }
123
+ const paramsClose = findMatchingDelimiter(expressionSource, paramsOpen, "(", ")");
124
+ let cursor = skipWhitespace(expressionSource, paramsClose + 1);
125
+ if (!expressionSource.startsWith("=>", cursor)) {
126
+ throw new InlineWorkflowCompileError("default async arrow workflow must use =>");
127
+ }
128
+ cursor = skipWhitespace(expressionSource, cursor + 2);
129
+ if (expressionSource[cursor] !== "{") {
130
+ throw new InlineWorkflowCompileError("default async arrow workflow must use a block body");
131
+ }
132
+ return cursor;
133
+ }
134
+
135
+ throw new InlineWorkflowCompileError("inline workflow default export must be an async function or async arrow function");
136
+ }
137
+
138
+ function compileExecutor(defaultExpression: string): InlineWorkflowExecutor {
139
+ try {
140
+ return new AsyncFunction("api", "Type", `"use strict";\nconst workflow = ${defaultExpression};\nreturn await workflow(api);`);
141
+ } catch (error) {
142
+ throw new InlineWorkflowCompileError(`inline workflow default export did not compile: ${unknownErrorMessage(error)}`);
143
+ }
144
+ }
145
+
146
+ class LiteralParser {
147
+ private index: number;
148
+ private readonly source: string;
149
+
150
+ constructor(source: string, offset: number) {
151
+ this.source = source;
152
+ this.index = offset;
153
+ }
154
+
155
+ get offset(): number {
156
+ return this.index;
157
+ }
158
+
159
+ parseValue(): unknown {
160
+ this.skipWhitespace();
161
+ const char = this.source[this.index];
162
+ if (char === "{") return this.parseObject();
163
+ if (char === "[") return this.parseArray();
164
+ if (char === '"' || char === "'") return this.parseString(char);
165
+ if (char === "-" || isDigit(char)) return this.parseNumber();
166
+ if (this.source.startsWith("true", this.index)) return this.consumeKeyword("true", true);
167
+ if (this.source.startsWith("false", this.index)) return this.consumeKeyword("false", false);
168
+ if (this.source.startsWith("null", this.index)) return this.consumeKeyword("null", null);
169
+ if (char === "`") this.fail("template literals are not allowed in inline workflow meta");
170
+ this.fail(`unexpected token in inline workflow meta: ${char ?? "end of input"}`);
171
+ }
172
+
173
+ private parseObject(): Record<string, unknown> {
174
+ const object: Record<string, unknown> = {};
175
+ this.expect("{");
176
+ this.skipWhitespace();
177
+ if (this.peek() === "}") {
178
+ this.index++;
179
+ return object;
180
+ }
181
+
182
+ while (true) {
183
+ this.skipWhitespace();
184
+ if (this.source.startsWith("...", this.index)) this.fail("spread properties are not allowed in inline workflow meta");
185
+ if (this.peek() === "[") this.fail("computed properties are not allowed in inline workflow meta");
186
+ const key = this.parsePropertyKey();
187
+ this.skipWhitespace();
188
+ if (this.peek() !== ":") this.fail("inline workflow meta properties must use explicit key: value syntax");
189
+ this.index++;
190
+ object[key] = this.parseValue();
191
+ this.skipWhitespace();
192
+ const next = this.peek();
193
+ if (next === ",") {
194
+ this.index++;
195
+ this.skipWhitespace();
196
+ if (this.peek() === "}") {
197
+ this.index++;
198
+ return object;
199
+ }
200
+ continue;
201
+ }
202
+ if (next === "}") {
203
+ this.index++;
204
+ return object;
205
+ }
206
+ this.fail("expected `,` or `}` in inline workflow meta object");
207
+ }
208
+ }
209
+
210
+ private parseArray(): unknown[] {
211
+ const array: unknown[] = [];
212
+ this.expect("[");
213
+ this.skipWhitespace();
214
+ if (this.peek() === "]") {
215
+ this.index++;
216
+ return array;
217
+ }
218
+
219
+ while (true) {
220
+ array.push(this.parseValue());
221
+ this.skipWhitespace();
222
+ const next = this.peek();
223
+ if (next === ",") {
224
+ this.index++;
225
+ this.skipWhitespace();
226
+ if (this.peek() === "]") {
227
+ this.index++;
228
+ return array;
229
+ }
230
+ continue;
231
+ }
232
+ if (next === "]") {
233
+ this.index++;
234
+ return array;
235
+ }
236
+ this.fail("expected `,` or `]` in inline workflow meta array");
237
+ }
238
+ }
239
+
240
+ private parsePropertyKey(): string {
241
+ const char = this.peek();
242
+ if (char === '"' || char === "'") return this.parseString(char);
243
+ if (!isIdentifierStart(char)) this.fail("inline workflow meta property keys must be strings or identifiers");
244
+ const start = this.index;
245
+ this.index++;
246
+ while (isIdentifierPart(this.peek())) this.index++;
247
+ return this.source.slice(start, this.index);
248
+ }
249
+
250
+ private parseString(quote: string): string {
251
+ this.expect(quote);
252
+ let value = "";
253
+ while (this.index < this.source.length) {
254
+ const char = this.source[this.index];
255
+ if (char === quote) {
256
+ this.index++;
257
+ return value;
258
+ }
259
+ if (char === "\\") {
260
+ value += this.parseEscapeSequence();
261
+ continue;
262
+ }
263
+ if (char === "\n" || char === "\r") this.fail("inline workflow meta strings must not contain raw newlines");
264
+ value += char;
265
+ this.index++;
266
+ }
267
+ this.fail("unterminated string in inline workflow meta");
268
+ }
269
+
270
+ private parseEscapeSequence(): string {
271
+ this.expect("\\");
272
+ const escaped = this.source[this.index];
273
+ if (escaped === undefined) this.fail("unterminated escape sequence in inline workflow meta");
274
+ this.index++;
275
+ switch (escaped) {
276
+ case '"':
277
+ case "'":
278
+ case "\\":
279
+ case "/":
280
+ return escaped;
281
+ case "b":
282
+ return "\b";
283
+ case "f":
284
+ return "\f";
285
+ case "n":
286
+ return "\n";
287
+ case "r":
288
+ return "\r";
289
+ case "t":
290
+ return "\t";
291
+ case "u": {
292
+ const hex = this.source.slice(this.index, this.index + 4);
293
+ if (!/^[0-9A-Fa-f]{4}$/.test(hex)) this.fail("invalid unicode escape in inline workflow meta");
294
+ this.index += 4;
295
+ return String.fromCharCode(Number.parseInt(hex, 16));
296
+ }
297
+ default:
298
+ this.fail(`unsupported escape sequence \\${escaped} in inline workflow meta`);
299
+ }
300
+ }
301
+
302
+ private parseNumber(): number {
303
+ const match = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
304
+ match.lastIndex = this.index;
305
+ const parsed = match.exec(this.source);
306
+ if (!parsed) this.fail("invalid number in inline workflow meta");
307
+ this.index = match.lastIndex;
308
+ return Number(parsed[0]);
309
+ }
310
+
311
+ private consumeKeyword<T>(keyword: string, value: T): T {
312
+ this.index += keyword.length;
313
+ if (isIdentifierPart(this.peek())) this.fail(`unexpected identifier after ${keyword} in inline workflow meta`);
314
+ return value;
315
+ }
316
+
317
+ private skipWhitespace(): void {
318
+ this.index = skipWhitespace(this.source, this.index);
319
+ }
320
+
321
+ private peek(): string | undefined {
322
+ return this.source[this.index];
323
+ }
324
+
325
+ private expect(char: string): void {
326
+ if (this.source[this.index] !== char) this.fail(`expected ${char} in inline workflow meta`);
327
+ this.index++;
328
+ }
329
+
330
+ private fail(message: string): never {
331
+ throw new InlineWorkflowCompileError(`${message} at offset ${this.index}`);
332
+ }
333
+ }
334
+
335
+ function findMatchingDelimiter(source: string, openIndex: number, open: string, close: string): number {
336
+ let depth = 1;
337
+ let index = openIndex + 1;
338
+ while (index < source.length) {
339
+ const char = source[index];
340
+ if (char === '"' || char === "'" || char === "`") {
341
+ index = skipQuotedSource(source, index, char);
342
+ continue;
343
+ }
344
+ if (char === "/" && source[index + 1] === "/") {
345
+ index = skipLineComment(source, index);
346
+ continue;
347
+ }
348
+ if (char === "/" && source[index + 1] === "*") {
349
+ index = skipBlockComment(source, index);
350
+ continue;
351
+ }
352
+ if (char === open) depth++;
353
+ if (char === close) {
354
+ depth--;
355
+ if (depth === 0) return index;
356
+ }
357
+ index++;
358
+ }
359
+ throw new InlineWorkflowCompileError(`unterminated ${open}${close} block in inline workflow default export`);
360
+ }
361
+
362
+ function skipQuotedSource(source: string, start: number, quote: string): number {
363
+ let index = start + 1;
364
+ while (index < source.length) {
365
+ const char = source[index];
366
+ if (char === "\\") {
367
+ index += 2;
368
+ continue;
369
+ }
370
+ if (char === quote) return index + 1;
371
+ index++;
372
+ }
373
+ throw new InlineWorkflowCompileError("unterminated string in inline workflow default export");
374
+ }
375
+
376
+ function skipLineComment(source: string, start: number): number {
377
+ const newline = source.indexOf("\n", start + 2);
378
+ return newline === -1 ? source.length : newline + 1;
379
+ }
380
+
381
+ function skipBlockComment(source: string, start: number): number {
382
+ const end = source.indexOf("*/", start + 2);
383
+ if (end === -1) throw new InlineWorkflowCompileError("unterminated block comment in inline workflow default export");
384
+ return end + 2;
385
+ }
386
+
387
+ function skipWhitespace(source: string, start: number): number {
388
+ let index = start;
389
+ while (index < source.length && /\s/.test(source[index] ?? "")) index++;
390
+ return index;
391
+ }
392
+
393
+ function isDigit(value: string | undefined): boolean {
394
+ return value !== undefined && value >= "0" && value <= "9";
395
+ }
396
+
397
+ function isIdentifierStart(value: string | undefined): boolean {
398
+ return value !== undefined && /[A-Za-z_$]/.test(value);
399
+ }
400
+
401
+ function isIdentifierPart(value: string | undefined): boolean {
402
+ return value !== undefined && /[A-Za-z0-9_$]/.test(value);
403
+ }