@kontourai/flow-agents 3.2.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 +173 -0
- package/CHANGELOG.md +54 -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/assignment-provider.js +10 -1
- 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-artifact-cleanup-audit.js +418 -11
- package/build/src/cli/workflow-sidecar.d.ts +310 -1
- package/build/src/cli/workflow-sidecar.js +1914 -126
- 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 +30 -3
- package/context/contracts/artifact-contract.md +16 -2
- 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 +194 -22
- 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/coordination-guide.md +370 -0
- package/docs/decisions/agent-coordination.md +26 -9
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +5 -3
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/trust-reconcile.md +42 -9
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +6 -2
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +23 -9
- package/docs/integrations/flow-agents-console.md +275 -0
- package/docs/integrations/index.md +4 -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-artifact-lifecycle.md +38 -1
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +2 -0
- package/evals/ci/run-baseline.sh +78 -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/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
- package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -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_checkpoint_signing.sh +10 -2
- package/evals/integration/test_ci_actor_identity.sh +221 -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 +389 -2
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_reconcile_preflight.sh +304 -0
- 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_takeover_protocol.sh +340 -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 +121 -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_verify_hold.sh +910 -0
- package/evals/integration/test_veritas_governance_kit.sh +257 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
- 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 +37 -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/continue-work/SKILL.md +2 -0
- package/kits/builder/skills/deliver/SKILL.md +115 -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 +33 -2
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +113 -7
- package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
- package/kits/veritas-governance/kit.json +19 -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 +214 -253
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/actor-identity.js +82 -0
- 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 +194 -22
- 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/lib/reconcile-shape.js +381 -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/assignment-provider.ts +12 -1
- 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-artifact-cleanup-audit.ts +483 -10
- package/src/cli/workflow-sidecar.ts +1980 -119
- 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 +29 -3
package/build/src/cli/init.js
CHANGED
|
@@ -10,7 +10,9 @@ import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
|
|
|
10
10
|
import { activateCodexLocal } from "../runtime-adapters.js";
|
|
11
11
|
import { main as buildBundles } from "../tools/build-universal-bundles.js";
|
|
12
12
|
import { root } from "../tools/common.js";
|
|
13
|
-
import { defaultCodexHome, durableInstallRecordPath } from "../lib/local-artifact-root.js";
|
|
13
|
+
import { defaultCodexHome, durableInstallRecordPath, skillsManifestPath } from "../lib/local-artifact-root.js";
|
|
14
|
+
import { runConsoleConnectWizard, describeConsoleStatus, buildPostInstallSummaryLines } from "../lib/console-connect-options.js";
|
|
15
|
+
import { buildReport } from "./telemetry-doctor.js";
|
|
14
16
|
const runtimeBundles = {
|
|
15
17
|
base: "base",
|
|
16
18
|
codex: "codex",
|
|
@@ -92,9 +94,39 @@ Options:
|
|
|
92
94
|
--console-token-file PATH
|
|
93
95
|
--console-tenant ID
|
|
94
96
|
--activate-kits
|
|
97
|
+
--activate-kit KIT_ID Activate one catalog kit by id. Repeat for multiple kits.
|
|
95
98
|
--yes, --headless
|
|
96
99
|
`);
|
|
97
100
|
}
|
|
101
|
+
function catalogKitIds() {
|
|
102
|
+
const catalogPath = path.join(root, "kits", "catalog.json");
|
|
103
|
+
if (!fs.existsSync(catalogPath))
|
|
104
|
+
return [];
|
|
105
|
+
const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf8"));
|
|
106
|
+
if (!Array.isArray(catalog.kits))
|
|
107
|
+
return [];
|
|
108
|
+
return catalog.kits
|
|
109
|
+
.map((entry) => typeof entry === "object" && entry !== null ? String(entry.id ?? "") : "")
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
function selectedKitIdsFromFlags(flags) {
|
|
113
|
+
const explicit = flagList(flags, "activate-kit").map((id) => id.trim()).filter(Boolean);
|
|
114
|
+
if (explicit.length)
|
|
115
|
+
return [...new Set(explicit)];
|
|
116
|
+
if (flagBool(flags, "activate-kits"))
|
|
117
|
+
return catalogKitIds();
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function writeInstallRecord(dest, runtime, global, activeKitIds = []) {
|
|
121
|
+
const recordPath = durableInstallRecordPath(dest);
|
|
122
|
+
const installRecordDir = path.dirname(recordPath);
|
|
123
|
+
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
124
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
125
|
+
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime, ...(global ? { global: true } : {}), active_kit_ids: activeKitIds };
|
|
126
|
+
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
127
|
+
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
128
|
+
fs.renameSync(recordTmp, recordPath);
|
|
129
|
+
}
|
|
98
130
|
function normalizeRuntime(value) {
|
|
99
131
|
if (!value)
|
|
100
132
|
return undefined;
|
|
@@ -104,6 +136,91 @@ function normalizeRuntime(value) {
|
|
|
104
136
|
return value;
|
|
105
137
|
throw new Error(`unknown runtime '${value}'; expected base, codex, claude-code, kiro, opencode, or pi`);
|
|
106
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* The three candidate global config directories a runtime's own tooling uses,
|
|
141
|
+
* shared between `globalDest` (the `--global` install target) and
|
|
142
|
+
* `detectRuntimeFromFilesystem` (auto-detection fallback) so "where does
|
|
143
|
+
* runtime X's global config live" is defined in exactly one place.
|
|
144
|
+
*/
|
|
145
|
+
function runtimeGlobalConfigCandidates(homedir, env) {
|
|
146
|
+
return {
|
|
147
|
+
"claude-code": path.join(homedir, ".claude"),
|
|
148
|
+
codex: defaultCodexHome(env, homedir),
|
|
149
|
+
opencode: path.join(env["XDG_CONFIG_HOME"] ?? path.join(homedir, ".config"), "opencode"),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Detect the current runtime from environment-variable signals already
|
|
154
|
+
* ambient in the invoking process (Claude Code / Codex / opencode session
|
|
155
|
+
* markers). Reuses the single existing `detectRuntime` implementation from
|
|
156
|
+
* `scripts/hooks/lib/actor-identity.js` (via the same `createRequire` pattern
|
|
157
|
+
* used elsewhere in this file for `install-merge.js`/`skill-drift.js`)
|
|
158
|
+
* instead of forking a second copy of the env-var contract. `pi`'s env-var
|
|
159
|
+
* contract is unverified there (see actor-identity.js's own docstring) and is
|
|
160
|
+
* intentionally out of scope for auto-detection, so it maps down to
|
|
161
|
+
* `"unknown"` here (pi still falls back to `"base"` like today).
|
|
162
|
+
*/
|
|
163
|
+
export function detectRuntimeFromProcessEnv(env = process.env) {
|
|
164
|
+
const actorIdentityPath = path.join(root, "scripts", "hooks", "lib", "actor-identity.js");
|
|
165
|
+
const _require = createRequire(import.meta.url);
|
|
166
|
+
const { detectRuntime } = _require(actorIdentityPath);
|
|
167
|
+
const detected = detectRuntime(env);
|
|
168
|
+
if (detected === "claude-code" || detected === "codex" || detected === "opencode")
|
|
169
|
+
return detected;
|
|
170
|
+
return "unknown";
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Detect the current runtime by probing for exactly one runtime's own global
|
|
174
|
+
* config directory on disk (the same paths `globalDest` computes for
|
|
175
|
+
* `--global` installs — see `runtimeGlobalConfigCandidates`). Zero matches or
|
|
176
|
+
* 2+ matches are both ambiguous and must never guess: only a single
|
|
177
|
+
* unambiguous hit returns a runtime; everything else is `"unknown"`.
|
|
178
|
+
*/
|
|
179
|
+
export function detectRuntimeFromFilesystem(homedir = os.homedir(), env = process.env) {
|
|
180
|
+
const candidates = runtimeGlobalConfigCandidates(homedir, env);
|
|
181
|
+
const matches = Object.keys(candidates).filter((runtime) => {
|
|
182
|
+
try {
|
|
183
|
+
return fs.existsSync(candidates[runtime]);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return matches.length === 1 ? matches[0] : "unknown";
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Layered runtime auto-detection default used to pre-fill both the
|
|
193
|
+
* interactive prompt and the headless `--runtime` fallback: (1) env-var
|
|
194
|
+
* signals from the invoking process, (2) failing that, an unambiguous
|
|
195
|
+
* filesystem probe, (3) failing that, today's `"base"` default. An explicit
|
|
196
|
+
* `--runtime` flag or interactive answer always overrides this — this
|
|
197
|
+
* function only supplies the fallback default (see `interactiveOptions` /
|
|
198
|
+
* `headlessOptions`).
|
|
199
|
+
*/
|
|
200
|
+
export function detectDefaultRuntime(env = process.env, homedir = os.homedir()) {
|
|
201
|
+
const fromEnv = detectRuntimeFromProcessEnv(env);
|
|
202
|
+
if (fromEnv !== "unknown")
|
|
203
|
+
return fromEnv;
|
|
204
|
+
const fromFilesystem = detectRuntimeFromFilesystem(homedir, env);
|
|
205
|
+
if (fromFilesystem !== "unknown")
|
|
206
|
+
return fromFilesystem;
|
|
207
|
+
return "base";
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Returns the runtime the layered detector actually matched (env signal
|
|
211
|
+
* first, then an unambiguous filesystem probe), or "unknown" if neither
|
|
212
|
+
* fired. Distinct from `detectDefaultRuntime()`, which additionally falls
|
|
213
|
+
* back to `"base"` when detection is unknown -- the post-install summary's
|
|
214
|
+
* "(auto-detected)" annotation needs to distinguish "detection fired" from
|
|
215
|
+
* "detection defaulted to base," which `detectDefaultRuntime()`'s return
|
|
216
|
+
* value alone cannot express.
|
|
217
|
+
*/
|
|
218
|
+
function detectedRuntimeSignal(env = process.env, homedir = os.homedir()) {
|
|
219
|
+
const fromEnv = detectRuntimeFromProcessEnv(env);
|
|
220
|
+
if (fromEnv !== "unknown")
|
|
221
|
+
return fromEnv;
|
|
222
|
+
return detectRuntimeFromFilesystem(homedir, env);
|
|
223
|
+
}
|
|
107
224
|
function normalizeTelemetrySink(value) {
|
|
108
225
|
if (value === "local-files" || value === "local-kontour-console" || value === "kontour-hosted-console" || value === "user-hosted-console" || value === "kontour-cloud" || value === "hosted-kontour-console")
|
|
109
226
|
return value;
|
|
@@ -119,6 +236,24 @@ function telemetrySinksFromFlags(flags) {
|
|
|
119
236
|
function needsConsoleCredentials(sinks) {
|
|
120
237
|
return sinks.some((sink) => sink !== "local-files");
|
|
121
238
|
}
|
|
239
|
+
const DEFAULT_HOSTED_CONSOLE_URL = "https://console.kontourai.io";
|
|
240
|
+
const DEFAULT_LOCAL_CONSOLE_URL = "http://127.0.0.1:3737";
|
|
241
|
+
/**
|
|
242
|
+
* Resolve one of `console-presets.sh`'s bash functions (by name) against the
|
|
243
|
+
* source root, so the interactive wizard's printed defaults are never a
|
|
244
|
+
* second hardcoded literal competing with the one bash source of truth
|
|
245
|
+
* (itself overridable via FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL /
|
|
246
|
+
* FLOW_AGENTS_LOCAL_KONTOUR_CONSOLE_URL, depending on which function is
|
|
247
|
+
* named). `fallback` is only a defensive floor for the rare case the
|
|
248
|
+
* subprocess produces no output (e.g. an unreadable source tree) -- it is not
|
|
249
|
+
* a second default.
|
|
250
|
+
*/
|
|
251
|
+
function presetConsoleUrl(bashFnName, fallback) {
|
|
252
|
+
const scriptPath = path.join(root, "scripts", "telemetry", "console-presets.sh");
|
|
253
|
+
const result = spawnSync("bash", ["-c", `source "${scriptPath}" && ${bashFnName}`], { encoding: "utf8" });
|
|
254
|
+
const stdout = (result.stdout ?? "").trim();
|
|
255
|
+
return stdout || fallback;
|
|
256
|
+
}
|
|
122
257
|
async function questionHidden(prompt) {
|
|
123
258
|
if (!process.stdin.isTTY)
|
|
124
259
|
return "";
|
|
@@ -157,13 +292,16 @@ function defaultDest(runtime) {
|
|
|
157
292
|
return path.join(os.homedir(), ".flow-agents");
|
|
158
293
|
return process.cwd();
|
|
159
294
|
}
|
|
160
|
-
function globalDest(runtime) {
|
|
295
|
+
export function globalDest(runtime) {
|
|
296
|
+
// Shared with detectRuntimeFromFilesystem so the "where is runtime X's global
|
|
297
|
+
// config" path logic is defined exactly once.
|
|
298
|
+
const candidates = runtimeGlobalConfigCandidates(os.homedir(), process.env);
|
|
161
299
|
if (runtime === "claude-code") {
|
|
162
300
|
// Honor FLOW_AGENTS_USER_CLAUDE_SETTINGS for test isolation (same as checkScopeCollision).
|
|
163
301
|
const override = process.env["FLOW_AGENTS_USER_CLAUDE_SETTINGS"];
|
|
164
302
|
if (override)
|
|
165
303
|
return path.dirname(override);
|
|
166
|
-
return
|
|
304
|
+
return candidates["claude-code"];
|
|
167
305
|
}
|
|
168
306
|
if (runtime === "opencode") {
|
|
169
307
|
// Honor FLOW_AGENTS_USER_OPENCODE_CONFIG (points to the opencode.json FILE) for test isolation,
|
|
@@ -172,11 +310,15 @@ function globalDest(runtime) {
|
|
|
172
310
|
if (override)
|
|
173
311
|
return path.dirname(override);
|
|
174
312
|
// Global opencode config: ~/.config/opencode/ (honor XDG_CONFIG_HOME when set, else ~/.config).
|
|
175
|
-
return
|
|
313
|
+
return candidates.opencode;
|
|
176
314
|
}
|
|
177
315
|
if (runtime === "codex") {
|
|
178
316
|
// codex --global routes to the standard Codex home. --dest remains an explicit override.
|
|
179
|
-
|
|
317
|
+
// `candidates.codex` is built from the same `CODEX_HOME || ~/.codex` resolution as
|
|
318
|
+
// `defaultCodexHome()` (see runtimeGlobalConfigCandidates above), so this is byte-identical
|
|
319
|
+
// to calling `defaultCodexHome()` directly while keeping the candidate map the single
|
|
320
|
+
// source of truth for "where does runtime X's global config live".
|
|
321
|
+
return candidates.codex;
|
|
180
322
|
}
|
|
181
323
|
if (runtime === "pi") {
|
|
182
324
|
// pi has no documented global config dir.
|
|
@@ -185,46 +327,94 @@ function globalDest(runtime) {
|
|
|
185
327
|
}
|
|
186
328
|
return defaultDest(runtime);
|
|
187
329
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Console-connect sink/credential gathering for interactiveOptions(),
|
|
332
|
+
* extracted as a pure refactor (identical behavior, identical returned
|
|
333
|
+
* shape) so interactiveOptions() itself stays a thin top-level orchestration.
|
|
334
|
+
* Covers both branches of "did the caller already give --telemetry-sink(s)
|
|
335
|
+
* on argv":
|
|
336
|
+
* - sinkFlagsGiven: the guided wizard is bypassed entirely (same as the old
|
|
337
|
+
* hidden sinkAnswer skip); only whichever of URL/token/tenant was not
|
|
338
|
+
* already supplied via flag gets its own prompt.
|
|
339
|
+
* - not given: the full guided wizard runs, with askWithFlagOverrides /
|
|
340
|
+
* askHiddenWithFlagOverrides short-circuiting any individual sub-prompt
|
|
341
|
+
* whose answer was already supplied via flag -- same flag-wins precedence
|
|
342
|
+
* as the sinkFlagsGiven branch, just applied per-sub-prompt instead of
|
|
343
|
+
* skipping the whole wizard.
|
|
344
|
+
* Flag-wins-over-prompt precedence is unchanged from before this wizard
|
|
345
|
+
* existed.
|
|
346
|
+
*/
|
|
347
|
+
async function gatherConsoleConnectOptions(rl, args, consoleTokenFile) {
|
|
348
|
+
const sinkFlagsGiven = flagList(args.flags, "telemetry-sink").length > 0 || flagList(args.flags, "telemetry-sinks").length > 0;
|
|
349
|
+
const consoleUrlFlag = flagString(args.flags, "console-url");
|
|
350
|
+
const consoleTenantFlag = flagString(args.flags, "console-tenant") ?? flagString(args.flags, "console-tenant-id");
|
|
351
|
+
if (sinkFlagsGiven) {
|
|
352
|
+
const telemetrySinks = telemetrySinksFromFlags(args.flags);
|
|
353
|
+
const needsUserHostedUrl = telemetrySinks.includes("user-hosted-console") || telemetrySinks.includes("hosted-kontour-console");
|
|
354
|
+
const consoleUrl = consoleUrlFlag ?? (needsUserHostedUrl ? await rl.question("User-hosted Console URL: ") : undefined);
|
|
355
|
+
const consoleTokenValue = consoleTokenFile ? undefined : (needsConsoleCredentials(telemetrySinks) ? await questionHidden("Console telemetry token (blank to skip): ") : undefined);
|
|
356
|
+
const consoleTenant = consoleTenantFlag ?? (needsConsoleCredentials(telemetrySinks) ? await rl.question("Console tenant ID (blank to skip): ") : undefined);
|
|
357
|
+
return { telemetrySinks, consoleUrl, consoleTokenValue, consoleTenant };
|
|
358
|
+
}
|
|
359
|
+
const askWithFlagOverrides = async (prompt) => {
|
|
360
|
+
if (prompt.includes("Self-hosted Console URL") && consoleUrlFlag !== undefined)
|
|
361
|
+
return consoleUrlFlag;
|
|
362
|
+
if (prompt.includes("Console tenant ID") && consoleTenantFlag !== undefined)
|
|
363
|
+
return consoleTenantFlag;
|
|
364
|
+
return rl.question(prompt);
|
|
365
|
+
};
|
|
366
|
+
const askHiddenWithFlagOverrides = async (prompt) => {
|
|
367
|
+
if (consoleTokenFile)
|
|
368
|
+
return "";
|
|
369
|
+
return questionHidden(prompt);
|
|
370
|
+
};
|
|
371
|
+
const wizardResult = await runConsoleConnectWizard({ ask: askWithFlagOverrides, askHidden: askHiddenWithFlagOverrides }, {
|
|
372
|
+
hostedUrl: presetConsoleUrl("flow_agents_kontour_hosted_console_url", DEFAULT_HOSTED_CONSOLE_URL),
|
|
373
|
+
localUrl: presetConsoleUrl("flow_agents_local_kontour_console_url", DEFAULT_LOCAL_CONSOLE_URL),
|
|
374
|
+
});
|
|
375
|
+
for (const warning of wizardResult.warnings)
|
|
376
|
+
console.warn(`flow-agents init: ${warning}`);
|
|
377
|
+
return {
|
|
378
|
+
telemetrySinks: wizardResult.telemetrySinks,
|
|
379
|
+
consoleUrl: consoleUrlFlag ?? wizardResult.consoleUrl,
|
|
380
|
+
consoleTokenValue: consoleTokenFile ? undefined : wizardResult.consoleTokenValue,
|
|
381
|
+
consoleTenant: consoleTenantFlag ?? wizardResult.consoleTenant,
|
|
382
|
+
};
|
|
197
383
|
}
|
|
198
384
|
async function interactiveOptions(argv) {
|
|
199
385
|
const args = parseArgs(argv);
|
|
200
386
|
const rl = createInterface({ input, output });
|
|
201
387
|
try {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
388
|
+
const explicitRuntimeFlag = flagString(args.flags, "runtime");
|
|
389
|
+
const runtimeDefault = normalizeRuntime(explicitRuntimeFlag) ?? detectDefaultRuntime();
|
|
390
|
+
const runtimeAnswer = explicitRuntimeFlag ?? await rl.question(`Runtime [${runtimeDefault}]: `);
|
|
391
|
+
const runtimeAnswerTrimmed = runtimeAnswer.trim();
|
|
392
|
+
const runtime = normalizeRuntime(runtimeAnswerTrimmed || runtimeDefault) ?? runtimeDefault;
|
|
393
|
+
// "(auto-detected)" is only accurate when the runtime was neither an
|
|
394
|
+
// explicit --runtime flag nor a typed interactive answer AND the layered
|
|
395
|
+
// detector actually matched a signal (not merely defaulted to "base").
|
|
396
|
+
const runtimeAutoDetected = !explicitRuntimeFlag && !runtimeAnswerTrimmed && detectedRuntimeSignal() === runtime;
|
|
205
397
|
const isGlobal = flagBool(args.flags, "global");
|
|
206
398
|
const destBase = isGlobal ? globalDest(runtime) : defaultDest(runtime);
|
|
207
399
|
const destDefault = flagString(args.flags, "dest", destBase) ?? destBase;
|
|
208
400
|
const destAnswer = flagString(args.flags, "dest") ?? await rl.question(`Install destination [${destDefault}]: `);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
: await rl.question(`Telemetry sinks [${sinkDefault.join(",")}]: `);
|
|
213
|
-
const telemetrySinks = telemetrySinksFromValues([sinkAnswer || sinkDefault.join(",")]);
|
|
214
|
-
const needsUserHostedUrl = telemetrySinks.includes("user-hosted-console") || telemetrySinks.includes("hosted-kontour-console");
|
|
215
|
-
const consoleUrl = flagString(args.flags, "console-url") ?? (needsUserHostedUrl ? await rl.question("User-hosted Console URL: ") : undefined);
|
|
401
|
+
// Guided "Connect to Kontour Console?" wizard (Hosted/Local/Self-hosted/Skip)
|
|
402
|
+
// replaces the old hidden sink-keyword prompt -- see gatherConsoleConnectOptions
|
|
403
|
+
// for the flag-override / sink-flags-given branching.
|
|
216
404
|
const consoleEndpoint = flagString(args.flags, "console-endpoint") ?? flagString(args.flags, "console-endpoint-url");
|
|
217
405
|
const consoleTokenFile = flagString(args.flags, "console-token-file");
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
406
|
+
const { telemetrySinks, consoleUrl, consoleTokenValue, consoleTenant } = await gatherConsoleConnectOptions(rl, args, consoleTokenFile);
|
|
407
|
+
const activeKitIds = selectedKitIdsFromFlags(args.flags);
|
|
408
|
+
if (!activeKitIds.length && !flagBool(args.flags, "activate-kits") && !flagList(args.flags, "activate-kit").length) {
|
|
409
|
+
const available = catalogKitIds();
|
|
410
|
+
if (available.length) {
|
|
411
|
+
const answer = await rl.question(`Activate kits by id (comma-separated, default none) [none]: `);
|
|
412
|
+
activeKitIds.push(...answer.split(",").map((id) => id.trim()).filter((id) => available.includes(id)));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
226
415
|
return {
|
|
227
416
|
runtime,
|
|
417
|
+
runtimeAutoDetected,
|
|
228
418
|
global: isGlobal,
|
|
229
419
|
dest: path.resolve(destAnswer.trim() || destDefault),
|
|
230
420
|
consoleUrl: consoleUrl?.trim() || undefined,
|
|
@@ -233,7 +423,8 @@ async function interactiveOptions(argv) {
|
|
|
233
423
|
consoleTokenValue: consoleTokenValue?.trim() || undefined,
|
|
234
424
|
consoleTenant: consoleTenant?.trim() || undefined,
|
|
235
425
|
telemetrySinks,
|
|
236
|
-
activateKits:
|
|
426
|
+
activateKits: activeKitIds.length > 0,
|
|
427
|
+
activeKitIds,
|
|
237
428
|
};
|
|
238
429
|
}
|
|
239
430
|
finally {
|
|
@@ -242,7 +433,7 @@ async function interactiveOptions(argv) {
|
|
|
242
433
|
}
|
|
243
434
|
function headlessOptions(argv) {
|
|
244
435
|
const args = parseArgs(argv);
|
|
245
|
-
const runtime = normalizeRuntime(flagString(args.flags, "runtime")) ??
|
|
436
|
+
const runtime = normalizeRuntime(flagString(args.flags, "runtime")) ?? detectDefaultRuntime();
|
|
246
437
|
const isGlobal = flagBool(args.flags, "global");
|
|
247
438
|
const destBase = isGlobal ? globalDest(runtime) : defaultDest(runtime);
|
|
248
439
|
return {
|
|
@@ -254,10 +445,11 @@ function headlessOptions(argv) {
|
|
|
254
445
|
consoleTokenFile: flagString(args.flags, "console-token-file"),
|
|
255
446
|
consoleTenant: flagString(args.flags, "console-tenant") ?? flagString(args.flags, "console-tenant-id"),
|
|
256
447
|
telemetrySinks: telemetrySinksFromFlags(args.flags),
|
|
257
|
-
activateKits:
|
|
448
|
+
activateKits: selectedKitIdsFromFlags(args.flags).length > 0,
|
|
449
|
+
activeKitIds: selectedKitIdsFromFlags(args.flags),
|
|
258
450
|
};
|
|
259
451
|
}
|
|
260
|
-
function ensureBundle(runtime) {
|
|
452
|
+
export function ensureBundle(runtime) {
|
|
261
453
|
const bundle = path.join(root, "dist", runtimeBundles[runtime]);
|
|
262
454
|
if (!fs.existsSync(path.join(bundle, "install.sh"))) {
|
|
263
455
|
const rc = buildBundles();
|
|
@@ -278,6 +470,17 @@ function ensureBundle(runtime) {
|
|
|
278
470
|
// installs need an absolute, session-independent path instead.
|
|
279
471
|
const GLOBAL_INSTALL_PROJECT_DIR_PREFIX = /root="\$\{CLAUDE_PROJECT_DIR:-\$\(pwd\)\}";\s*/g;
|
|
280
472
|
const GLOBAL_INSTALL_PROJECT_DIR_VAR = /"\$root\//g;
|
|
473
|
+
function warnInstallMergeConflicts(conflicts) {
|
|
474
|
+
for (const conflict of conflicts) {
|
|
475
|
+
console.warn(`install-merge: conflict: preserving existing setting '${conflict.path}' and not applying Flow Agents managed value`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function mergeInstallSettings(mergeSettings, existing, managed) {
|
|
479
|
+
const conflicts = [];
|
|
480
|
+
const merged = mergeSettings(existing, managed, { onConflict: (conflict) => conflicts.push(conflict) });
|
|
481
|
+
warnInstallMergeConflicts(conflicts);
|
|
482
|
+
return merged;
|
|
483
|
+
}
|
|
281
484
|
function rewriteCommandForGlobalInstall(command, sourceRoot) {
|
|
282
485
|
return command
|
|
283
486
|
.replace(GLOBAL_INSTALL_PROJECT_DIR_PREFIX, "")
|
|
@@ -369,9 +572,10 @@ function installBundle(bundle, options) {
|
|
|
369
572
|
return result.status ?? 1;
|
|
370
573
|
}
|
|
371
574
|
function activateKits(options) {
|
|
372
|
-
|
|
575
|
+
const activeKitIds = options.activeKitIds ?? [];
|
|
576
|
+
if (!options.activateKits || activeKitIds.length === 0 || options.runtime !== "codex")
|
|
373
577
|
return 0;
|
|
374
|
-
const result = activateCodexLocal(options.dest, options.dest);
|
|
578
|
+
const result = activateCodexLocal(options.dest, options.dest, { kitIdFilter: activeKitIds });
|
|
375
579
|
if (Array.isArray(result.errors) && result.errors.length > 0) {
|
|
376
580
|
console.error(JSON.stringify(result, null, 2));
|
|
377
581
|
return 1;
|
|
@@ -380,6 +584,70 @@ function activateKits(options) {
|
|
|
380
584
|
console.log(`Activated ${generated.length} Codex local runtime asset(s)`);
|
|
381
585
|
return 0;
|
|
382
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Headless counterpart of interactiveOptions()'s inline `runtimeAutoDetected`
|
|
589
|
+
* computation. headlessOptions() itself is untouched (see its doc note) --
|
|
590
|
+
* this is computed separately in main() and merged onto its result, so the
|
|
591
|
+
* "(auto-detected)" annotation applies to both install paths without adding
|
|
592
|
+
* a new required prompt or changing headlessOptions()'s own behavior.
|
|
593
|
+
*/
|
|
594
|
+
function headlessRuntimeAutoDetected(argv) {
|
|
595
|
+
const args = parseArgs(argv);
|
|
596
|
+
if (flagString(args.flags, "runtime"))
|
|
597
|
+
return false;
|
|
598
|
+
return detectedRuntimeSignal() !== "unknown";
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* G2 auto-verify + G3 post-install summary, shared by both the headless and
|
|
602
|
+
* interactive install paths (applies identically regardless of which one
|
|
603
|
+
* reached main()'s successful non-global install tail). Calls
|
|
604
|
+
* telemetry-doctor.ts's buildReport() in-process against the endpoint
|
|
605
|
+
* install-console-config.sh just wrote. Never allowed to change the exit
|
|
606
|
+
* code or hang the install: buildReport already has its own bounded default
|
|
607
|
+
* reachability timeout (2000ms unless overridden), and any thrown/rejected
|
|
608
|
+
* error here is caught and downgraded to a warning line only.
|
|
609
|
+
*/
|
|
610
|
+
async function printPostInstallSummary(options) {
|
|
611
|
+
let doctorConsole = { sink: "local-only", reachability: { checked: false, ok: null } };
|
|
612
|
+
let tokenConfigured = false;
|
|
613
|
+
let tenantConfigured = false;
|
|
614
|
+
try {
|
|
615
|
+
const report = await buildReport(["--dest", options.dest]);
|
|
616
|
+
doctorConsole = report.console;
|
|
617
|
+
tokenConfigured = report.console.tokenConfigured;
|
|
618
|
+
tenantConfigured = report.console.tenantConfigured;
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
console.warn(`flow-agents init: WARNING: could not verify the Console connection: ${error.message} (telemetry still installed; run 'flow-agents telemetry-doctor' to re-check).`);
|
|
622
|
+
}
|
|
623
|
+
const consoleStatus = describeConsoleStatus({ console: doctorConsole });
|
|
624
|
+
const nextSteps = [`Run your agent inside ${options.dest} -- Flow Agents hooks are already wired.`];
|
|
625
|
+
if (consoleStatus.status !== "local-only") {
|
|
626
|
+
nextSteps.push("Telemetry now flows to Kontour Console; visit the Console for ROI/economics views.");
|
|
627
|
+
}
|
|
628
|
+
// Only for the "reachability was never attempted" case (self-hosted/BYO
|
|
629
|
+
// HTTPS hosts default to not-allowed without --allow-network) -- never for
|
|
630
|
+
// local-only (nothing to verify) or the already-verified case (nothing left
|
|
631
|
+
// to do). doctorConsole.reachability.checked is the same raw signal
|
|
632
|
+
// describeConsoleStatus used to pick its NOT_CHECKED_DETAIL branch, so this
|
|
633
|
+
// stays in sync with that classifier without re-parsing its detail string.
|
|
634
|
+
if (consoleStatus.status === "connected-unverified" && doctorConsole.reachability.checked === false) {
|
|
635
|
+
nextSteps.push("Self-hosted/BYO Console reachability was not checked; run `flow-agents telemetry-doctor --allow-network` to verify it.");
|
|
636
|
+
}
|
|
637
|
+
nextSteps.push("Re-run `flow-agents init` anytime to reconfigure runtime, destination, or Console connection.");
|
|
638
|
+
const summaryLines = buildPostInstallSummaryLines({
|
|
639
|
+
runtime: options.runtime,
|
|
640
|
+
runtimeAutoDetected: Boolean(options.runtimeAutoDetected),
|
|
641
|
+
dest: options.dest,
|
|
642
|
+
telemetrySinks: options.telemetrySinks,
|
|
643
|
+
consoleStatus,
|
|
644
|
+
tokenConfigured,
|
|
645
|
+
tenantConfigured,
|
|
646
|
+
nextSteps,
|
|
647
|
+
});
|
|
648
|
+
for (const line of summaryLines)
|
|
649
|
+
console.log(line);
|
|
650
|
+
}
|
|
383
651
|
export async function main(argv = process.argv.slice(2)) {
|
|
384
652
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
385
653
|
usage();
|
|
@@ -387,7 +655,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
387
655
|
}
|
|
388
656
|
const headless = argv.includes("--yes") || argv.includes("--headless") || !process.stdin.isTTY;
|
|
389
657
|
try {
|
|
390
|
-
const options = headless
|
|
658
|
+
const options = headless
|
|
659
|
+
? { ...headlessOptions(argv), runtimeAutoDetected: headlessRuntimeAutoDetected(argv) }
|
|
660
|
+
: await interactiveOptions(argv);
|
|
391
661
|
// Scope-collision check for claude-code: Claude Code merges user-level
|
|
392
662
|
// (~/.claude/settings.json) and project-level (.claude/settings.json) settings
|
|
393
663
|
// and runs ALL matching hooks from both files. If a user-level settings file
|
|
@@ -443,7 +713,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
443
713
|
existing = {};
|
|
444
714
|
}
|
|
445
715
|
}
|
|
446
|
-
const merged = mergeSettings
|
|
716
|
+
const merged = mergeInstallSettings(mergeSettings, existing, managed);
|
|
447
717
|
const tmp = `${destSettingsPath}.tmp.${process.pid}`;
|
|
448
718
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
449
719
|
fs.renameSync(tmp, destSettingsPath);
|
|
@@ -452,16 +722,27 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
452
722
|
const skillsSync = copyDirMerge(path.join(bundle, ".claude", "skills"), path.join(options.dest, "skills"));
|
|
453
723
|
const agentsSync = copyDirMerge(path.join(bundle, ".claude", "agents"), path.join(options.dest, "agents"));
|
|
454
724
|
// Write version stamp.
|
|
455
|
-
|
|
456
|
-
const installRecordDir = path.dirname(recordPath);
|
|
457
|
-
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
458
|
-
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
459
|
-
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code", global: true };
|
|
460
|
-
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
461
|
-
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
462
|
-
fs.renameSync(recordTmp, recordPath);
|
|
725
|
+
writeInstallRecord(options.dest, "claude-code", true, options.activeKitIds ?? []);
|
|
463
726
|
console.log(`Flow Agents global hooks merged for claude-code in ${options.dest}`);
|
|
464
727
|
console.log(`Synced skills (+${skillsSync.added} new, ~${skillsSync.updated} updated) and agents (+${agentsSync.added} new, ~${agentsSync.updated} updated) in ${options.dest}`);
|
|
728
|
+
// Write a per-skill-file sha256 content-hash manifest, sibling of install.json, so
|
|
729
|
+
// `flow-agents skill-drift-check` and the SessionStart advisory can classify installed
|
|
730
|
+
// skill files as in_sync/kit_updated/user_modified/unbaselined/missing_install/kit_removed
|
|
731
|
+
// without re-deriving a second hashing convention (kontourai/flow-agents#439). This is an
|
|
732
|
+
// additive convenience feature layered on top of the install that already succeeded above
|
|
733
|
+
// (settings merge + skills/agents sync) — a failure writing this manifest must never fail
|
|
734
|
+
// the whole `init --global` run, so it is contained here: warn and continue.
|
|
735
|
+
try {
|
|
736
|
+
const skillDriftLibPath = path.join(root, "scripts", "hooks", "lib", "skill-drift.js");
|
|
737
|
+
const _requireSkillDrift = createRequire(import.meta.url);
|
|
738
|
+
const { buildManifest, writeManifestAtomic } = _requireSkillDrift(skillDriftLibPath);
|
|
739
|
+
const skillsManifest = buildManifest({ skillsSourceDir: path.join(bundle, ".claude", "skills"), runtime: "claude-code" });
|
|
740
|
+
writeManifestAtomic(skillsManifestPath(options.dest), skillsManifest);
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
console.error(`flow-agents init: WARNING: could not write skills drift-detection manifest: ${error.message} ` +
|
|
744
|
+
"(drift detection will report unbaselined until the next successful --global sync).");
|
|
745
|
+
}
|
|
465
746
|
return 0;
|
|
466
747
|
}
|
|
467
748
|
// --global for opencode: merge FA opencode.json into the global opencode config dir.
|
|
@@ -490,21 +771,13 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
490
771
|
existing = {};
|
|
491
772
|
}
|
|
492
773
|
}
|
|
493
|
-
const merged = mergeSettings
|
|
774
|
+
const merged = mergeInstallSettings(mergeSettings, existing, managed);
|
|
494
775
|
const tmp = `${destConfigPath}.tmp.${process.pid}`;
|
|
495
776
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}
|
|
496
777
|
`, "utf8");
|
|
497
778
|
fs.renameSync(tmp, destConfigPath);
|
|
498
779
|
// Write version stamp.
|
|
499
|
-
|
|
500
|
-
const installRecordDir = path.dirname(recordPath);
|
|
501
|
-
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
502
|
-
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
503
|
-
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "opencode", global: true };
|
|
504
|
-
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
505
|
-
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}
|
|
506
|
-
`, "utf8");
|
|
507
|
-
fs.renameSync(recordTmp, recordPath);
|
|
780
|
+
writeInstallRecord(options.dest, "opencode", true, options.activeKitIds ?? []);
|
|
508
781
|
console.log(`Flow Agents global config merged for opencode in ${options.dest}`);
|
|
509
782
|
return 0;
|
|
510
783
|
}
|
|
@@ -535,6 +808,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
535
808
|
const installed = result.status ?? 1;
|
|
536
809
|
if (installed !== 0)
|
|
537
810
|
return installed;
|
|
811
|
+
writeInstallRecord(options.dest, "codex", true, options.activeKitIds ?? []);
|
|
538
812
|
return activateKits(options);
|
|
539
813
|
}
|
|
540
814
|
// --global for pi: NOT_VERIFIED (no documented global dir). Warn and fall through to workspace install.
|
|
@@ -547,7 +821,13 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
547
821
|
const installed = installBundle(bundle, options);
|
|
548
822
|
if (installed !== 0)
|
|
549
823
|
return installed;
|
|
550
|
-
|
|
824
|
+
writeInstallRecord(options.dest, options.runtime, options.global, options.activeKitIds ?? []);
|
|
825
|
+
const activated = activateKits(options);
|
|
826
|
+
// G2/G3: shared post-install auto-verify + summary tail. Applies
|
|
827
|
+
// identically whether main() reached here via headlessOptions() or
|
|
828
|
+
// interactiveOptions() -- never changes `activated`'s exit code.
|
|
829
|
+
await printPostInstallSummary(options);
|
|
830
|
+
return activated;
|
|
551
831
|
}
|
|
552
832
|
catch (error) {
|
|
553
833
|
console.error(`flow-agents init: ${error.message}`);
|
|
@@ -619,7 +899,7 @@ function dogfoodClaudeCode(bundleRoot, dest) {
|
|
|
619
899
|
existing = {};
|
|
620
900
|
}
|
|
621
901
|
}
|
|
622
|
-
const merged = mergeSettings
|
|
902
|
+
const merged = mergeInstallSettings(mergeSettings, existing, managed);
|
|
623
903
|
const tmp = `${destSettingsPath}.tmp.${process.pid}`;
|
|
624
904
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
625
905
|
fs.renameSync(tmp, destSettingsPath);
|
|
@@ -662,7 +942,7 @@ function dogfoodCodex(bundleRoot, dest) {
|
|
|
662
942
|
existing = {};
|
|
663
943
|
}
|
|
664
944
|
}
|
|
665
|
-
const merged = mergeSettings
|
|
945
|
+
const merged = mergeInstallSettings(mergeSettings, existing, managed);
|
|
666
946
|
const tmp = `${destHooksPath}.tmp.${process.pid}`;
|
|
667
947
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
668
948
|
fs.renameSync(tmp, destHooksPath);
|