pi-workflow-engine 0.10.2 → 0.11.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/.pi/extensions/pi-workflow-engine/index.ts +199 -120
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +11 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +145 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +376 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +87 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +84 -529
- package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +256 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +413 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +43 -1
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +81 -0
- package/.pi/extensions/pi-workflow-engine/src/budget.ts +7 -0
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +17 -3
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +164 -61
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +117 -20
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +64 -15
- package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +59 -13
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +14 -3
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +208 -74
- package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +66 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +138 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-fingerprint.ts +15 -0
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +8 -3
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +148 -64
- package/.pi/extensions/pi-workflow-engine/src/options.ts +9 -6
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +203 -39
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +2 -5
- package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +48 -0
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +665 -0
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +75 -16
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +83 -17
- package/.pi/extensions/pi-workflow-engine/src/review/review-budget.ts +45 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +117 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +14 -66
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +38 -12
- package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +42 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +17 -22
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +143 -52
- package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +153 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +295 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +153 -0
- package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +404 -0
- package/.pi/extensions/pi-workflow-engine/src/types.ts +38 -10
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +5 -69
- package/.pi/extensions/pi-workflow-engine/src/unknown-error.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +121 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +19 -1
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +28 -3
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +282 -41
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +48 -26
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +8 -1
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +8 -2
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +8 -1
- package/README.md +170 -226
- package/USAGE.md +92 -12
- package/package.json +9 -9
package/USAGE.md
CHANGED
|
@@ -27,6 +27,7 @@ Useful flags:
|
|
|
27
27
|
/workflow:inspector # open the current or last workflow inspector
|
|
28
28
|
/workflow code-review --result-viewer # explicitly open the findings viewer
|
|
29
29
|
/workflow code-review --review-viewer # alias for --result-viewer
|
|
30
|
+
/workflow:results # reopen the latest code-review findings without rerunning
|
|
30
31
|
/workflow code-review --concurrency=4 # cap concurrent subagents
|
|
31
32
|
/workflow code-review --budget=50000 # output-token ceiling for subagents
|
|
32
33
|
/workflow code-review --resume <run-id> # replay matching completed agent calls
|
|
@@ -42,7 +43,7 @@ Useful flags:
|
|
|
42
43
|
| `diagnose` | Investigates a bug, failing command, or regression. |
|
|
43
44
|
| `perf-review` | Reviews a slow path or performance concern. |
|
|
44
45
|
|
|
45
|
-
`code-review` auto-detects a diff when you do not pass args. If there is an open GitHub PR for the current branch, it uses
|
|
46
|
+
`code-review` auto-detects a diff when you do not pass args. If there is an open GitHub PR for the current branch, it uses the cumulative PR diff; otherwise it falls back to branch-vs-main/master or `HEAD~1`. Per-commit `gh pr diff --patch` input is rejected because it can surface superseded code. If diff capture fails, the review fails instead of returning an empty report or "no findings."
|
|
46
47
|
|
|
47
48
|
## Use Dynamax for custom workflows
|
|
48
49
|
|
|
@@ -68,26 +69,65 @@ Sticky mode and inspector shortcut:
|
|
|
68
69
|
/workflow:dynamax status
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
The workflow inspector
|
|
72
|
+
The workflow inspector and code-review results viewer are also registered as first-class shortcuts shown by `/hotkeys`. The defaults are `ctrl+shift+m` for the inspector and `ctrl+shift+r` for the latest review results. `ctrl+o` is intentionally not used because pi already uses it for tool-output expansion and tree filtering.
|
|
72
73
|
|
|
73
74
|
When only the literal `dynamax` token is used, the opt-in is one-shot: the next agent run receives the workflow permission reminder, stays visibly active for that run, then clears after the run ends. When `/workflow:dynamax on` is used, the opt-in is sticky for the current pi session until `/workflow:dynamax off`. Sticky mode, one-shot pending mode, and active Dynamax workflow runs show compact TUI status with `/workflow:dynamax on|off` and the inspector shortcut; off mode clears that status line.
|
|
74
75
|
|
|
75
76
|
When the host agent calls the `workflow` tool from a TUI session, pi opens the live workflow inspector for that run. The compact workflow widget still shows the latest moving status above the editor, but the inspector is the richer view for phases, agents, findings, and logs.
|
|
76
77
|
|
|
77
|
-
Configure
|
|
78
|
+
Configure either shortcut by creating `~/.pi/agent/extensions/pi-workflow-engine.json`:
|
|
78
79
|
|
|
79
80
|
```json
|
|
80
81
|
{
|
|
81
82
|
"shortcuts": {
|
|
82
|
-
"inspector": "ctrl+shift+x"
|
|
83
|
+
"inspector": "ctrl+shift+x",
|
|
84
|
+
"results": "ctrl+shift+y"
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
Set `
|
|
89
|
+
Set either shortcut to `null` to disable it while keeping `/workflow:inspector` and `/workflow:results` available.
|
|
88
90
|
|
|
89
91
|
With Dynamax enabled, the host agent usually calls the `workflow` tool with `script`: a one-off inline workflow tailored to your prompt. It can still call a saved workflow by `name` when one already fits.
|
|
90
92
|
|
|
93
|
+
### Adaptive multi-pass authoring
|
|
94
|
+
|
|
95
|
+
A single fan-out is still the default when it can answer the question. When the first pass may reveal gaps, conflicts, weak claims, or missing evidence, an inline workflow can inspect those intermediate results and commission only the follow-up work the LLM says is needed:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const firstPass = (await api.parallel(initialTasks)).filter((result) => result !== null);
|
|
99
|
+
|
|
100
|
+
const MAX_FOLLOW_UPS = 4;
|
|
101
|
+
const GapAnalysis = Type.Object({
|
|
102
|
+
items: Type.Array(
|
|
103
|
+
Type.Object({ question: Type.String(), reason: Type.String() }),
|
|
104
|
+
{ maxItems: MAX_FOLLOW_UPS },
|
|
105
|
+
),
|
|
106
|
+
});
|
|
107
|
+
const gaps = await api.agent(
|
|
108
|
+
`Identify only material gaps that require another agent:\n${JSON.stringify(firstPass)}`,
|
|
109
|
+
{ schema: GapAnalysis, thinkingLevel: "low" },
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const followUpItems = gaps?.items.slice(0, MAX_FOLLOW_UPS) ?? [];
|
|
113
|
+
const followUps = followUpItems.length
|
|
114
|
+
? (await api.parallel(
|
|
115
|
+
followUpItems.map((item) => () =>
|
|
116
|
+
api.agent(`Resolve this gap and cite evidence: ${JSON.stringify(item)}`, {
|
|
117
|
+
thinkingLevel: "low",
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
)).filter((result) => result !== null)
|
|
121
|
+
: [];
|
|
122
|
+
|
|
123
|
+
return api.agent(
|
|
124
|
+
`Synthesize the first pass and any follow-ups:\n${JSON.stringify({ firstPass, followUps })}`,
|
|
125
|
+
{ thinkingLevel: "medium" },
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The structured gap-analysis result lets ordinary TypeScript decide whether a second pass exists. Put a hard `maxItems` bound on LLM-authored task lists and defensively slice before fan-out. Prefer conditionals and bounded loops over new iteration, quorum, graph, reduction, or retry primitives, and do not generate follow-up agents when the first pass is already sufficient.
|
|
130
|
+
|
|
91
131
|
## Workflow results
|
|
92
132
|
|
|
93
133
|
Workflow results are rendered in pi with:
|
|
@@ -104,7 +144,11 @@ These workflow usage totals are separate from `--perf`, which is internal timing
|
|
|
104
144
|
|
|
105
145
|
During a run, pi shows live phases and subagent status. Use `--inspect` if you want a larger live view while the workflow is active, then `/workflow:inspector` if you want to bring the last completed inspector back up afterward.
|
|
106
146
|
|
|
107
|
-
Code-review findings are rendered as a formatted result message by default. pi no longer asks whether to open the findings viewer. Use `--result-viewer` or `--review-viewer` when you want to inspect findings interactively, press `enter` to expand/collapse the nicely formatted finding text, and use `1`-`9` to jump directly to a visible finding.
|
|
147
|
+
Code-review findings are rendered as a formatted result message by default. pi no longer asks whether to open the findings viewer. Use `--result-viewer` or `--review-viewer` when you want to inspect findings interactively, press `enter` to expand/collapse the nicely formatted finding text, and use `1`-`9` to jump directly to a visible finding. The viewer is centred, scales to the terminal, and shows the visible finding/detail ranges while scrolling. `/workflow:results` or `ctrl+shift+r` reopens the most recent validated code-review report in the current pi session without rerunning the workflow; selections reset when it reopens.
|
|
148
|
+
|
|
149
|
+
The Fix action revalidates the exact reviewed PR/ref/index/working-tree snapshot, then runs each selected finding through its own worktree-isolated agent and returns the finding ID, validation summary, patch, and changed status. Failed attempts do not discard successful previews, no patch is applied to your active tree automatically, and a moved or unverifiable review target is rejected rather than patched against the wrong code. The original review and all Fix previews retained in that pi session share one output-token budget. Reopening the viewer does not reset it, finalized preview usage is always deducted, and only one preview can run at a time.
|
|
150
|
+
|
|
151
|
+
Inline GitHub comments are available only for findings captured from a verified PR target. Before posting, the engine revalidates the reviewed snapshot and requires the current PR head to match; identical comments on that head are skipped.
|
|
108
152
|
|
|
109
153
|
### Resume a run
|
|
110
154
|
|
|
@@ -114,9 +158,24 @@ Every workflow result includes a run id. Resume with:
|
|
|
114
158
|
/workflow code-review --resume <run-id> HEAD~3
|
|
115
159
|
```
|
|
116
160
|
|
|
117
|
-
The `workflow` tool exposes the same feature as `resumeFromRunId`.
|
|
161
|
+
The `workflow` tool exposes the same feature as `resumeFromRunId`. Replay is explicit for agents that share the workflow directory and automatic for isolated patch-producing agents:
|
|
118
162
|
|
|
119
|
-
|
|
163
|
+
| Agent call | Default during resume | Override |
|
|
164
|
+
| --- | --- | --- |
|
|
165
|
+
| Shared workspace | Runs live | `resume: "read-only"` opts into repository-wide Git-visible replay; `resumeInputs` adds ignored paths under the workflow cwd |
|
|
166
|
+
| `isolation: "worktree"` | Replays when identity matches | `resume: "off"` forces a live run |
|
|
167
|
+
|
|
168
|
+
Use `resume: "read-only"` only when the shared agent is advisory. In Git repositories the engine resolves the repository root and binds replay to HEAD, staged and unstaged tracked changes, index modes, and bounded non-ignored untracked contents across that whole worktree, even when pi starts in a subdirectory. Add ignored or generated files under the workflow cwd with cwd-relative `resumeInputs`; explicitly named ignored paths are fingerprinted directly and cannot escape that cwd. The complete identity is checked before accepting a hit, after execution, and again after cleanup. A changed surface turns a hit into a live run and prevents unsafe recording. Tracked symlinks, submodules, and unsupported index states fail closed. Use `resume: "off"` when the call may inspect undeclared ignored files, external paths, services, environment, or clock state.
|
|
169
|
+
|
|
170
|
+
Journals use replay contract v2. A hit requires the same prompt/options and execution identity: repository-wide Git-visible state plus explicitly declared ignored inputs, workflow source provenance, coding-agent runtime version, effective system prompt/provider/model/thinking level, ordered selected skill contents, and ordered executable tool definitions plus source fingerprints. Unverifiable, cyclic, oversized, symlinked, submodule-backed, or mutable identity surfaces fail closed and run live. A tool-free structured agent uses a capability identity instead of hashing a workspace it cannot observe; an isolated agent binds replay to both the commit and tree objects of its exact prepared worktree baseline, including deterministic normalized snapshots for repositories with no commits. Each resumed run writes a fresh journal under its new run id, and cached results do not add usage or budget spend.
|
|
171
|
+
|
|
172
|
+
Cached values are revalidated before use: text must still be text, structured output must satisfy the current typebox schema, and an isolated patch must have consistent metadata and pass `git apply --check --binary` against its fresh baseline worktree. A malformed or stale value runs live.
|
|
173
|
+
|
|
174
|
+
Invalidations appear in progress output with a concise reason. Legacy v1 journals and early v2 entries without effective-session identity are parsed for compatibility but always miss. Git repositories with no commits and non-Git directories remain supported. Git control data and engine-owned workflow journals cannot be declared as inputs; if a Git-visible or explicit surface exceeds safety bounds, that call runs live.
|
|
175
|
+
|
|
176
|
+
Statically loaded built-ins fingerprint and revalidate their bounded extension source tree so imported helper changes invalidate replay, while inline workflows use the compiler-provided script fingerprint. Dynamically discovered and programmatic modules are intentionally non-replayable because their transitive runtime imports or captured closures cannot be bound to an immutable source snapshot.
|
|
177
|
+
|
|
178
|
+
Resume only caches completed `agent()` calls. It does not snapshot arbitrary workflow local variables, in-flight tool work, environment or clock state, external services, or generated inputs outside the captured repository/workflow/skill/tool surfaces. Use `resume: "off"` for calls that depend on those values, and keep prompts and options deterministic when you want cache hits.
|
|
120
179
|
|
|
121
180
|
## Author a saved workflow
|
|
122
181
|
|
|
@@ -142,13 +201,29 @@ export default async function run({ agent, parallel, phase, args }: WorkflowApi)
|
|
|
142
201
|
phase("Find");
|
|
143
202
|
const findings = compactResults(
|
|
144
203
|
await parallel([
|
|
145
|
-
() => agent(`Find correctness issues: ${args}`, {
|
|
146
|
-
|
|
204
|
+
() => agent(`Find correctness issues: ${args}`, {
|
|
205
|
+
schema: Finding,
|
|
206
|
+
tools: SEARCH_TOOLS,
|
|
207
|
+
toolHints: SEARCH_TOOL_HINTS,
|
|
208
|
+
thinkingLevel: "low",
|
|
209
|
+
resume: "read-only",
|
|
210
|
+
}),
|
|
211
|
+
() => agent(`Find edge cases: ${args}`, {
|
|
212
|
+
schema: Finding,
|
|
213
|
+
tools: SEARCH_TOOLS,
|
|
214
|
+
toolHints: SEARCH_TOOL_HINTS,
|
|
215
|
+
thinkingLevel: "low",
|
|
216
|
+
resume: "read-only",
|
|
217
|
+
}),
|
|
147
218
|
]),
|
|
148
219
|
);
|
|
149
220
|
|
|
150
221
|
phase("Synthesize");
|
|
151
|
-
return agent(`Summarize: ${JSON.stringify(findings)}`, {
|
|
222
|
+
return agent(`Summarize: ${JSON.stringify(findings)}`, {
|
|
223
|
+
thinkingLevel: "medium",
|
|
224
|
+
tools: [],
|
|
225
|
+
resume: "read-only",
|
|
226
|
+
});
|
|
152
227
|
}
|
|
153
228
|
```
|
|
154
229
|
|
|
@@ -158,11 +233,14 @@ Core primitives:
|
|
|
158
233
|
| --- | --- |
|
|
159
234
|
| `agent(prompt, opts)` | Runs one isolated subagent. With `schema`, returns validated structured data. |
|
|
160
235
|
| `parallel(thunks)` | Runs thunks concurrently and waits for all; recoverable failures become `null` slots. |
|
|
236
|
+
| `parallel(thunks, { settled: true })` | Retains each success or recoverable failure as a serialisable discriminated result. |
|
|
161
237
|
| `pipeline(items, ...stages)` | Runs each item through stages independently; a failed item becomes `null`. |
|
|
162
238
|
| `phase(title)` / `log(message)` | Updates workflow progress UI. |
|
|
163
239
|
| `progress(event)` | Emits counters, summaries, and lane items. |
|
|
164
240
|
| `workflow(ref, args?)` | Runs another workflow inline as a sub-step and returns its result. |
|
|
165
241
|
|
|
242
|
+
Use settled mode when the workflow must distinguish a successful `null` from a failed branch. It preserves input order and returns `{ ok: true, value }` or `{ ok: false, error: { name?, message } }` for every thunk. Genuine workflow cancellation still rejects the whole `parallel()` call and cancels siblings.
|
|
243
|
+
|
|
166
244
|
Set `thinkingLevel` on fan-out agents. Otherwise many subagents can inherit an expensive global reasoning level.
|
|
167
245
|
|
|
168
246
|
`tools` is a strict allowlist. If you set `tools: ["read", "bash"]`, extension tools such as `ast-grep`, `mgrep`, `fffind`, or `ffgrep` are hidden from that subagent. Add `toolHints: ["search"]` to dynamically expose installed grep/find/search-like tools while keeping the concrete base allowlist portable. The built-in advisory workflows use `tools: ["read", "bash", "grep", "find", "ls"]` plus `toolHints: ["search"]`.
|
|
@@ -188,7 +266,9 @@ if (edit.changed) {
|
|
|
188
266
|
return { summary: edit.result };
|
|
189
267
|
```
|
|
190
268
|
|
|
191
|
-
Isolated agents require the workflow `cwd` to be inside a git work tree. Outside git, the agent fails fast rather than silently mutating the shared directory. The return value is `{ result, patch, changed }`, where `result` is the normal text or structured `agent()` result and `patch` is a `git diff HEAD` patch. Worktree setup costs disk and git process time, so keep it opt-in for mutating/parallel-edit stages.
|
|
269
|
+
Isolated agents require the workflow `cwd` to be inside a git work tree. Outside git, the agent fails fast rather than silently mutating the shared directory. The return value is `{ result, patch, changed }`, where `result` is the normal text or structured `agent()` result and `patch` is a `git diff HEAD` patch. Worktree results are replayable by default; add `resume: "off"` when their inputs are not fully captured. Worktree setup costs disk and git process time, so keep it opt-in for mutating/parallel-edit stages.
|
|
270
|
+
|
|
271
|
+
Cleanup is a required finalizer. The engine attempts every registered worktree removal, retains failed paths for another cleanup attempt, and fails the workflow with the aggregated cleanup error if any worktree still cannot be removed. Advisory finalizers such as UI snapshots and journal pruning do not replace an otherwise valid workflow result.
|
|
192
272
|
|
|
193
273
|
### Compose workflows
|
|
194
274
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-workflow-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zero-dependency Dynamax workflows for pi: live parallel subagents, typed handoffs, synthesized results.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"image": "https://raw.githubusercontent.com/timbrinded/pi-workflow-engine/master/assets/preview.png"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"typecheck": "
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
39
|
"test": "bun test --timeout=30000",
|
|
40
40
|
"test:changedlines": "bun test tests/changedlines.test.ts",
|
|
41
41
|
"test:concurrency": "bun test tests/concurrency.test.ts",
|
|
@@ -46,17 +46,17 @@
|
|
|
46
46
|
"bench:discovery": "bun run scripts/bench/discovery.ts",
|
|
47
47
|
"bench:startup": "bun run scripts/bench/startup.ts",
|
|
48
48
|
"bench:ui": "bun run scripts/bench/ui.ts",
|
|
49
|
-
"prepublishOnly": "
|
|
49
|
+
"prepublishOnly": "tsc --noEmit && bun run test"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@earendil-works/pi-agent-core": "
|
|
53
|
-
"@earendil-works/pi-ai": "
|
|
54
|
-
"@earendil-works/pi-coding-agent": "
|
|
55
|
-
"@earendil-works/pi-tui": "
|
|
56
|
-
"typebox": "
|
|
52
|
+
"@earendil-works/pi-agent-core": "*",
|
|
53
|
+
"@earendil-works/pi-ai": "*",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
55
|
+
"@earendil-works/pi-tui": "*",
|
|
56
|
+
"typebox": "*"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.20.0",
|
|
60
|
-
"
|
|
60
|
+
"typescript": "^7.0.2"
|
|
61
61
|
}
|
|
62
62
|
}
|