@kontourai/flow-agents 3.3.0 → 3.4.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/.github/workflows/add-to-project.yml +15 -0
- package/.github/workflows/ci.yml +161 -0
- package/CHANGELOG.md +41 -0
- package/CONTEXT.md +5 -1
- package/README.md +19 -8
- package/build/src/builder-flow-run-adapter.d.ts +80 -0
- package/build/src/builder-flow-run-adapter.js +241 -0
- package/build/src/builder-flow-runtime.d.ts +16 -0
- package/build/src/builder-flow-runtime.js +290 -0
- package/build/src/cli/builder-run.d.ts +1 -0
- package/build/src/cli/builder-run.js +27 -0
- package/build/src/cli/effective-backlog-settings.js +70 -2
- package/build/src/cli/init.d.ts +34 -0
- package/build/src/cli/init.js +341 -61
- package/build/src/cli/kit.js +55 -12
- package/build/src/cli/pull-work-provider.js +346 -5
- package/build/src/cli/skill-drift-check.d.ts +1 -0
- package/build/src/cli/skill-drift-check.js +165 -0
- package/build/src/cli/telemetry-doctor.d.ts +37 -0
- package/build/src/cli/telemetry-doctor.js +53 -6
- package/build/src/cli/validate-hook-influence.js +37 -7
- package/build/src/cli/workflow-sidecar.d.ts +93 -8
- package/build/src/cli/workflow-sidecar.js +1175 -158
- package/build/src/cli.js +5 -0
- package/build/src/flow-kit/validate.d.ts +54 -34
- package/build/src/flow-kit/validate.js +237 -26
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/console-connect-options.d.ts +97 -0
- package/build/src/lib/console-connect-options.js +199 -0
- package/build/src/lib/console-telemetry-validate.d.ts +49 -0
- package/build/src/lib/console-telemetry-validate.js +91 -0
- package/build/src/lib/flow-resolver.d.ts +54 -1
- package/build/src/lib/flow-resolver.js +112 -5
- package/build/src/lib/fs.d.ts +17 -0
- package/build/src/lib/fs.js +172 -0
- package/build/src/lib/local-artifact-root.d.ts +44 -1
- package/build/src/lib/local-artifact-root.js +131 -3
- package/build/src/runtime-adapters.d.ts +39 -3
- package/build/src/runtime-adapters.js +77 -31
- package/build/src/tools/build-universal-bundles.js +40 -2
- package/build/src/tools/codex-agent-routing.d.ts +2 -0
- package/build/src/tools/codex-agent-routing.js +49 -0
- package/build/src/tools/generate-context-map.js +1 -0
- package/build/src/tools/validate-source-tree.js +27 -1
- package/context/scripts/hooks/lib/kit-catalog.js +235 -0
- package/context/scripts/hooks/lib/runnable-command.js +177 -0
- package/context/scripts/hooks/stop-goal-fit.js +278 -48
- package/context/scripts/hooks/workflow-steering.js +121 -21
- package/context/scripts/package.json +3 -0
- package/context/scripts/telemetry/install-console-config.sh +25 -4
- package/context/scripts/telemetry/lib/config.sh +102 -12
- package/context/scripts/telemetry/lib/pricing.sh +50 -0
- package/context/scripts/telemetry/lib/session.sh +3 -0
- package/context/scripts/telemetry/lib/transport.sh +87 -0
- package/context/scripts/telemetry/lib/usage.sh +205 -4
- package/context/scripts/telemetry/telemetry.conf +6 -0
- package/context/scripts/telemetry/telemetry.sh +48 -0
- package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
- package/docs/agent-usage-feedback-loop.md +35 -0
- package/docs/architecture-engine-and-kits.md +110 -0
- package/docs/context-map.md +2 -0
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +3 -1
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +3 -0
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +19 -9
- package/docs/integrations/flow-agents-console.md +167 -0
- package/docs/kit-authoring-guide.md +52 -21
- package/docs/spec/builder-flow-runtime.md +80 -0
- package/docs/spec/runtime-hook-surface.md +45 -1
- package/docs/specs/economics-record-contract.md +270 -0
- package/docs/specs/harness-capability-matrix.md +74 -0
- package/docs/specs/learning-review-proposals-contract.md +340 -0
- package/docs/specs/routing-efficiency-review.md +59 -0
- package/docs/verifiable-trust.md +74 -25
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +1 -0
- package/evals/ci/run-baseline.sh +72 -4
- package/evals/fixtures/economics/acceptance.json +12 -0
- package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
- package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
- package/evals/fixtures/economics/critique.json +22 -0
- package/evals/fixtures/economics/expected-record.json +71 -0
- package/evals/fixtures/economics/session-usage-event.json +1 -0
- package/evals/fixtures/economics/state.json +11 -0
- package/evals/fixtures/economics/transcript.jsonl +3 -0
- package/evals/fixtures/hook-influence/cases.json +7 -7
- package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
- package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
- package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
- package/evals/integration/test_builder_entry_enforcement.sh +241 -0
- package/evals/integration/test_builder_step_producers.sh +18 -10
- package/evals/integration/test_bundle_install.sh +172 -0
- package/evals/integration/test_console_tenant_isolation.sh +167 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
- package/evals/integration/test_dual_emit_flow_step.sh +10 -4
- package/evals/integration/test_economics_record.sh +674 -0
- package/evals/integration/test_effective_backlog_settings.sh +1 -1
- package/evals/integration/test_evidence_capture_hook.sh +17 -2
- package/evals/integration/test_exemption_usage_review.sh +198 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +83 -0
- package/evals/integration/test_flowdef_session_activation.sh +0 -1
- package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
- package/evals/integration/test_gate_lockdown.sh +7 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
- package/evals/integration/test_goal_fit_hook.sh +2031 -0
- package/evals/integration/test_hook_category_behaviors.sh +8 -1
- package/evals/integration/test_hook_influence_cases.sh +25 -1
- package/evals/integration/test_install_merge.sh +227 -2
- package/evals/integration/test_kit_conformance_levels.sh +6 -6
- package/evals/integration/test_learning_review_proposals.sh +329 -0
- package/evals/integration/test_liveness_conflict_injection.sh +26 -22
- package/evals/integration/test_liveness_console_relay.sh +166 -0
- package/evals/integration/test_liveness_heartbeat.sh +17 -17
- package/evals/integration/test_liveness_worktree_root.sh +575 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
- package/evals/integration/test_publish_delivery.sh +331 -1
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_record_check.sh +378 -0
- package/evals/integration/test_routing_efficiency.sh +71 -0
- package/evals/integration/test_runtime_adapter_activation.sh +28 -0
- package/evals/integration/test_session_resume_roundtrip.sh +16 -19
- package/evals/integration/test_skill_drift_check.sh +870 -0
- package/evals/integration/test_telemetry.sh +445 -0
- package/evals/integration/test_telemetry_doctor.sh +66 -0
- package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
- package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
- package/evals/integration/test_usage_cost.sh +61 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
- package/evals/integration/test_workflow_steering_hook.sh +157 -16
- package/evals/integration/test_workspace_settings.sh +176 -0
- package/evals/lib/env.sh +26 -0
- package/evals/lib/node.sh +8 -0
- package/evals/run.sh +29 -0
- package/evals/static/test_ci_integration_coverage.sh +115 -0
- package/evals/static/test_declared_scope_forms_documented.sh +114 -0
- package/evals/static/test_universal_bundles.sh +34 -0
- package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
- package/evals/static/test_workflow_skills.sh +1 -1
- package/kits/builder/flows/build.flow.json +9 -18
- package/kits/builder/flows/publish-learn.flow.json +5 -1
- package/kits/builder/kit.json +120 -0
- package/kits/builder/skills/deliver/SKILL.md +42 -0
- package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -0
- package/kits/builder/skills/learning-review/SKILL.md +51 -0
- package/kits/builder/skills/plan-work/SKILL.md +17 -20
- package/kits/builder/skills/pull-work/SKILL.md +21 -0
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +35 -7
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/kit.json +14 -0
- package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
- package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
- package/package.json +2 -2
- package/packaging/manifest.json +29 -0
- package/schemas/backlog-provider-settings.schema.json +13 -0
- package/schemas/workflow-state.schema.json +44 -0
- package/scripts/README.md +4 -0
- package/scripts/check-content-boundary.cjs +8 -1
- package/scripts/ci/trust-reconcile.js +136 -0
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/codex-exit-code.js +316 -0
- package/scripts/hooks/lib/kit-catalog.js +235 -0
- package/scripts/hooks/lib/liveness-write.js +28 -1
- package/scripts/hooks/lib/local-artifact-paths.js +97 -1
- package/scripts/hooks/lib/runnable-command.js +177 -0
- package/scripts/hooks/lib/skill-drift.js +350 -0
- package/scripts/hooks/stop-goal-fit.js +278 -48
- package/scripts/hooks/workflow-steering.js +121 -21
- package/scripts/install-codex-home.sh +97 -47
- package/scripts/install-merge.js +72 -14
- package/scripts/install-owned-files.js +178 -0
- package/scripts/liveness/relay.sh +84 -0
- package/scripts/telemetry/economics-record.schema.json +145 -0
- package/scripts/telemetry/economics-record.sh +331 -0
- package/scripts/telemetry/install-console-config.sh +25 -4
- package/scripts/telemetry/learning-review-decide.sh +124 -0
- package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
- package/scripts/telemetry/learning-review-proposals.sh +484 -0
- package/scripts/telemetry/lib/config.sh +102 -12
- package/scripts/telemetry/lib/pricing.sh +14 -6
- package/scripts/telemetry/lib/session.sh +3 -0
- package/scripts/telemetry/lib/transport.sh +133 -15
- package/scripts/telemetry/lib/usage.sh +121 -28
- package/scripts/telemetry/routing-efficiency.sh +0 -0
- package/scripts/telemetry/telemetry.conf +6 -0
- package/scripts/telemetry/telemetry.sh +48 -0
- package/src/builder-flow-run-adapter.ts +357 -0
- package/src/builder-flow-runtime.ts +348 -0
- package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
- package/src/cli/builder-flow-runtime.test.mjs +213 -0
- package/src/cli/builder-run.ts +28 -0
- package/src/cli/codex-agent-routing.test.mjs +44 -0
- package/src/cli/codex-exit-code.test.mjs +207 -0
- package/src/cli/console-connect-options.test.mjs +329 -0
- package/src/cli/console-telemetry-validate.test.mjs +157 -0
- package/src/cli/effective-backlog-settings.ts +68 -2
- package/src/cli/flow-resolver-composition.test.mjs +72 -0
- package/src/cli/init.test.mjs +161 -0
- package/src/cli/init.ts +407 -62
- package/src/cli/kit-metadata-security.test.mjs +443 -0
- package/src/cli/kit.ts +50 -12
- package/src/cli/pull-work-provider.ts +377 -3
- package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
- package/src/cli/skill-drift-check.ts +196 -0
- package/src/cli/telemetry-doctor.test.mjs +53 -0
- package/src/cli/telemetry-doctor.ts +50 -7
- package/src/cli/validate-hook-influence.ts +37 -6
- package/src/cli/workflow-sidecar.ts +1150 -151
- package/src/cli.ts +5 -0
- package/src/flow-kit/validate.ts +277 -38
- package/src/index.ts +19 -0
- package/src/lib/console-connect-options.ts +261 -0
- package/src/lib/console-telemetry-validate.ts +88 -0
- package/src/lib/flow-resolver.ts +117 -4
- package/src/lib/fs.ts +160 -0
- package/src/lib/local-artifact-root.ts +129 -3
- package/src/runtime-adapters.ts +113 -33
- package/src/tools/build-universal-bundles.ts +36 -2
- package/src/tools/codex-agent-routing.ts +48 -0
- package/src/tools/generate-context-map.ts +1 -0
- package/src/tools/validate-source-tree.ts +26 -1
package/build/src/lib/fs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
3
4
|
export function readJson(file) {
|
|
4
5
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
5
6
|
}
|
|
@@ -7,6 +8,123 @@ export function writeJson(file, value) {
|
|
|
7
8
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
8
9
|
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
9
10
|
}
|
|
11
|
+
function existingAncestor(file) {
|
|
12
|
+
let current = path.resolve(file);
|
|
13
|
+
const missing = [];
|
|
14
|
+
while (!fs.existsSync(current)) {
|
|
15
|
+
missing.unshift(path.basename(current));
|
|
16
|
+
const parent = path.dirname(current);
|
|
17
|
+
if (parent === current)
|
|
18
|
+
break;
|
|
19
|
+
current = parent;
|
|
20
|
+
}
|
|
21
|
+
return { ancestor: current, missing };
|
|
22
|
+
}
|
|
23
|
+
/** Resolve a possibly-missing path through its nearest existing ancestor. */
|
|
24
|
+
export function canonicalProspectivePath(file) {
|
|
25
|
+
const { ancestor, missing } = existingAncestor(file);
|
|
26
|
+
return path.resolve(fs.realpathSync(ancestor), ...missing);
|
|
27
|
+
}
|
|
28
|
+
function isContained(root, target) {
|
|
29
|
+
const relative = path.relative(root, target);
|
|
30
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
31
|
+
}
|
|
32
|
+
/** Reject equality and either-direction containment after canonical resolution. */
|
|
33
|
+
export function assertPathsDisjoint(first, second) {
|
|
34
|
+
const firstReal = canonicalProspectivePath(first);
|
|
35
|
+
const secondReal = canonicalProspectivePath(second);
|
|
36
|
+
if (isContained(firstReal, secondReal) || isContained(secondReal, firstReal)) {
|
|
37
|
+
throw new Error(`refusing overlapping paths: ${firstReal} and ${secondReal}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a directory below root without following any symlink that already
|
|
42
|
+
* exists at or below the root. Ambient aliases above root (for example macOS
|
|
43
|
+
* /tmp -> /private/tmp) are resolved once and are not treated as destination
|
|
44
|
+
* components.
|
|
45
|
+
*/
|
|
46
|
+
export function ensureSafeDirectory(root, directory) {
|
|
47
|
+
const rootPath = path.resolve(root);
|
|
48
|
+
if (fs.existsSync(rootPath)) {
|
|
49
|
+
const rootStat = fs.lstatSync(rootPath);
|
|
50
|
+
if (rootStat.isSymbolicLink())
|
|
51
|
+
throw new Error(`refusing symlink destination root: ${rootPath}`);
|
|
52
|
+
if (!rootStat.isDirectory())
|
|
53
|
+
throw new Error(`destination root is not a directory: ${rootPath}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const { ancestor, missing } = existingAncestor(rootPath);
|
|
57
|
+
let current = fs.realpathSync(ancestor);
|
|
58
|
+
for (const part of missing) {
|
|
59
|
+
current = path.join(current, part);
|
|
60
|
+
if (fs.existsSync(current)) {
|
|
61
|
+
const stat = fs.lstatSync(current);
|
|
62
|
+
if (stat.isSymbolicLink())
|
|
63
|
+
throw new Error(`refusing to write through symlink: ${current}`);
|
|
64
|
+
if (!stat.isDirectory())
|
|
65
|
+
throw new Error(`destination component is not a directory: ${current}`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
fs.mkdirSync(current);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const rootReal = fs.realpathSync(rootPath);
|
|
73
|
+
const target = path.resolve(directory);
|
|
74
|
+
const prospective = canonicalProspectivePath(target);
|
|
75
|
+
if (!isContained(rootReal, prospective))
|
|
76
|
+
throw new Error(`path escapes root: ${target}`);
|
|
77
|
+
const relative = path.relative(rootReal, prospective);
|
|
78
|
+
let current = rootReal;
|
|
79
|
+
for (const part of relative.split(path.sep).filter(Boolean)) {
|
|
80
|
+
current = path.join(current, part);
|
|
81
|
+
if (fs.existsSync(current)) {
|
|
82
|
+
const stat = fs.lstatSync(current);
|
|
83
|
+
if (stat.isSymbolicLink())
|
|
84
|
+
throw new Error(`refusing to write through symlink: ${current}`);
|
|
85
|
+
if (!stat.isDirectory())
|
|
86
|
+
throw new Error(`destination component is not a directory: ${current}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
fs.mkdirSync(current);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return prospective;
|
|
93
|
+
}
|
|
94
|
+
function assertSafeFileTarget(root, file) {
|
|
95
|
+
const target = path.resolve(file);
|
|
96
|
+
ensureSafeDirectory(root, path.dirname(target));
|
|
97
|
+
const rootReal = fs.realpathSync(path.resolve(root));
|
|
98
|
+
const prospective = canonicalProspectivePath(target);
|
|
99
|
+
if (!isContained(rootReal, prospective) || prospective === rootReal)
|
|
100
|
+
throw new Error(`path escapes root: ${target}`);
|
|
101
|
+
if (fs.existsSync(target)) {
|
|
102
|
+
const stat = fs.lstatSync(target);
|
|
103
|
+
if (stat.isSymbolicLink())
|
|
104
|
+
throw new Error(`refusing to replace symlink: ${target}`);
|
|
105
|
+
if (!stat.isFile())
|
|
106
|
+
throw new Error(`destination is not a regular file: ${target}`);
|
|
107
|
+
}
|
|
108
|
+
return target;
|
|
109
|
+
}
|
|
110
|
+
/** Atomically replace a regular file without following a final-component symlink. */
|
|
111
|
+
export function atomicWriteFile(root, file, data) {
|
|
112
|
+
const target = assertSafeFileTarget(root, file);
|
|
113
|
+
const temp = path.join(path.dirname(target), `.${path.basename(target)}.flow-agents-${process.pid}-${crypto.randomBytes(6).toString("hex")}.tmp`);
|
|
114
|
+
try {
|
|
115
|
+
fs.writeFileSync(temp, data, { flag: "wx" });
|
|
116
|
+
fs.renameSync(temp, target);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
fs.rmSync(temp, { force: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function atomicWriteJson(root, file, value) {
|
|
123
|
+
atomicWriteFile(root, file, `${JSON.stringify(value, null, 2)}\n`);
|
|
124
|
+
}
|
|
125
|
+
export function atomicCopyFile(root, source, target) {
|
|
126
|
+
atomicWriteFile(root, target, fs.readFileSync(source));
|
|
127
|
+
}
|
|
10
128
|
export function copyDir(src, dest) {
|
|
11
129
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
12
130
|
if (fs.existsSync(dest))
|
|
@@ -16,6 +134,60 @@ export function copyDir(src, dest) {
|
|
|
16
134
|
filter: (source) => !source.split(path.sep).some((part) => [".git", "__pycache__", ".pytest_cache"].includes(part)),
|
|
17
135
|
});
|
|
18
136
|
}
|
|
137
|
+
function copiedTreeDigest(root) {
|
|
138
|
+
const hash = crypto.createHash("sha256");
|
|
139
|
+
for (const file of walkFiles(root)) {
|
|
140
|
+
const rel = path.relative(root, file).split(path.sep).join("/");
|
|
141
|
+
if (rel.split("/").some((part) => [".git", "__pycache__", ".pytest_cache"].includes(part)))
|
|
142
|
+
continue;
|
|
143
|
+
hash.update(rel);
|
|
144
|
+
hash.update("\0");
|
|
145
|
+
hash.update(fs.readFileSync(file));
|
|
146
|
+
hash.update("\0");
|
|
147
|
+
}
|
|
148
|
+
return hash.digest("hex");
|
|
149
|
+
}
|
|
150
|
+
/** Copy through a verified sibling directory, then swap with rollback. */
|
|
151
|
+
export function copyDirAtomic(root, src, dest) {
|
|
152
|
+
assertPathsDisjoint(src, dest);
|
|
153
|
+
const parent = ensureSafeDirectory(root, path.dirname(dest));
|
|
154
|
+
if (fs.existsSync(dest)) {
|
|
155
|
+
const stat = fs.lstatSync(dest);
|
|
156
|
+
if (stat.isSymbolicLink())
|
|
157
|
+
throw new Error(`refusing to replace symlink: ${dest}`);
|
|
158
|
+
if (!stat.isDirectory())
|
|
159
|
+
throw new Error(`destination is not a directory: ${dest}`);
|
|
160
|
+
}
|
|
161
|
+
const nonce = `${process.pid}-${crypto.randomBytes(6).toString("hex")}`;
|
|
162
|
+
const temp = path.join(parent, `.${path.basename(dest)}.flow-agents-${nonce}.tmp`);
|
|
163
|
+
const backup = path.join(parent, `.${path.basename(dest)}.flow-agents-${nonce}.old`);
|
|
164
|
+
try {
|
|
165
|
+
fs.cpSync(src, temp, {
|
|
166
|
+
recursive: true,
|
|
167
|
+
errorOnExist: true,
|
|
168
|
+
force: false,
|
|
169
|
+
filter: (source) => !source.split(path.sep).some((part) => [".git", "__pycache__", ".pytest_cache"].includes(part)),
|
|
170
|
+
});
|
|
171
|
+
if (copiedTreeDigest(src) !== copiedTreeDigest(temp))
|
|
172
|
+
throw new Error(`copied kit verification failed: ${src}`);
|
|
173
|
+
if (fs.existsSync(dest))
|
|
174
|
+
fs.renameSync(dest, backup);
|
|
175
|
+
try {
|
|
176
|
+
fs.renameSync(temp, dest);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (fs.existsSync(backup))
|
|
180
|
+
fs.renameSync(backup, dest);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
187
|
+
if (fs.existsSync(backup) && !fs.existsSync(dest))
|
|
188
|
+
fs.renameSync(backup, dest);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
19
191
|
export function assertPathContained(root, target) {
|
|
20
192
|
const rootReal = fs.realpathSync(root);
|
|
21
193
|
let existingParent = path.dirname(target);
|
|
@@ -11,10 +11,53 @@ export declare const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
|
11
11
|
* and the durable per-destination install record root
|
|
12
12
|
* (`DURABLE_FLOW_AGENTS_DIR`, i.e. `.flow-agents`).
|
|
13
13
|
*/
|
|
14
|
-
export declare function defaultCodexHome(): string;
|
|
14
|
+
export declare function defaultCodexHome(env?: NodeJS.ProcessEnv, homedir?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* #357: resolve the SHARED, git-common-dir-anchored `.kontourai/flow-agents` root for `cwd`.
|
|
17
|
+
*
|
|
18
|
+
* `git rev-parse --git-common-dir` returns the ONE `.git` directory shared by every worktree
|
|
19
|
+
* of a repository (in the primary checkout this is simply `.git` relative to cwd; in a linked
|
|
20
|
+
* worktree it resolves to the primary checkout's real `.git` directory, e.g.
|
|
21
|
+
* `/path/to/primary/.git`, NOT the worktree's own `.git` file). Taking `path.dirname()` of that
|
|
22
|
+
* resolved, absolute path yields the primary checkout's repo root — the one location every
|
|
23
|
+
* worktree of the same repo should treat as the shared `.kontourai/flow-agents` store, so a
|
|
24
|
+
* `liveness claim` or `ensure-session` invoked from ANY worktree's cwd is visible to a reader
|
|
25
|
+
* in any other checkout of the same repo (including the primary one).
|
|
26
|
+
*
|
|
27
|
+
* Returns null (never throws) when git is unavailable, `cwd` is not inside a git working tree,
|
|
28
|
+
* or the command otherwise fails — callers MUST fail open to the existing cwd-based
|
|
29
|
+
* `path.resolve(cwd, FLOW_AGENTS_RUNTIME_DIR)` behavior in that case (see flowAgentsArtifactRoot
|
|
30
|
+
* below), which is also exactly what happens today in the single-checkout case: there,
|
|
31
|
+
* `--git-common-dir` resolves to `.git` under cwd itself, so `path.dirname()` of its absolute
|
|
32
|
+
* form is cwd — byte-identical to today's plain `path.resolve(cwd, ...)` result.
|
|
33
|
+
*
|
|
34
|
+
* #413 iteration-2 Fix 3: strips the ambient GIT_DIR/GIT_COMMON_DIR/GIT_WORK_TREE/
|
|
35
|
+
* GIT_CEILING_DIRECTORIES env vars before shelling out. Any of these, if set in the calling
|
|
36
|
+
* process's environment (e.g. leaked from an outer `git rebase`/hook invocation, or a stray
|
|
37
|
+
* export in a dev shell), can silently redirect `--git-common-dir` to resolve a DIFFERENT
|
|
38
|
+
* repository's `.git` than the one `cwd` is actually inside — which would silently redirect
|
|
39
|
+
* the shared `.kontourai/flow-agents` store to that other repo. Passing an explicit env with
|
|
40
|
+
* those keys deleted makes resolution depend only on `cwd`, never on ambient process state.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveSharedRepoRoot(cwd: string): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* #413 iteration-2 Fix 2 (documentation only, no behavior change): when `cwd` is a
|
|
45
|
+
* SUBDIRECTORY of a plain (non-worktree) checkout, `git rev-parse --git-common-dir` returns a
|
|
46
|
+
* RELATIVE path (e.g. `../.git` or deeper) that `resolveSharedRepoRoot` walks up to the repo
|
|
47
|
+
* root — so this intentionally resolves to `<repo-root>/.kontourai/flow-agents` regardless of
|
|
48
|
+
* which subdirectory `cwd` is, NOT `<subdir>/.kontourai/flow-agents` (the old plain
|
|
49
|
+
* `path.resolve(cwd, ...)` cwd-anchored behavior). This is the CORRECT shared-store semantics —
|
|
50
|
+
* one store per repo, matching every other cwd (primary checkout, linked worktree) this
|
|
51
|
+
* resolver already unifies — not a regression to guard against. See
|
|
52
|
+
* evals/integration/test_liveness_worktree_root.sh's AC6b for the explicit assertion, and its
|
|
53
|
+
* header docstring for why AC6's "byte-identical" guarantee is scoped to repo-root cwd and
|
|
54
|
+
* non-git cwd only, never to an arbitrary subdirectory cwd.
|
|
55
|
+
*/
|
|
15
56
|
export declare function flowAgentsArtifactRoot(cwd?: string): string;
|
|
16
57
|
export declare function durableFlowAgentsRoot(cwd?: string): string;
|
|
17
58
|
export declare function durableInstallRecordPath(cwd?: string): string;
|
|
59
|
+
/** Path to the per-skill-file content-hash drift manifest, a sibling of `install.json` under the same durable root. */
|
|
60
|
+
export declare function skillsManifestPath(cwd?: string): string;
|
|
18
61
|
export declare function telemetryDataDir(cwd?: string): string;
|
|
19
62
|
export declare function legacyTelemetryDataDir(cwd?: string): string;
|
|
20
63
|
export declare function firstExistingPath(candidates: string[]): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
4
5
|
export const KONTOURAI_DIR = ".kontourai";
|
|
5
6
|
export const FLOW_AGENTS_RUNTIME_SUBDIR = "flow-agents";
|
|
6
7
|
export const FLOW_AGENTS_RUNTIME_DIR = `${KONTOURAI_DIR}/${FLOW_AGENTS_RUNTIME_SUBDIR}`;
|
|
@@ -14,11 +15,134 @@ export const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
|
14
15
|
* and the durable per-destination install record root
|
|
15
16
|
* (`DURABLE_FLOW_AGENTS_DIR`, i.e. `.flow-agents`).
|
|
16
17
|
*/
|
|
17
|
-
export function defaultCodexHome() {
|
|
18
|
-
return
|
|
18
|
+
export function defaultCodexHome(env = process.env, homedir = os.homedir()) {
|
|
19
|
+
return env["CODEX_HOME"] || path.join(homedir, ".codex");
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* #357: resolve the SHARED, git-common-dir-anchored `.kontourai/flow-agents` root for `cwd`.
|
|
23
|
+
*
|
|
24
|
+
* `git rev-parse --git-common-dir` returns the ONE `.git` directory shared by every worktree
|
|
25
|
+
* of a repository (in the primary checkout this is simply `.git` relative to cwd; in a linked
|
|
26
|
+
* worktree it resolves to the primary checkout's real `.git` directory, e.g.
|
|
27
|
+
* `/path/to/primary/.git`, NOT the worktree's own `.git` file). Taking `path.dirname()` of that
|
|
28
|
+
* resolved, absolute path yields the primary checkout's repo root — the one location every
|
|
29
|
+
* worktree of the same repo should treat as the shared `.kontourai/flow-agents` store, so a
|
|
30
|
+
* `liveness claim` or `ensure-session` invoked from ANY worktree's cwd is visible to a reader
|
|
31
|
+
* in any other checkout of the same repo (including the primary one).
|
|
32
|
+
*
|
|
33
|
+
* Returns null (never throws) when git is unavailable, `cwd` is not inside a git working tree,
|
|
34
|
+
* or the command otherwise fails — callers MUST fail open to the existing cwd-based
|
|
35
|
+
* `path.resolve(cwd, FLOW_AGENTS_RUNTIME_DIR)` behavior in that case (see flowAgentsArtifactRoot
|
|
36
|
+
* below), which is also exactly what happens today in the single-checkout case: there,
|
|
37
|
+
* `--git-common-dir` resolves to `.git` under cwd itself, so `path.dirname()` of its absolute
|
|
38
|
+
* form is cwd — byte-identical to today's plain `path.resolve(cwd, ...)` result.
|
|
39
|
+
*
|
|
40
|
+
* #413 iteration-2 Fix 3: strips the ambient GIT_DIR/GIT_COMMON_DIR/GIT_WORK_TREE/
|
|
41
|
+
* GIT_CEILING_DIRECTORIES env vars before shelling out. Any of these, if set in the calling
|
|
42
|
+
* process's environment (e.g. leaked from an outer `git rebase`/hook invocation, or a stray
|
|
43
|
+
* export in a dev shell), can silently redirect `--git-common-dir` to resolve a DIFFERENT
|
|
44
|
+
* repository's `.git` than the one `cwd` is actually inside — which would silently redirect
|
|
45
|
+
* the shared `.kontourai/flow-agents` store to that other repo. Passing an explicit env with
|
|
46
|
+
* those keys deleted makes resolution depend only on `cwd`, never on ambient process state.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveSharedRepoRoot(cwd) {
|
|
49
|
+
try {
|
|
50
|
+
const env = { ...process.env };
|
|
51
|
+
delete env["GIT_DIR"];
|
|
52
|
+
delete env["GIT_COMMON_DIR"];
|
|
53
|
+
delete env["GIT_WORK_TREE"];
|
|
54
|
+
delete env["GIT_CEILING_DIRECTORIES"];
|
|
55
|
+
const out = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
56
|
+
cwd,
|
|
57
|
+
env,
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
60
|
+
}).trim();
|
|
61
|
+
if (!out)
|
|
62
|
+
return null;
|
|
63
|
+
const absoluteCommonDir = path.resolve(cwd, out);
|
|
64
|
+
return path.dirname(absoluteCommonDir);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* #413 iteration-2 Fix 1: cheap discriminator for the loud-vs-silent fail-open decision below.
|
|
72
|
+
* Walks up from `cwd` (bounded, same 40-level cap the hook-side ancestor walks use) looking for
|
|
73
|
+
* a `.git` entry — a FILE (linked worktree's gitdir pointer) or a DIRECTORY (primary checkout) —
|
|
74
|
+
* without shelling out to git at all, so it stays meaningful even when git itself is the thing
|
|
75
|
+
* that's broken/absent. Never throws; a filesystem error at any level is treated as "no .git
|
|
76
|
+
* found here", not as a crash.
|
|
77
|
+
*/
|
|
78
|
+
function isInsideGitWorkingTree(cwd) {
|
|
79
|
+
try {
|
|
80
|
+
let dir = path.resolve(cwd);
|
|
81
|
+
const root = path.parse(dir).root;
|
|
82
|
+
for (let depth = 0; depth < 40; depth++) {
|
|
83
|
+
if (fs.existsSync(path.join(dir, ".git")))
|
|
84
|
+
return true;
|
|
85
|
+
if (dir === root)
|
|
86
|
+
break;
|
|
87
|
+
const parent = path.dirname(dir);
|
|
88
|
+
if (parent === dir)
|
|
89
|
+
break;
|
|
90
|
+
dir = parent;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* #413 iteration-2 Fix 1 (HIGH, security): emit a LOUD stderr warning before falling open, but
|
|
100
|
+
* ONLY when `cwd` is genuinely inside a git working tree (per isInsideGitWorkingTree above) AND
|
|
101
|
+
* resolveSharedRepoRoot still returned null — i.e. git resolution was ATTEMPTED and FAILED, not
|
|
102
|
+
* merely "there was no git repo to resolve in the first place". That second, benign case (a
|
|
103
|
+
* scratch dir, a non-git cwd) must stay perfectly silent — this is exactly what
|
|
104
|
+
* evals/integration/test_liveness_worktree_root.sh's AC6 companion case (no git at all) already
|
|
105
|
+
* asserts, and it must remain byte-identical to today.
|
|
106
|
+
*
|
|
107
|
+
* The loud case (corrupted gitlink, bad GIT_DIR-in-cwd's-own-config, git binary missing, a
|
|
108
|
+
* permissions error on .git, etc.) is #357's exact silent-fail-open bug: an actor's
|
|
109
|
+
* liveness/ownership claim lands in an ISOLATED cwd-local store invisible to every other
|
|
110
|
+
* worktree/checkout of the same repo, with no diagnostic at all. A warning (not a hard refuse —
|
|
111
|
+
* this helper sits on read paths too, and a degraded read must still return something usable)
|
|
112
|
+
* gives an operator the signal needed to pass `--artifact-root` explicitly instead of silently
|
|
113
|
+
* losing coordination visibility.
|
|
114
|
+
*/
|
|
115
|
+
function warnIfFailingOpenInsideGitTree(cwd, fallbackPath) {
|
|
116
|
+
if (!isInsideGitWorkingTree(cwd))
|
|
117
|
+
return;
|
|
118
|
+
process.stderr.write(`[artifact-root] WARNING: inside a git working tree but could not resolve the shared repo root ` +
|
|
119
|
+
`(git rev-parse --git-common-dir failed or returned nothing from ${cwd}); falling back to a ` +
|
|
120
|
+
`cwd-local store at ${fallbackPath} — coordination claims may be invisible to other ` +
|
|
121
|
+
`worktrees/actors. Pass --artifact-root explicitly to fix.\n`);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* #413 iteration-2 Fix 2 (documentation only, no behavior change): when `cwd` is a
|
|
125
|
+
* SUBDIRECTORY of a plain (non-worktree) checkout, `git rev-parse --git-common-dir` returns a
|
|
126
|
+
* RELATIVE path (e.g. `../.git` or deeper) that `resolveSharedRepoRoot` walks up to the repo
|
|
127
|
+
* root — so this intentionally resolves to `<repo-root>/.kontourai/flow-agents` regardless of
|
|
128
|
+
* which subdirectory `cwd` is, NOT `<subdir>/.kontourai/flow-agents` (the old plain
|
|
129
|
+
* `path.resolve(cwd, ...)` cwd-anchored behavior). This is the CORRECT shared-store semantics —
|
|
130
|
+
* one store per repo, matching every other cwd (primary checkout, linked worktree) this
|
|
131
|
+
* resolver already unifies — not a regression to guard against. See
|
|
132
|
+
* evals/integration/test_liveness_worktree_root.sh's AC6b for the explicit assertion, and its
|
|
133
|
+
* header docstring for why AC6's "byte-identical" guarantee is scoped to repo-root cwd and
|
|
134
|
+
* non-git cwd only, never to an arbitrary subdirectory cwd.
|
|
135
|
+
*/
|
|
20
136
|
export function flowAgentsArtifactRoot(cwd = process.cwd()) {
|
|
21
|
-
|
|
137
|
+
const sharedRepoRoot = resolveSharedRepoRoot(cwd);
|
|
138
|
+
if (sharedRepoRoot)
|
|
139
|
+
return path.resolve(sharedRepoRoot, FLOW_AGENTS_RUNTIME_DIR);
|
|
140
|
+
// Fail-open: not a git repo, git unavailable, or command failed — reproduce today's plain
|
|
141
|
+
// cwd-based behavior unchanged (AC6 backward-compat guarantee). #413 iteration-2 Fix 1: loud,
|
|
142
|
+
// not silent, when cwd IS inside a git working tree (see warnIfFailingOpenInsideGitTree).
|
|
143
|
+
const fallback = path.resolve(cwd, FLOW_AGENTS_RUNTIME_DIR);
|
|
144
|
+
warnIfFailingOpenInsideGitTree(cwd, fallback);
|
|
145
|
+
return fallback;
|
|
22
146
|
}
|
|
23
147
|
export function durableFlowAgentsRoot(cwd = process.cwd()) {
|
|
24
148
|
return path.resolve(cwd, DURABLE_FLOW_AGENTS_DIR);
|
|
@@ -26,6 +150,10 @@ export function durableFlowAgentsRoot(cwd = process.cwd()) {
|
|
|
26
150
|
export function durableInstallRecordPath(cwd = process.cwd()) {
|
|
27
151
|
return path.join(durableFlowAgentsRoot(cwd), "install.json");
|
|
28
152
|
}
|
|
153
|
+
/** Path to the per-skill-file content-hash drift manifest, a sibling of `install.json` under the same durable root. */
|
|
154
|
+
export function skillsManifestPath(cwd = process.cwd()) {
|
|
155
|
+
return path.join(durableFlowAgentsRoot(cwd), "skills-manifest.json");
|
|
156
|
+
}
|
|
29
157
|
export function telemetryDataDir(cwd = process.cwd()) {
|
|
30
158
|
return path.resolve(cwd, KONTOURAI_DIR, "telemetry");
|
|
31
159
|
}
|
|
@@ -8,8 +8,36 @@ export type KitAsset = {
|
|
|
8
8
|
source_kind: string;
|
|
9
9
|
description?: string;
|
|
10
10
|
};
|
|
11
|
+
export type KitWorkflowTriggerRef = {
|
|
12
|
+
kit_id: string;
|
|
13
|
+
source_kind: string;
|
|
14
|
+
id: string;
|
|
15
|
+
when: string;
|
|
16
|
+
target_flow_id?: string;
|
|
17
|
+
default_skill?: string;
|
|
18
|
+
conditional_skills?: {
|
|
19
|
+
when: string;
|
|
20
|
+
skill: string;
|
|
21
|
+
}[];
|
|
22
|
+
required_sequence?: string[];
|
|
23
|
+
post_verify_targets?: string[];
|
|
24
|
+
};
|
|
25
|
+
export type KitHookInfluenceExpectationRef = {
|
|
26
|
+
kit_id: string;
|
|
27
|
+
source_kind: string;
|
|
28
|
+
id: string;
|
|
29
|
+
description: string;
|
|
30
|
+
tier: string;
|
|
31
|
+
hook?: string;
|
|
32
|
+
event?: string;
|
|
33
|
+
must_include_guidance: string[];
|
|
34
|
+
must_include_actions: string[];
|
|
35
|
+
};
|
|
11
36
|
export type KitInventory = {
|
|
12
37
|
assets: KitAsset[];
|
|
38
|
+
dependencies: KitDependencyRef[];
|
|
39
|
+
workflow_triggers: KitWorkflowTriggerRef[];
|
|
40
|
+
hook_influence_expectations: KitHookInfluenceExpectationRef[];
|
|
13
41
|
warnings: string[];
|
|
14
42
|
errors: string[];
|
|
15
43
|
};
|
|
@@ -24,7 +52,15 @@ export type LoadedKit = {
|
|
|
24
52
|
kit_id: string;
|
|
25
53
|
assets: KitAsset[];
|
|
26
54
|
dependencies: KitDependencyRef[];
|
|
55
|
+
workflow_triggers: KitWorkflowTriggerRef[];
|
|
56
|
+
hook_influence_expectations: KitHookInfluenceExpectationRef[];
|
|
27
57
|
};
|
|
28
|
-
export declare function readKitInventory(sourceRoot: string, dest: string
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
export declare function readKitInventory(sourceRoot: string, dest: string, options?: {
|
|
59
|
+
kitIdFilter?: string[];
|
|
60
|
+
}): KitInventory;
|
|
61
|
+
export declare function activateCodexLocal(sourceRoot: string, dest: string, options?: {
|
|
62
|
+
kitIdFilter?: string[];
|
|
63
|
+
}): Record<string, unknown>;
|
|
64
|
+
export declare function activateStrandsLocal(sourceRoot: string, dest: string, options?: {
|
|
65
|
+
kitIdFilter?: string[];
|
|
66
|
+
}): Record<string, unknown>;
|