supipowers 1.5.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +131 -42
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +18 -8
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +135 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +268 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +1 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1399 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import type { Platform } from "../../platform/types.js";
|
|
2
|
+
import { systemPromptText } from "../../platform/system-prompt.js";
|
|
3
|
+
import type {
|
|
4
|
+
UltraPlanCursor,
|
|
5
|
+
UltraPlanHookEventName,
|
|
6
|
+
UltraPlanHookObservation,
|
|
7
|
+
UltraPlanLaunchContext,
|
|
8
|
+
UltraPlanMutationPlan,
|
|
9
|
+
UltraPlanRuntimeTracker,
|
|
10
|
+
UltraPlanStorageResult,
|
|
11
|
+
} from "../../types.js";
|
|
12
|
+
import { LAUNCH_CONTEXT_METADATA_KEY, recoverLaunchContextFromEvent } from "./launch-context.js";
|
|
13
|
+
import { normalizeHookEvent, type NormalizeHookEventInput } from "./normalize.js";
|
|
14
|
+
import {
|
|
15
|
+
loadTracker as loadTrackerDefault,
|
|
16
|
+
saveTrackerAtomic as saveTrackerAtomicDefault,
|
|
17
|
+
} from "./tracker-storage.js";
|
|
18
|
+
import { reduce as reduceDefault, type ReducerState } from "./reducer.js";
|
|
19
|
+
import {
|
|
20
|
+
repairOnSessionShutdown as repairOnSessionShutdownDefault,
|
|
21
|
+
repairOnSessionStart as repairOnSessionStartDefault,
|
|
22
|
+
type RepairPlan,
|
|
23
|
+
} from "./repair.js";
|
|
24
|
+
import {
|
|
25
|
+
resolveSessionMigration as resolveSessionMigrationDefault,
|
|
26
|
+
type MigrationOutcome,
|
|
27
|
+
} from "./migration.js";
|
|
28
|
+
import {
|
|
29
|
+
listActiveUltraPlanExecutions,
|
|
30
|
+
readActiveUltraPlanExecutionForSession as readRegistryActiveExecutionForSession,
|
|
31
|
+
type ActiveUltraPlanExecution,
|
|
32
|
+
} from "./active-execution.js";
|
|
33
|
+
import { applyUltraPlanMutation } from "./apply-mutation.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Slice-2 hook bridge.
|
|
37
|
+
*
|
|
38
|
+
* This is the only UltraPlan module that `src/context-mode/hooks.ts` imports. It wires platform
|
|
39
|
+
* hook events into the runtime pipeline (normalize \u2192 reduce \u2192 apply) without performing any
|
|
40
|
+
* business decisions itself. Every runtime unit is injected so the bridge is fully testable in
|
|
41
|
+
* isolation and so the context-mode hook layer stays generic.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export interface UltraPlanSessionContext {
|
|
45
|
+
sessionId: string;
|
|
46
|
+
cwd: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface UltraPlanHookBridgeDeps {
|
|
50
|
+
/** Resolves the currently-focused UltraPlan session for a given platform hook context. */
|
|
51
|
+
resolveActiveSession(ctx: unknown): UltraPlanSessionContext | null;
|
|
52
|
+
|
|
53
|
+
normalize(input: NormalizeHookEventInput): UltraPlanHookObservation;
|
|
54
|
+
|
|
55
|
+
loadTracker(paths: Platform["paths"], cwd: string, sessionId: string): UltraPlanStorageResult<UltraPlanRuntimeTracker>;
|
|
56
|
+
saveTrackerAtomic(
|
|
57
|
+
paths: Platform["paths"],
|
|
58
|
+
cwd: string,
|
|
59
|
+
sessionId: string,
|
|
60
|
+
tracker: UltraPlanRuntimeTracker,
|
|
61
|
+
): UltraPlanStorageResult<string>;
|
|
62
|
+
|
|
63
|
+
reduce(state: ReducerState, action: Parameters<typeof reduceDefault>[1]): UltraPlanMutationPlan;
|
|
64
|
+
|
|
65
|
+
/** Single I/O funnel that applies a mutation plan in the required durability order. */
|
|
66
|
+
applyMutationPlan(input: ApplyMutationPlanInput): void;
|
|
67
|
+
|
|
68
|
+
repairOnSessionStart(state: { tracker: UltraPlanRuntimeTracker; manifest: null }, nowIso: string): RepairPlan;
|
|
69
|
+
repairOnSessionShutdown(state: { tracker: UltraPlanRuntimeTracker; manifest: null }, nowIso: string): RepairPlan;
|
|
70
|
+
|
|
71
|
+
resolveSessionMigration(input: {
|
|
72
|
+
paths: Platform["paths"];
|
|
73
|
+
cwd: string;
|
|
74
|
+
sessionId: string;
|
|
75
|
+
nowIso: string;
|
|
76
|
+
}): MigrationOutcome;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ApplyMutationPlanInput {
|
|
80
|
+
platform: Platform;
|
|
81
|
+
cwd: string;
|
|
82
|
+
sessionId: string;
|
|
83
|
+
observation: UltraPlanHookObservation;
|
|
84
|
+
mutationPlan: UltraPlanMutationPlan;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register the UltraPlan hook bridge on a platform. The six UltraPlan-relevant hooks are wired to
|
|
89
|
+
* the runtime pipeline; when `resolveActiveSession` returns null the handlers are no-ops.
|
|
90
|
+
*/
|
|
91
|
+
export function registerUltraPlanHookBridge(
|
|
92
|
+
platform: Platform,
|
|
93
|
+
overrides: Partial<UltraPlanHookBridgeDeps> = {},
|
|
94
|
+
): void {
|
|
95
|
+
const deps: UltraPlanHookBridgeDeps = {
|
|
96
|
+
resolveActiveSession: overrides.resolveActiveSession ?? defaultResolveActiveSession,
|
|
97
|
+
normalize: overrides.normalize ?? normalizeHookEvent,
|
|
98
|
+
loadTracker: overrides.loadTracker ?? loadTrackerDefault,
|
|
99
|
+
saveTrackerAtomic: overrides.saveTrackerAtomic ?? saveTrackerAtomicDefault,
|
|
100
|
+
reduce: overrides.reduce ?? reduceDefault,
|
|
101
|
+
applyMutationPlan: overrides.applyMutationPlan ?? applyMutationPlanDefault,
|
|
102
|
+
repairOnSessionStart: overrides.repairOnSessionStart ?? repairOnSessionStartDefault,
|
|
103
|
+
repairOnSessionShutdown: overrides.repairOnSessionShutdown ?? repairOnSessionShutdownDefault,
|
|
104
|
+
resolveSessionMigration: overrides.resolveSessionMigration ?? resolveSessionMigrationDefault,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const sessionStart = (rawEvent: unknown, ctx?: unknown) =>
|
|
108
|
+
handleSessionStart(platform, deps, rawEvent, ctx);
|
|
109
|
+
const beforeAgentStart = (rawEvent: unknown, ctx?: unknown) =>
|
|
110
|
+
handleAttemptEvent(platform, deps, "before_agent_start", rawEvent, ctx);
|
|
111
|
+
const toolCall = (rawEvent: unknown, ctx?: unknown) =>
|
|
112
|
+
handleAttemptEvent(platform, deps, "tool_call", rawEvent, ctx);
|
|
113
|
+
const toolResult = (rawEvent: unknown, ctx?: unknown) =>
|
|
114
|
+
handleAttemptEvent(platform, deps, "tool_result", rawEvent, ctx);
|
|
115
|
+
const agentEnd = (rawEvent: unknown, ctx?: unknown) =>
|
|
116
|
+
handleAttemptEvent(platform, deps, "agent_end", rawEvent, ctx);
|
|
117
|
+
const sessionShutdown = (rawEvent: unknown, ctx?: unknown) =>
|
|
118
|
+
handleSessionShutdown(platform, deps, rawEvent, ctx);
|
|
119
|
+
|
|
120
|
+
platform.on("session_start", sessionStart);
|
|
121
|
+
platform.on("before_agent_start", beforeAgentStart);
|
|
122
|
+
platform.on("tool_call", toolCall);
|
|
123
|
+
platform.on("tool_result", toolResult);
|
|
124
|
+
platform.on("agent_end", agentEnd);
|
|
125
|
+
platform.on("session_shutdown", sessionShutdown);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Hook handlers
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function handleSessionStart(
|
|
133
|
+
platform: Platform,
|
|
134
|
+
deps: UltraPlanHookBridgeDeps,
|
|
135
|
+
rawEvent: unknown,
|
|
136
|
+
ctx: unknown,
|
|
137
|
+
): void {
|
|
138
|
+
const session = resolveSessionContext(deps, platform, "session_start", rawEvent, ctx);
|
|
139
|
+
if (!session) return;
|
|
140
|
+
|
|
141
|
+
const nowIso = new Date().toISOString();
|
|
142
|
+
|
|
143
|
+
// Migration check runs before any tracker work so a partial global directory can be repaired.
|
|
144
|
+
deps.resolveSessionMigration({ paths: platform.paths, cwd: session.cwd, sessionId: session.sessionId, nowIso });
|
|
145
|
+
|
|
146
|
+
const tracker = loadTrackerOrEmpty(deps, platform, session);
|
|
147
|
+
const repair = deps.repairOnSessionStart({ tracker, manifest: null }, nowIso);
|
|
148
|
+
void repair; // Slice-2 bridge applies repair actions inside the reducer flow below.
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
const observation = deps.normalize({
|
|
152
|
+
hookEvent: "session_start",
|
|
153
|
+
sessionId: session.sessionId,
|
|
154
|
+
nowIso,
|
|
155
|
+
metadata: extractMetadata(rawEvent, ctx),
|
|
156
|
+
prompt: extractPrompt(rawEvent, ctx),
|
|
157
|
+
persistedActiveAttempt: tracker.activeAttempt,
|
|
158
|
+
payload: toRecord(rawEvent),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const plan = deps.reduce({ tracker, cursor: tracker.activeAttempt?.cursorSnapshot ?? null }, {
|
|
162
|
+
kind: "session_started",
|
|
163
|
+
observation,
|
|
164
|
+
nowIso,
|
|
165
|
+
});
|
|
166
|
+
deps.applyMutationPlan({ platform, cwd: session.cwd, sessionId: session.sessionId, observation, mutationPlan: plan });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleAttemptEvent(
|
|
170
|
+
platform: Platform,
|
|
171
|
+
deps: UltraPlanHookBridgeDeps,
|
|
172
|
+
hookEvent: UltraPlanHookEventName,
|
|
173
|
+
rawEvent: unknown,
|
|
174
|
+
ctx: unknown,
|
|
175
|
+
): void {
|
|
176
|
+
const session = resolveSessionContext(deps, platform, hookEvent, rawEvent, ctx);
|
|
177
|
+
if (!session) return;
|
|
178
|
+
|
|
179
|
+
const nowIso = new Date().toISOString();
|
|
180
|
+
const tracker = loadTrackerOrEmpty(deps, platform, session);
|
|
181
|
+
const activeExecution = resolveActiveUltraPlanExecutionForEvent(session, rawEvent, ctx);
|
|
182
|
+
const observation = deps.normalize({
|
|
183
|
+
hookEvent,
|
|
184
|
+
sessionId: session.sessionId,
|
|
185
|
+
nowIso,
|
|
186
|
+
metadata: extractMetadata(rawEvent, ctx),
|
|
187
|
+
prompt: extractPrompt(rawEvent, ctx),
|
|
188
|
+
persistedActiveAttempt: tracker.activeAttempt,
|
|
189
|
+
payload: extractPayload(rawEvent),
|
|
190
|
+
targetHint: extractTargetHint(rawEvent, ctx),
|
|
191
|
+
fallbackTargetHint: buildFallbackTargetHint(activeExecution),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const cursor: UltraPlanCursor | null = tracker.activeAttempt?.cursorSnapshot ?? null;
|
|
195
|
+
|
|
196
|
+
const plan = deps.reduce({ tracker, cursor }, reducerActionForEvent(hookEvent, observation, nowIso));
|
|
197
|
+
deps.applyMutationPlan({ platform, cwd: session.cwd, sessionId: session.sessionId, observation, mutationPlan: plan });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleSessionShutdown(
|
|
201
|
+
platform: Platform,
|
|
202
|
+
deps: UltraPlanHookBridgeDeps,
|
|
203
|
+
rawEvent: unknown,
|
|
204
|
+
ctx: unknown,
|
|
205
|
+
): void {
|
|
206
|
+
const session = resolveSessionContext(deps, platform, "session_shutdown", rawEvent, ctx);
|
|
207
|
+
if (!session) return;
|
|
208
|
+
|
|
209
|
+
const nowIso = new Date().toISOString();
|
|
210
|
+
const tracker = loadTrackerOrEmpty(deps, platform, session);
|
|
211
|
+
const repair = deps.repairOnSessionShutdown({ tracker, manifest: null }, nowIso);
|
|
212
|
+
void repair; // passed into the reducer via repair_applied actions in Slice-5 expansion.
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
const observation = deps.normalize({
|
|
216
|
+
hookEvent: "session_shutdown",
|
|
217
|
+
sessionId: session.sessionId,
|
|
218
|
+
nowIso,
|
|
219
|
+
metadata: extractMetadata(rawEvent, ctx),
|
|
220
|
+
prompt: extractPrompt(rawEvent, ctx),
|
|
221
|
+
persistedActiveAttempt: tracker.activeAttempt,
|
|
222
|
+
payload: toRecord(rawEvent),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const plan = deps.reduce({ tracker, cursor: tracker.activeAttempt?.cursorSnapshot ?? null }, {
|
|
226
|
+
kind: "session_shutdown",
|
|
227
|
+
observation,
|
|
228
|
+
nowIso,
|
|
229
|
+
});
|
|
230
|
+
deps.applyMutationPlan({ platform, cwd: session.cwd, sessionId: session.sessionId, observation, mutationPlan: plan });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Default resolvers
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
function defaultResolveActiveSession(_ctx: unknown): UltraPlanSessionContext | null {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function applyMutationPlanDefault(input: ApplyMutationPlanInput): void {
|
|
242
|
+
applyUltraPlanMutation(input);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Helpers
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
function loadTrackerOrEmpty(
|
|
250
|
+
deps: UltraPlanHookBridgeDeps,
|
|
251
|
+
platform: Platform,
|
|
252
|
+
session: UltraPlanSessionContext,
|
|
253
|
+
): UltraPlanRuntimeTracker {
|
|
254
|
+
const result = deps.loadTracker(platform.paths, session.cwd, session.sessionId);
|
|
255
|
+
if (result.ok) return result.value;
|
|
256
|
+
return {
|
|
257
|
+
version: 1,
|
|
258
|
+
sessionId: session.sessionId,
|
|
259
|
+
activeAttempt: null,
|
|
260
|
+
finalizedAttempts: [],
|
|
261
|
+
appliedFingerprints: [],
|
|
262
|
+
pendingMutation: null,
|
|
263
|
+
updatedAt: new Date().toISOString(),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function resolveSessionContext(
|
|
268
|
+
deps: UltraPlanHookBridgeDeps,
|
|
269
|
+
platform: Platform,
|
|
270
|
+
hookEvent: UltraPlanHookEventName,
|
|
271
|
+
rawEvent: unknown,
|
|
272
|
+
ctx: unknown,
|
|
273
|
+
): UltraPlanSessionContext | null {
|
|
274
|
+
const explicit = deps.resolveActiveSession(ctx);
|
|
275
|
+
if (explicit) {
|
|
276
|
+
return explicit;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return resolveFallbackSessionContext(deps, platform, hookEvent, rawEvent, ctx);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function resolveFallbackSessionContext(
|
|
283
|
+
deps: UltraPlanHookBridgeDeps,
|
|
284
|
+
platform: Platform,
|
|
285
|
+
hookEvent: UltraPlanHookEventName,
|
|
286
|
+
rawEvent: unknown,
|
|
287
|
+
ctx: unknown,
|
|
288
|
+
): UltraPlanSessionContext | null {
|
|
289
|
+
if (hookEvent === "session_start") {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const launchContext = recoverLaunchContextFromEvent({
|
|
294
|
+
metadata: extractMetadata(rawEvent, ctx),
|
|
295
|
+
prompt: extractPrompt(rawEvent, ctx),
|
|
296
|
+
persistedActiveAttempt: null,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (launchContext) {
|
|
300
|
+
const activeExecution = readActiveUltraPlanExecutionForLaunchContext(launchContext);
|
|
301
|
+
return activeExecution ? { sessionId: activeExecution.sessionId, cwd: activeExecution.cwd } : null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (hookEvent === "before_agent_start") {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const activeExecution = readUniqueActiveUltraPlanExecutionForCwd(extractCwd(rawEvent, ctx));
|
|
309
|
+
if (!activeExecution) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const tracker = deps.loadTracker(platform.paths, activeExecution.cwd, activeExecution.sessionId);
|
|
314
|
+
if (!tracker.ok || !tracker.value.activeAttempt) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!sameLaunchContext(tracker.value.activeAttempt.launchContext, activeExecution.launchContext)) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { sessionId: activeExecution.sessionId, cwd: activeExecution.cwd };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function readActiveUltraPlanExecutionForSession(
|
|
326
|
+
session: UltraPlanSessionContext,
|
|
327
|
+
): ActiveUltraPlanExecution | null {
|
|
328
|
+
return readRegistryActiveExecutionForSession(session.sessionId, session.cwd);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function readActiveUltraPlanExecutionForLaunchContext(
|
|
332
|
+
launchContext: UltraPlanLaunchContext,
|
|
333
|
+
): ActiveUltraPlanExecution | null {
|
|
334
|
+
const matches = listActiveUltraPlanExecutions().filter((execution) => sameLaunchContext(launchContext, execution.launchContext));
|
|
335
|
+
return matches.length === 1 ? matches[0]! : null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function readUniqueActiveUltraPlanExecutionForCwd(cwd: string | null): ActiveUltraPlanExecution | null {
|
|
339
|
+
if (!cwd) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const matches = listActiveUltraPlanExecutions().filter((execution) => execution.cwd === cwd);
|
|
344
|
+
return matches.length === 1 ? matches[0]! : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function resolveActiveUltraPlanExecutionForEvent(
|
|
348
|
+
session: UltraPlanSessionContext,
|
|
349
|
+
rawEvent: unknown,
|
|
350
|
+
ctx: unknown,
|
|
351
|
+
): ActiveUltraPlanExecution | null {
|
|
352
|
+
const launchContext = recoverLaunchContextFromEvent({
|
|
353
|
+
metadata: extractMetadata(rawEvent, ctx),
|
|
354
|
+
prompt: extractPrompt(rawEvent, ctx),
|
|
355
|
+
persistedActiveAttempt: null,
|
|
356
|
+
});
|
|
357
|
+
if (launchContext) {
|
|
358
|
+
const activeExecution = readActiveUltraPlanExecutionForLaunchContext(launchContext);
|
|
359
|
+
if (activeExecution?.sessionId === session.sessionId && activeExecution.cwd === session.cwd) {
|
|
360
|
+
return activeExecution;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return readActiveUltraPlanExecutionForSession(session);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function sameLaunchContext(left: UltraPlanLaunchContext, right: UltraPlanLaunchContext): boolean {
|
|
368
|
+
return left.attemptId === right.attemptId && left.attemptKey === right.attemptKey;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function extractCwd(rawEvent: unknown, ctx: unknown): string | null {
|
|
372
|
+
const rawCwd = asRecord(rawEvent)?.cwd;
|
|
373
|
+
if (typeof rawCwd === "string" && rawCwd.length > 0) {
|
|
374
|
+
return rawCwd;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const ctxCwd = asRecord(ctx)?.cwd;
|
|
378
|
+
return typeof ctxCwd === "string" && ctxCwd.length > 0 ? ctxCwd : null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
function extractTargetHint(
|
|
383
|
+
rawEvent: unknown,
|
|
384
|
+
ctx: unknown,
|
|
385
|
+
): NormalizeHookEventInput["targetHint"] | undefined {
|
|
386
|
+
const rawHint = asRecord(rawEvent)?.targetHint;
|
|
387
|
+
if (rawHint && typeof rawHint === "object" && !Array.isArray(rawHint)) {
|
|
388
|
+
return rawHint as NormalizeHookEventInput["targetHint"];
|
|
389
|
+
}
|
|
390
|
+
const ctxHint = asRecord(ctx)?.targetHint;
|
|
391
|
+
if (ctxHint && typeof ctxHint === "object" && !Array.isArray(ctxHint)) {
|
|
392
|
+
return ctxHint as NormalizeHookEventInput["targetHint"];
|
|
393
|
+
}
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function buildFallbackTargetHint(activeExecution: ActiveUltraPlanExecution | null): NormalizeHookEventInput["fallbackTargetHint"] {
|
|
398
|
+
if (!activeExecution) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
targetType: activeExecution.target.targetType,
|
|
404
|
+
stack: activeExecution.target.stack,
|
|
405
|
+
domainId: activeExecution.target.domainId,
|
|
406
|
+
level: activeExecution.target.level,
|
|
407
|
+
scenarioId: activeExecution.target.scenarioId,
|
|
408
|
+
phase: activeExecution.target.phase,
|
|
409
|
+
resolvedSlot: activeExecution.target.requiredSlot,
|
|
410
|
+
actorKind: "slot",
|
|
411
|
+
sourceAgent: "sub-agent",
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function extractPayload(rawEvent: unknown): Record<string, unknown> {
|
|
416
|
+
const payload = toRecord(rawEvent);
|
|
417
|
+
const signalPayload = asRecord(asRecord(rawEvent)?.details)?.payload;
|
|
418
|
+
if (!signalPayload) {
|
|
419
|
+
return payload;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
...payload,
|
|
424
|
+
payload: signalPayload,
|
|
425
|
+
summary: typeof payload.summary === "string" ? payload.summary : payload.payloadSummary,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function reducerActionForEvent(
|
|
430
|
+
hookEvent: UltraPlanHookEventName,
|
|
431
|
+
observation: UltraPlanHookObservation,
|
|
432
|
+
nowIso: string,
|
|
433
|
+
): Parameters<typeof reduceDefault>[1] {
|
|
434
|
+
switch (hookEvent) {
|
|
435
|
+
case "before_agent_start": {
|
|
436
|
+
// The bridge extracts a launch context (or fails correlation at normalization time).
|
|
437
|
+
// When correlation failed, pass an attempt_started action anyway so the reducer emits a
|
|
438
|
+
// blocker plan; the observation's correlationFailure carries the reason.
|
|
439
|
+
const launchContext = {
|
|
440
|
+
attemptId: observation.attemptId ?? "unknown",
|
|
441
|
+
attemptKey: observation.attemptKey ?? "unknown",
|
|
442
|
+
sourceAgent: observation.sourceAgent,
|
|
443
|
+
launchedAt: observation.occurredAt,
|
|
444
|
+
};
|
|
445
|
+
return { kind: "attempt_started", observation, launchContext };
|
|
446
|
+
}
|
|
447
|
+
case "tool_call":
|
|
448
|
+
case "tool_result":
|
|
449
|
+
return { kind: "observation_staged", observation };
|
|
450
|
+
case "agent_end":
|
|
451
|
+
return { kind: "attempt_finalized", observation, nowIso };
|
|
452
|
+
default:
|
|
453
|
+
return { kind: "observation_staged", observation };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function extractMetadata(rawEvent: unknown, ctx: unknown): Record<string, unknown> | null {
|
|
458
|
+
const rawMeta = asRecord(rawEvent)?.metadata as Record<string, unknown> | undefined;
|
|
459
|
+
const ctxMeta = asRecord(ctx)?.metadata as Record<string, unknown> | undefined;
|
|
460
|
+
if (rawMeta && ctxMeta) return { ...ctxMeta, ...rawMeta };
|
|
461
|
+
return rawMeta ?? ctxMeta ?? null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function extractPrompt(rawEvent: unknown, ctx: unknown): string | null {
|
|
465
|
+
const rawPrompt = asRecord(rawEvent)?.prompt;
|
|
466
|
+
if (typeof rawPrompt === "string") return rawPrompt;
|
|
467
|
+
const rawSystemPrompt = systemPromptText(asRecord(rawEvent)?.systemPrompt);
|
|
468
|
+
if (rawSystemPrompt.length > 0) return rawSystemPrompt;
|
|
469
|
+
const ctxPrompt = asRecord(ctx)?.prompt;
|
|
470
|
+
if (typeof ctxPrompt === "string") return ctxPrompt;
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function toRecord(value: unknown): Record<string, unknown> {
|
|
475
|
+
return asRecord(value) ?? {};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
479
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
480
|
+
return value as Record<string, unknown>;
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Re-export useful carrier bits for hosts that need them.
|
|
486
|
+
export { LAUNCH_CONTEXT_METADATA_KEY, recoverLaunchContextFromEvent };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
ULTRAPLAN_AGENT_SLOT_NAMES,
|
|
4
|
+
ULTRAPLAN_CURSOR_TARGETS,
|
|
5
|
+
ULTRAPLAN_EXECUTION_PHASES,
|
|
6
|
+
ULTRAPLAN_LEVELS,
|
|
7
|
+
ULTRAPLAN_STACKS,
|
|
8
|
+
} from "../contracts.js";
|
|
9
|
+
import type {
|
|
10
|
+
UltraPlanAttemptRecord,
|
|
11
|
+
UltraPlanLaunchContext,
|
|
12
|
+
UltraPlanSourceAgent,
|
|
13
|
+
} from "../../types.js";
|
|
14
|
+
import { isUltraPlanLaunchContext } from "../contracts.js";
|
|
15
|
+
import type { UltraPlanTargetHint } from "./normalize.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Slice-2 cross-hook correlation carrier.
|
|
19
|
+
*
|
|
20
|
+
* The runtime spec §cross-hook carrier requires that every slot-backed launch embed a launch
|
|
21
|
+
* context into both structured metadata and the prompt/assignment text so later hooks can recover
|
|
22
|
+
* the attempt identity even when the platform drops one carrier. This module owns minting,
|
|
23
|
+
* injecting, and recovering those carriers. It is pure — no I/O.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** The metadata key the bridge uses to carry a structured launch context through platform hooks. */
|
|
27
|
+
export const LAUNCH_CONTEXT_METADATA_KEY = "ultraplanLaunchContext" as const;
|
|
28
|
+
/** Prompt marker line form: `ULTRAPLAN_LAUNCH_CONTEXT=<json>`. */
|
|
29
|
+
export const LAUNCH_CONTEXT_PROMPT_MARKER = "ULTRAPLAN_LAUNCH_CONTEXT" as const;
|
|
30
|
+
/** Structured metadata key for the execution target hint carried alongside launch context. */
|
|
31
|
+
export const TARGET_HINT_METADATA_KEY = "ultraplanTargetHint" as const;
|
|
32
|
+
/** Prompt marker line form: `ULTRAPLAN_TARGET_HINT=<json>`. */
|
|
33
|
+
export const TARGET_HINT_PROMPT_MARKER = "ULTRAPLAN_TARGET_HINT" as const;
|
|
34
|
+
|
|
35
|
+
export interface MintLaunchContextInput {
|
|
36
|
+
attemptKey: string;
|
|
37
|
+
sourceAgent: UltraPlanSourceAgent;
|
|
38
|
+
nowIso: string;
|
|
39
|
+
/**
|
|
40
|
+
* When set, the minted launch context inherits the parent attempt's `attemptId` and
|
|
41
|
+
* `attemptKey`. Slice 2 uses this so a nested sub-agent shares its parent's attempt identity
|
|
42
|
+
* instead of minting a child attempt. Retries do NOT pass `inheritFrom` — they mint a fresh id.
|
|
43
|
+
*/
|
|
44
|
+
inheritFrom?: UltraPlanLaunchContext | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function mintLaunchContext(input: MintLaunchContextInput): UltraPlanLaunchContext {
|
|
48
|
+
const { attemptKey, sourceAgent, nowIso, inheritFrom } = input;
|
|
49
|
+
|
|
50
|
+
if (inheritFrom && isUltraPlanLaunchContext(inheritFrom)) {
|
|
51
|
+
return {
|
|
52
|
+
attemptId: inheritFrom.attemptId,
|
|
53
|
+
attemptKey: inheritFrom.attemptKey,
|
|
54
|
+
sourceAgent,
|
|
55
|
+
launchedAt: nowIso,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
attemptId: randomUUID(),
|
|
61
|
+
attemptKey,
|
|
62
|
+
sourceAgent,
|
|
63
|
+
launchedAt: nowIso,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Inject the exact `ULTRAPLAN_LAUNCH_CONTEXT=<json>` line into a prompt/assignment string.
|
|
69
|
+
*
|
|
70
|
+
* Idempotent: if the prompt already contains a marker line for a context with the same payload,
|
|
71
|
+
* a second injection does not duplicate it. Different payloads replace the prior marker so the
|
|
72
|
+
* latest intent wins.
|
|
73
|
+
*/
|
|
74
|
+
export function injectLaunchContextIntoPrompt(prompt: string, ctx: UltraPlanLaunchContext): string {
|
|
75
|
+
return injectJsonCarrierIntoPrompt(prompt, LAUNCH_CONTEXT_PROMPT_MARKER, ctx);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Inject the exact `ULTRAPLAN_TARGET_HINT=<json>` line into a prompt/assignment string.
|
|
80
|
+
* Mirrors the launch-context carrier semantics so later hooks can recover the execution target
|
|
81
|
+
* even when platform metadata is dropped.
|
|
82
|
+
*/
|
|
83
|
+
export function injectTargetHintIntoPrompt(prompt: string, hint: UltraPlanTargetHint): string {
|
|
84
|
+
return injectJsonCarrierIntoPrompt(prompt, TARGET_HINT_PROMPT_MARKER, hint);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface RecoverLaunchContextInput {
|
|
88
|
+
/** Structured platform metadata carried alongside the event. May be null. */
|
|
89
|
+
metadata: Record<string, unknown> | null | undefined;
|
|
90
|
+
/** Raw prompt / assignment / system prompt text that may carry the marker line. May be null. */
|
|
91
|
+
prompt: string | null | undefined;
|
|
92
|
+
/** The currently-persisted active attempt from the tracker, used as a last-resort. May be null. */
|
|
93
|
+
persistedActiveAttempt: UltraPlanAttemptRecord | null | undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/****
|
|
97
|
+
* Recover a launch context from the available carriers.
|
|
98
|
+
*
|
|
99
|
+
* Priority (spec §cross-hook carrier, line 464):
|
|
100
|
+
* 1. Structured metadata under the `ultraplanLaunchContext` key.
|
|
101
|
+
* 2. `ULTRAPLAN_LAUNCH_CONTEXT=<json>` line inside the prompt/assignment text.
|
|
102
|
+
* 3. The persisted active attempt record's own launch context, as a last-resort consistency check.
|
|
103
|
+
* 4. Otherwise return null; the normalization layer surfaces this as a correlation failure.
|
|
104
|
+
*/
|
|
105
|
+
export function recoverLaunchContextFromEvent(input: RecoverLaunchContextInput): UltraPlanLaunchContext | null {
|
|
106
|
+
const fromMetadata = readMetadataCarrier(input.metadata);
|
|
107
|
+
if (fromMetadata) return fromMetadata;
|
|
108
|
+
|
|
109
|
+
const fromPrompt = recoverLaunchContextFromPrompt(input.prompt);
|
|
110
|
+
if (fromPrompt) return fromPrompt;
|
|
111
|
+
|
|
112
|
+
if (input.persistedActiveAttempt && isUltraPlanLaunchContext(input.persistedActiveAttempt.launchContext)) {
|
|
113
|
+
return input.persistedActiveAttempt.launchContext;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function recoverLaunchContextFromPrompt(prompt: string | null | undefined): UltraPlanLaunchContext | null {
|
|
119
|
+
const candidate = readJsonPromptCarrier(prompt, LAUNCH_CONTEXT_PROMPT_MARKER);
|
|
120
|
+
return isUltraPlanLaunchContext(candidate) ? candidate : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function recoverTargetHintFromPrompt(prompt: string | null | undefined): UltraPlanTargetHint | null {
|
|
124
|
+
const candidate = readJsonPromptCarrier(prompt, TARGET_HINT_PROMPT_MARKER);
|
|
125
|
+
return isUltraPlanTargetHint(candidate) ? candidate : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- internal helpers ------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
function readMetadataCarrier(metadata: RecoverLaunchContextInput["metadata"]): UltraPlanLaunchContext | null {
|
|
131
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
132
|
+
const candidate = (metadata as Record<string, unknown>)[LAUNCH_CONTEXT_METADATA_KEY];
|
|
133
|
+
return isUltraPlanLaunchContext(candidate) ? candidate : null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function injectJsonCarrierIntoPrompt(prompt: string, marker: string, payload: unknown): string {
|
|
137
|
+
const line = buildMarkerLine(marker, payload);
|
|
138
|
+
if (prompt.includes(line)) {
|
|
139
|
+
return prompt;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const existingLineRegex = new RegExp(`^${escapeRegExp(marker)}=.*$`, "m");
|
|
143
|
+
if (existingLineRegex.test(prompt)) {
|
|
144
|
+
return prompt.replace(existingLineRegex, line);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const separator = prompt.endsWith("\n") || prompt.length === 0 ? "" : "\n";
|
|
148
|
+
return `${prompt}${separator}\n${line}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function readJsonPromptCarrier(prompt: string | null | undefined, marker: string): unknown {
|
|
152
|
+
if (typeof prompt !== "string" || prompt.length === 0) return null;
|
|
153
|
+
const prefix = `${marker}=`;
|
|
154
|
+
for (const line of prompt.split(/\r?\n/)) {
|
|
155
|
+
if (!line.startsWith(prefix)) continue;
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(line.slice(prefix.length));
|
|
158
|
+
} catch {
|
|
159
|
+
// Ignore malformed JSON and continue scanning for a valid marker.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildMarkerLine(marker: string, payload: unknown): string {
|
|
167
|
+
return `${marker}=${JSON.stringify(payload)}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isUltraPlanTargetHint(value: unknown): value is UltraPlanTargetHint {
|
|
171
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const candidate = value as Record<string, unknown>;
|
|
176
|
+
return isOptionalEnum(candidate.targetType, ULTRAPLAN_CURSOR_TARGETS)
|
|
177
|
+
&& isOptionalNullableEnum(candidate.stack, ULTRAPLAN_STACKS)
|
|
178
|
+
&& isOptionalNullableString(candidate.domainId)
|
|
179
|
+
&& isOptionalNullableEnum(candidate.level, ULTRAPLAN_LEVELS)
|
|
180
|
+
&& isOptionalNullableString(candidate.scenarioId)
|
|
181
|
+
&& isOptionalEnum(candidate.phase, ULTRAPLAN_EXECUTION_PHASES)
|
|
182
|
+
&& isOptionalNullableEnum(candidate.resolvedSlot, ULTRAPLAN_AGENT_SLOT_NAMES)
|
|
183
|
+
&& isOptionalEnum(candidate.actorKind, ["slot"] as const)
|
|
184
|
+
&& isOptionalEnum(candidate.sourceAgent, ["sub-agent"] as const);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isOptionalEnum<TValue extends string>(
|
|
188
|
+
value: unknown,
|
|
189
|
+
allowed: readonly TValue[],
|
|
190
|
+
): value is TValue | undefined {
|
|
191
|
+
return value === undefined || (typeof value === "string" && allowed.includes(value as TValue));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isOptionalNullableEnum<TValue extends string>(
|
|
195
|
+
value: unknown,
|
|
196
|
+
allowed: readonly TValue[],
|
|
197
|
+
): value is TValue | null | undefined {
|
|
198
|
+
return value === null || isOptionalEnum(value, allowed);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isOptionalNullableString(value: unknown): value is string | null | undefined {
|
|
202
|
+
return value === undefined || value === null || (typeof value === "string" && value.length > 0);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function escapeRegExp(value: string): string {
|
|
206
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
|
+
}
|