@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
|
@@ -18,8 +18,9 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { readLivenessEvents, freshHolders } = require('./lib/liveness-read');
|
|
20
20
|
const { resolveActor } = require('./lib/actor-identity');
|
|
21
|
-
const { flowAgentsArtifactRootsForRead } = require('./lib/local-artifact-paths');
|
|
21
|
+
const { flowAgentsArtifactRootsForRead, resolveSharedRepoRoot, warnIfFailingOpenInsideGitTree } = require('./lib/local-artifact-paths');
|
|
22
22
|
const { readCurrentPointer } = require('./lib/current-pointer');
|
|
23
|
+
const { workflowTriggersFor } = require('./lib/kit-catalog');
|
|
23
24
|
|
|
24
25
|
const STEERING = {
|
|
25
26
|
'tool-planner': [
|
|
@@ -71,14 +72,36 @@ const ACTIVE_STATE_STATUSES = new Set([
|
|
|
71
72
|
]);
|
|
72
73
|
|
|
73
74
|
function findRepoRoot(startDir) {
|
|
74
|
-
|
|
75
|
+
const resolvedStartDir = path.resolve(startDir || process.cwd());
|
|
76
|
+
// #357: prefer git's SHARED common-dir root (the primary checkout's repo root, resolved
|
|
77
|
+
// identically from any linked worktree) before falling back to the plain `.git`/AGENTS.md
|
|
78
|
+
// ancestor walk below. Without this, a worktree's OWN `.git` FILE (not the shared `.git`
|
|
79
|
+
// DIRECTORY) satisfies `fs.existsSync(path.join(dir, '.git'))` at the very first directory
|
|
80
|
+
// checked, so the walk always stopped at the worktree root instead of resolving to the
|
|
81
|
+
// shared primary checkout — see src/lib/local-artifact-root.ts's resolveSharedRepoRoot doc
|
|
82
|
+
// comment for the full rationale. Fails open to the walk below (unchanged) when git is
|
|
83
|
+
// unavailable, `resolvedStartDir` is not inside a git working tree, or the command fails —
|
|
84
|
+
// this is also exactly today's behavior in the single-checkout case, where
|
|
85
|
+
// `--git-common-dir` resolves to `.git` under `resolvedStartDir` itself.
|
|
86
|
+
const sharedRoot = resolveSharedRepoRoot(resolvedStartDir);
|
|
87
|
+
if (sharedRoot) return sharedRoot;
|
|
88
|
+
let dir = resolvedStartDir;
|
|
75
89
|
const root = path.parse(dir).root;
|
|
76
90
|
for (let depth = 0; dir && depth < 40; depth++) {
|
|
77
|
-
if (fs.existsSync(path.join(dir, '.git'))
|
|
91
|
+
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
92
|
+
// #413 iteration-2 Fix 1: resolveSharedRepoRoot failed above even though a `.git` entry
|
|
93
|
+
// exists right here — git resolution was ATTEMPTED and FAILED (corrupted gitlink, bad
|
|
94
|
+
// GIT_DIR, git unavailable, etc.), not merely "no git repo at all". Loud, not silent —
|
|
95
|
+
// see warnIfFailingOpenInsideGitTree's own doc comment for the full rationale. This walk
|
|
96
|
+
// still returns `dir` (the ancestor-walk fallback is preserved unchanged) after warning.
|
|
97
|
+
warnIfFailingOpenInsideGitTree(resolvedStartDir, dir);
|
|
98
|
+
return dir;
|
|
99
|
+
}
|
|
100
|
+
if (fs.existsSync(path.join(dir, 'AGENTS.md'))) return dir;
|
|
78
101
|
if (dir === root) break;
|
|
79
102
|
dir = path.dirname(dir);
|
|
80
103
|
}
|
|
81
|
-
return
|
|
104
|
+
return resolvedStartDir;
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
function walkStateFiles(dir, out = []) {
|
|
@@ -159,8 +182,25 @@ function latestWorkflowState(root) {
|
|
|
159
182
|
return states[0] || null;
|
|
160
183
|
}
|
|
161
184
|
|
|
185
|
+
// #293 AC7 (injection discipline): strips C0 (0x00-0x1F), DEL (0x7F), and C1 (0x80-0x9F,
|
|
186
|
+
// which includes ANSI-CSI-adjacent bytes) BEFORE whitespace-collapse/truncation — mirroring
|
|
187
|
+
// src/cli/workflow-sidecar.ts's stripControlCharsForDisplay / assignment-provider.ts's
|
|
188
|
+
// sanitizeDisplayField exactly (same charset, same rationale: holder/actor/assignee strings
|
|
189
|
+
// sourced from the shared multi-writer liveness stream or an attacker-postable GitHub comment
|
|
190
|
+
// are untrusted display input). Previously this function only collapsed whitespace, so a raw
|
|
191
|
+
// ESC/BEL byte embedded in a hostile actor string passed through unstripped into every steering
|
|
192
|
+
// notice that calls safeStateText (resumeSteering's LIVENESS WARNING block, supersessionSteering's
|
|
193
|
+
// SUPERSEDED notice) — fixed here, in the one shared helper, rather than in each call site.
|
|
162
194
|
function safeStateText(value, maxLength = 240) {
|
|
163
|
-
|
|
195
|
+
// Whitespace-collapse FIRST (so an embedded newline/tab becomes a joining space, preserving
|
|
196
|
+
// the existing multiline-summary neutralization behavior), THEN strip control/ANSI bytes
|
|
197
|
+
// (#293 AC7) — reversing this order would strip \n before the collapse ever sees it, losing
|
|
198
|
+
// the join-space between sentences. Only C0/DEL/C1 bytes THAT SURVIVE the collapse (ESC, BEL,
|
|
199
|
+
// and other non-whitespace control bytes — real \s already became a plain space above) are
|
|
200
|
+
// stripped, mirroring src/cli/workflow-sidecar.ts's stripControlCharsForDisplay /
|
|
201
|
+
// assignment-provider.ts's sanitizeDisplayField charset exactly.
|
|
202
|
+
const collapsed = String(value || '').replace(/\s+/g, ' ').trim();
|
|
203
|
+
const text = collapsed.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
|
|
164
204
|
if (text.length <= maxLength) return text;
|
|
165
205
|
return `${text.slice(0, maxLength - 3)}...`;
|
|
166
206
|
}
|
|
@@ -227,6 +267,9 @@ function stateSteering(root) {
|
|
|
227
267
|
`STATE: ${state.task_slug || path.basename(path.dirname(current.file))} is status:${state.status} phase:${state.phase}.`,
|
|
228
268
|
];
|
|
229
269
|
if (next.summary) parts.push(`Recorded next_action.summary: "${safeStateText(next.summary)}"`);
|
|
270
|
+
if (Array.isArray(next.skills) && next.skills.length) parts.push(`Required skills: ${next.skills.map(skill => safeStateText(skill, 80)).join(' -> ')}.`);
|
|
271
|
+
if (Array.isArray(next.operations) && next.operations.length) parts.push(`Required operations: ${next.operations.map(operation => safeStateText(operation, 80)).join(' -> ')}.`);
|
|
272
|
+
if (next.command) parts.push(`Run: ${safeStateText(next.command, 240)}`);
|
|
230
273
|
if (next.target_phase) parts.push(`Target phase: ${safeStateText(next.target_phase, 80)}.`);
|
|
231
274
|
if (next.status === 'needs_user' || state.status === 'needs_decision' || state.status === 'not_verified') {
|
|
232
275
|
parts.push('Do not deliver as complete until the user decision or accepted gap is recorded.');
|
|
@@ -248,6 +291,52 @@ function contextMapSteering(root) {
|
|
|
248
291
|
].join(' ');
|
|
249
292
|
}
|
|
250
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Compose the SessionStart advisory line for installed-skill drift (#439). Fires only inside a
|
|
296
|
+
* kit-bearing checkout (repo has `kits/` AND a built `dist/claude-code/.claude/skills` bundle) —
|
|
297
|
+
* never triggers a build itself, staying read-only and cheap on every SessionStart. Returns ''
|
|
298
|
+
* (never throws past this function) in every other case: not kit-bearing, no manifest yet, no
|
|
299
|
+
* drift found, or any internal error.
|
|
300
|
+
*
|
|
301
|
+
* Reuses the SINGLE shared classification choke point, `scripts/hooks/lib/skill-drift.js`'s
|
|
302
|
+
* `compareSkillDrift()` — this function must never hand-roll its own drift comparison (same
|
|
303
|
+
* convention the CLI check and the manifest writer follow, see #439 plan).
|
|
304
|
+
*
|
|
305
|
+
* @param {string} root Repository root
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
function skillDriftSteering(root) {
|
|
309
|
+
try {
|
|
310
|
+
if (!fs.existsSync(path.join(root, 'kits'))) return '';
|
|
311
|
+
if (!fs.existsSync(path.join(root, 'dist', 'claude-code', '.claude', 'skills'))) return '';
|
|
312
|
+
|
|
313
|
+
const { loadManifest, compareSkillDrift, resolveClaudeGlobalSkillsDir } = require('./lib/skill-drift');
|
|
314
|
+
|
|
315
|
+
const destDir = resolveClaudeGlobalSkillsDir(process.env);
|
|
316
|
+
// Sibling layout: <destDir>/.flow-agents/skills-manifest.json — mirrors
|
|
317
|
+
// src/lib/local-artifact-root.ts's durableFlowAgentsRoot(destDir)/skillsManifestPath exactly
|
|
318
|
+
// (durableFlowAgentsRoot(cwd) resolves to `${cwd}/.flow-agents`, no parent traversal).
|
|
319
|
+
const manifestPath = path.join(destDir, '.flow-agents', 'skills-manifest.json');
|
|
320
|
+
const manifest = loadManifest(manifestPath);
|
|
321
|
+
|
|
322
|
+
const installedDir = path.join(destDir, 'skills');
|
|
323
|
+
const kitSourceDir = path.join(root, 'dist', 'claude-code', '.claude', 'skills');
|
|
324
|
+
const report = compareSkillDrift({ installedDir, kitSourceDir, manifest });
|
|
325
|
+
if (!report.hasDrift) return '';
|
|
326
|
+
|
|
327
|
+
const { kitUpdated, userModified, unbaselined, missingInstall, kitRemoved } = report.summary;
|
|
328
|
+
// kit_removed (#439 review fix) folds into staleCount alongside the other non-in_sync
|
|
329
|
+
// states — this advisory line only ever surfaces kit-updated/user-modified counts by name
|
|
330
|
+
// (see the template string below), so kit_removed does not get its own named count here; it
|
|
331
|
+
// is still reflected in the total. Full per-state detail is always available via
|
|
332
|
+
// `flow-agents skill-drift-check`.
|
|
333
|
+
const staleCount = kitUpdated + userModified + unbaselined + missingInstall + kitRemoved;
|
|
334
|
+
return `[SKILL DRIFT] ${staleCount} installed Claude Code skill file(s) are stale vs this kit (${kitUpdated} kit-updated, ${userModified} user-modified). Run \`flow-agents init --runtime claude-code --global\` to refresh (user-modified files are reported, not overwritten). See \`flow-agents skill-drift-check\` for details.`;
|
|
335
|
+
} catch {
|
|
336
|
+
return '';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
251
340
|
function promptText(input) {
|
|
252
341
|
const candidates = [
|
|
253
342
|
input && input.prompt,
|
|
@@ -262,7 +351,7 @@ function promptText(input) {
|
|
|
262
351
|
return '';
|
|
263
352
|
}
|
|
264
353
|
|
|
265
|
-
function
|
|
354
|
+
function looksLikeImplementationWork(text) {
|
|
266
355
|
const normalized = safeStateText(text, 2000).toLowerCase();
|
|
267
356
|
if (!normalized) return false;
|
|
268
357
|
const readOnlyIntent = /\b(read[- ]only|review[- ]only|no (source |code |file )?(changes?|edits?|modifications?)|do not (modify|edit|change|write|fix|implement|touch)|don't (modify|edit|change|write|fix|implement|touch)|dont (modify|edit|change|write|fix|implement|touch))\b/.test(normalized);
|
|
@@ -281,17 +370,29 @@ function looksLikeBuilderWork(text) {
|
|
|
281
370
|
return hasWorkVerb || (hasWorkObject && hasRequestLanguage);
|
|
282
371
|
}
|
|
283
372
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
]
|
|
373
|
+
function looksLikeKnowledgeWork(text) {
|
|
374
|
+
const normalized = safeStateText(text, 2000).toLowerCase();
|
|
375
|
+
if (!normalized) return false;
|
|
376
|
+
return /\b(remember|capture|save|file|bookmark)\s+(this|that|the|my|our)\b/.test(normalized) ||
|
|
377
|
+
/\bcapture\s+(this\s+)?(decision|learning|lesson|note|context)\b/.test(normalized) ||
|
|
378
|
+
/\bwhat did we learn about\b/.test(normalized);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function kitWorkflowSteering(input, root) {
|
|
382
|
+
const prompt = promptText(input);
|
|
383
|
+
const categories = [];
|
|
384
|
+
if (looksLikeImplementationWork(prompt)) categories.push('implementation-work-detected');
|
|
385
|
+
if (looksLikeKnowledgeWork(prompt)) categories.push('knowledge-capture-detected');
|
|
386
|
+
if (!categories.length) return '';
|
|
387
|
+
try {
|
|
388
|
+
return categories
|
|
389
|
+
.flatMap(category => workflowTriggersFor(root, category))
|
|
390
|
+
.map(trigger => trigger.steering)
|
|
391
|
+
.filter(Boolean)
|
|
392
|
+
.join('\n');
|
|
393
|
+
} catch {
|
|
394
|
+
return '';
|
|
395
|
+
}
|
|
295
396
|
}
|
|
296
397
|
|
|
297
398
|
/**
|
|
@@ -324,6 +425,13 @@ function resumeSteering(root, current) {
|
|
|
324
425
|
// Full next action (240-char display path, not the 80-char normalization)
|
|
325
426
|
const nextSummary = next.summary ? safeStateText(next.summary, 240) : 'none';
|
|
326
427
|
lines.push(`Next action: ${nextSummary}`);
|
|
428
|
+
if (Array.isArray(next.skills) && next.skills.length) {
|
|
429
|
+
lines.push(`Required skills: ${next.skills.map(skill => safeStateText(skill, 80)).join(' -> ')}`);
|
|
430
|
+
}
|
|
431
|
+
if (Array.isArray(next.operations) && next.operations.length) {
|
|
432
|
+
lines.push(`Required operations: ${next.operations.map(operation => safeStateText(operation, 80)).join(' -> ')}`);
|
|
433
|
+
}
|
|
434
|
+
if (next.command) lines.push(`Run: ${safeStateText(next.command, 240)}`);
|
|
327
435
|
|
|
328
436
|
// Plan artifact path
|
|
329
437
|
let planPath = 'not found';
|
|
@@ -411,6 +519,51 @@ function resumeSteering(root, current) {
|
|
|
411
519
|
}
|
|
412
520
|
}
|
|
413
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Compose the every-turn SUPERSEDED steering notice (issue #293).
|
|
524
|
+
*
|
|
525
|
+
* Unlike resumeSteering()'s RESUME block (SessionStart only), this notice must
|
|
526
|
+
* surface on EVERY turn (UserPromptSubmit as well as SessionStart) once another
|
|
527
|
+
* actor holds a fresh liveness claim on the caller's own subject — the
|
|
528
|
+
* "don't publish, you may be stale" signal the verify-hold gate backstops at
|
|
529
|
+
* publish time. Reuses the SAME liveness-only join resumeSteering()'s liveness
|
|
530
|
+
* advisory block already performs (freshHolders(events, slug, selfActor, now)),
|
|
531
|
+
* not a second computation.
|
|
532
|
+
*
|
|
533
|
+
* freshHolders() already excludes selfActor internally (see
|
|
534
|
+
* scripts/hooks/lib/liveness-read.js), so any holder returned here is by
|
|
535
|
+
* definition another actor — no additional self-filter is required.
|
|
536
|
+
*
|
|
537
|
+
* Fully fail-open: any error anywhere in this function returns '' rather than
|
|
538
|
+
* throwing, matching every other steering helper in this file.
|
|
539
|
+
*
|
|
540
|
+
* @param {string} root Repository root
|
|
541
|
+
* @param {{ file: string, payload: object }} current Latest active state entry (this actor's own)
|
|
542
|
+
* @returns {string}
|
|
543
|
+
*/
|
|
544
|
+
function supersessionSteering(root, current) {
|
|
545
|
+
try {
|
|
546
|
+
const state = current.payload;
|
|
547
|
+
const workflowDir = path.dirname(current.file);
|
|
548
|
+
const slug = state.task_slug || path.basename(workflowDir);
|
|
549
|
+
|
|
550
|
+
const resolved = resolveActor(process.env);
|
|
551
|
+
const selfActor = resolved.actor || 'local';
|
|
552
|
+
|
|
553
|
+
const events = flowAgentsArtifactRootsForRead(root)
|
|
554
|
+
.flatMap(artifactRoot => readLivenessEvents(path.join(artifactRoot, 'liveness', 'events.jsonl')));
|
|
555
|
+
if (events.length === 0) return '';
|
|
556
|
+
|
|
557
|
+
const holders = freshHolders(events, slug, selfActor, Date.now());
|
|
558
|
+
if (holders.length === 0) return '';
|
|
559
|
+
|
|
560
|
+
const holder = holders[0];
|
|
561
|
+
return `[SUPERSEDED: another agent appears live on this work: actor ${safeStateText(holder.actor)}, last seen ${holder.lastAt}. Your claim on this subject may be stale — do not publish until you re-verify hold (see verify-hold gate) or hand off.]`;
|
|
562
|
+
} catch {
|
|
563
|
+
return '';
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
414
567
|
function run(rawInput) {
|
|
415
568
|
try {
|
|
416
569
|
const input = JSON.parse(rawInput);
|
|
@@ -443,18 +596,35 @@ function run(rawInput) {
|
|
|
443
596
|
}
|
|
444
597
|
|
|
445
598
|
if (event === 'UserPromptSubmit') {
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
hints.push(
|
|
599
|
+
const kitHint = kitWorkflowSteering(input, root);
|
|
600
|
+
if (kitHint) {
|
|
601
|
+
hints.push(kitHint);
|
|
449
602
|
const contextHint = contextMapSteering(root);
|
|
450
603
|
if (contextHint) hints.push(contextHint);
|
|
451
604
|
}
|
|
452
605
|
}
|
|
453
606
|
|
|
607
|
+
// Every-turn supersession notice (#293): fires on UserPromptSubmit (every turn),
|
|
608
|
+
// not just SessionStart, so a takeover mid-session keeps surfacing the warning.
|
|
609
|
+
if (event === 'UserPromptSubmit' && current) {
|
|
610
|
+
const supersessionHint = supersessionSteering(root, current);
|
|
611
|
+
if (supersessionHint) hints.push(supersessionHint);
|
|
612
|
+
}
|
|
613
|
+
|
|
454
614
|
// SessionStart only: append the RESUME block for richer situational awareness
|
|
455
615
|
if (event === 'SessionStart' && current) {
|
|
456
616
|
const resumeBlock = resumeSteering(root, current);
|
|
457
617
|
if (resumeBlock) hints.push(resumeBlock);
|
|
618
|
+
const supersessionHint = supersessionSteering(root, current);
|
|
619
|
+
if (supersessionHint) hints.push(supersessionHint);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// SessionStart only, unconditional of `current` (#439): the installed-skill drift advisory
|
|
623
|
+
// fires on every SessionStart inside a kit-bearing checkout, independent of whether an active
|
|
624
|
+
// workflow session exists — do NOT fold this into the `current`-gated block above.
|
|
625
|
+
if (event === 'SessionStart') {
|
|
626
|
+
const driftHint = skillDriftSteering(root);
|
|
627
|
+
if (driftHint) hints.push(driftHint);
|
|
458
628
|
}
|
|
459
629
|
|
|
460
630
|
if (shouldAppendWorkflowContext) {
|
|
@@ -487,12 +657,14 @@ module.exports = {
|
|
|
487
657
|
stateSteering,
|
|
488
658
|
critiqueSteering,
|
|
489
659
|
contextMapSteering,
|
|
660
|
+
skillDriftSteering,
|
|
490
661
|
latestWorkflowState,
|
|
491
662
|
findRepoRoot,
|
|
492
663
|
safeStateText,
|
|
493
664
|
stateNeedsAmbientSteering,
|
|
494
665
|
resumeSteering,
|
|
666
|
+
supersessionSteering,
|
|
495
667
|
promptText,
|
|
496
|
-
|
|
497
|
-
|
|
668
|
+
looksLikeImplementationWork,
|
|
669
|
+
kitWorkflowSteering,
|
|
498
670
|
};
|
|
@@ -15,6 +15,10 @@ Options:
|
|
|
15
15
|
--console-token-file PATH
|
|
16
16
|
File containing the bearer token.
|
|
17
17
|
--console-tenant ID Tenant identifier for hosted Console routing.
|
|
18
|
+
--no-economics-relay Write console_economics_relay=0 to opt out of the
|
|
19
|
+
automatic kit-economics relay (default-on once a
|
|
20
|
+
Console telemetry sink is configured; see
|
|
21
|
+
lib/config.sh).
|
|
18
22
|
EOF
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -105,6 +109,7 @@ main() {
|
|
|
105
109
|
local console_token=""
|
|
106
110
|
local console_token_file="${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-${CONSOLE_TELEMETRY_TOKEN_FILE:-}}"
|
|
107
111
|
local console_tenant="${FLOW_AGENTS_CONSOLE_TENANT:-${CONSOLE_TENANT_ID:-}}"
|
|
112
|
+
local no_economics_relay=0
|
|
108
113
|
telemetry_sinks=()
|
|
109
114
|
if [[ -n "${FLOW_AGENTS_TELEMETRY_SINKS:-}" ]]; then
|
|
110
115
|
append_sinks "$FLOW_AGENTS_TELEMETRY_SINKS"
|
|
@@ -139,6 +144,10 @@ main() {
|
|
|
139
144
|
console_tenant="$2"
|
|
140
145
|
shift 2
|
|
141
146
|
;;
|
|
147
|
+
--no-economics-relay)
|
|
148
|
+
no_economics_relay=1
|
|
149
|
+
shift
|
|
150
|
+
;;
|
|
142
151
|
--help|-h)
|
|
143
152
|
usage
|
|
144
153
|
exit 0
|
|
@@ -181,7 +190,12 @@ main() {
|
|
|
181
190
|
done
|
|
182
191
|
[[ "$console_sink_count" -le 1 ]] || die "select at most one Console telemetry sink"
|
|
183
192
|
|
|
184
|
-
if [[
|
|
193
|
+
if [[ "$console_sink_count" -gt 0 && -z "$console_tenant" ]]; then
|
|
194
|
+
echo "warning: install-console-config.sh: a Console telemetry sink was selected with no --console-tenant; records will be untenanted" >&2
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
if [[ -z "$console_url" && -z "$console_endpoint" && -z "$console_token" && -z "$console_tenant" \
|
|
198
|
+
&& "$no_economics_relay" -eq 0 ]]; then
|
|
185
199
|
return 0
|
|
186
200
|
fi
|
|
187
201
|
if [[ -z "$console_url" && -z "$console_endpoint" && ( -n "$console_token" || -n "$console_tenant" ) ]]; then
|
|
@@ -199,15 +213,22 @@ main() {
|
|
|
199
213
|
set_config_key "$conf" "console_telemetry_endpoint_url" "$console_endpoint"
|
|
200
214
|
set_config_key "$conf" "console_telemetry_token" "$console_token"
|
|
201
215
|
set_config_key "$conf" "console_tenant_id" "$console_tenant"
|
|
216
|
+
if [[ "$no_economics_relay" -eq 1 ]]; then
|
|
217
|
+
set_config_key "$conf" "console_economics_relay" "0"
|
|
218
|
+
fi
|
|
202
219
|
if [[ -n "$console_token" ]]; then
|
|
203
220
|
chmod 600 "$conf" 2>/dev/null || true
|
|
204
221
|
fi
|
|
205
222
|
|
|
206
223
|
local target="${console_endpoint:-$console_url}"
|
|
207
|
-
if [[ -n "$
|
|
208
|
-
|
|
224
|
+
if [[ -n "$target" ]]; then
|
|
225
|
+
if [[ -n "$console_tenant" ]]; then
|
|
226
|
+
echo "Configured Console telemetry in $conf for $target (tenant: $console_tenant)"
|
|
227
|
+
else
|
|
228
|
+
echo "Configured Console telemetry in $conf for $target"
|
|
229
|
+
fi
|
|
209
230
|
else
|
|
210
|
-
echo "
|
|
231
|
+
echo "Recorded economics-relay opt-out in $conf"
|
|
211
232
|
fi
|
|
212
233
|
}
|
|
213
234
|
|
|
@@ -2,14 +2,78 @@
|
|
|
2
2
|
# config.sh — Load telemetry configuration with defaults
|
|
3
3
|
|
|
4
4
|
TELEMETRY_DIR="${TELEMETRY_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
5
|
-
|
|
5
|
+
# TELEMETRY_DIR is <workspace>/scripts/telemetry, so the workspace root is
|
|
6
|
+
# two levels up.
|
|
7
|
+
TELEMETRY_WORKSPACE_ROOT="${TELEMETRY_WORKSPACE_ROOT:-$(cd "${TELEMETRY_DIR}/../.." && pwd)}"
|
|
8
|
+
|
|
9
|
+
# A workspace/global conf only counts as operator-created (and thus trusted
|
|
10
|
+
# with credentials) if it is mode 600 and owned by the current user.
|
|
11
|
+
# install-console-config.sh always chmod 600s the conf it writes; git can
|
|
12
|
+
# only store 644/755 (never 600), so this gate rejects a conf smuggled in via
|
|
13
|
+
# clone/tarball/PR/supply-chain even if it happens to land at the expected
|
|
14
|
+
# path. Portable across macOS/BSD stat (-f) and GNU/Linux stat (-c).
|
|
15
|
+
telemetry_conf_trusted() {
|
|
16
|
+
local file="$1" mode owner
|
|
17
|
+
# Reject symlinks outright: stat below reports the link's own mode/owner
|
|
18
|
+
# (lstat semantics, no -L), but the config file is later read by following
|
|
19
|
+
# the link, so a symlink lets `chmod -h 600 <link>` pass this gate while
|
|
20
|
+
# the read lands on a different, untrusted target. Never trust a link.
|
|
21
|
+
[[ -L "$file" ]] && return 1
|
|
22
|
+
[[ -f "$file" ]] || return 1
|
|
23
|
+
mode=$(stat -f '%Lp' "$file" 2>/dev/null) || mode=$(stat -c '%a' "$file" 2>/dev/null)
|
|
24
|
+
owner=$(stat -f '%u' "$file" 2>/dev/null) || owner=$(stat -c '%u' "$file" 2>/dev/null)
|
|
25
|
+
[[ "$mode" == "600" && -n "$owner" && "$owner" == "$(id -u)" ]]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# A candidate that exists on disk but fails telemetry_conf_trusted would
|
|
29
|
+
# otherwise be silently ignored (telemetry stays fail-open with no signal to
|
|
30
|
+
# the operator). Print a one-time, visible warning naming the path and the
|
|
31
|
+
# fix whenever this happens. Never warn when the file is simply absent, and
|
|
32
|
+
# never change which file TELEMETRY_CONFIG_FILE resolves to below -- this is
|
|
33
|
+
# purely additive/observational.
|
|
34
|
+
telemetry_conf_warn_untrusted() {
|
|
35
|
+
local file="$1"
|
|
36
|
+
# `-e` catches a regular file; `-L` additionally catches a symlink that
|
|
37
|
+
# `-e` would miss (a symlink to a directory, or a dangling symlink) --
|
|
38
|
+
# both of those are rejected by telemetry_conf_trusted's `-L` check above
|
|
39
|
+
# but must still surface this warning, not silently emit none.
|
|
40
|
+
[[ -e "$file" || -L "$file" ]] || return 0
|
|
41
|
+
printf 'warning: config.sh: %s exists but is not trusted (must be mode 600, owned by the current user, and not a symlink); telemetry will ignore it and stay fail-open. Fix with: chmod 600 "%s"\n' "$file" "$file" >&2
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Config file resolution order: (1) explicit TELEMETRY_CONFIG_FILE env always
|
|
45
|
+
# wins; (2) a gitignored per-workspace conf at .kontourai/telemetry-console.conf
|
|
46
|
+
# if present and operator-trusted (project-specific override); (3) a
|
|
47
|
+
# gitignored user-global conf at ~/.flow-agents/telemetry-console.conf if
|
|
48
|
+
# present and operator-trusted, matching the existing ~/.flow-agents
|
|
49
|
+
# install-home convention (see scripts/discover-agents.sh,
|
|
50
|
+
# scripts/context-budget/budget-scan.sh) so one machine-wide install can carry owner
|
|
51
|
+
# credentials without per-workspace wiring; (4) the shipped telemetry.conf
|
|
52
|
+
# default. The shipped scripts/telemetry/telemetry.conf is tracked and ships
|
|
53
|
+
# into every packaged dist bundle, so it must never carry owner credentials.
|
|
54
|
+
# A candidate that exists but fails the trust gate falls through silently
|
|
55
|
+
# (telemetry stays fail-open).
|
|
56
|
+
TELEMETRY_LOCAL_CONFIG_FILE="${TELEMETRY_WORKSPACE_ROOT}/.kontourai/telemetry-console.conf"
|
|
57
|
+
TELEMETRY_GLOBAL_CONFIG_FILE="${HOME:-}/.flow-agents/telemetry-console.conf"
|
|
58
|
+
if [[ -n "${TELEMETRY_CONFIG_FILE:-}" ]]; then
|
|
59
|
+
:
|
|
60
|
+
elif telemetry_conf_trusted "$TELEMETRY_LOCAL_CONFIG_FILE"; then
|
|
61
|
+
TELEMETRY_CONFIG_FILE="$TELEMETRY_LOCAL_CONFIG_FILE"
|
|
62
|
+
else
|
|
63
|
+
telemetry_conf_warn_untrusted "$TELEMETRY_LOCAL_CONFIG_FILE"
|
|
64
|
+
if [[ -n "${HOME:-}" ]] && telemetry_conf_trusted "$TELEMETRY_GLOBAL_CONFIG_FILE"; then
|
|
65
|
+
TELEMETRY_CONFIG_FILE="$TELEMETRY_GLOBAL_CONFIG_FILE"
|
|
66
|
+
else
|
|
67
|
+
[[ -n "${HOME:-}" ]] && telemetry_conf_warn_untrusted "$TELEMETRY_GLOBAL_CONFIG_FILE"
|
|
68
|
+
TELEMETRY_CONFIG_FILE="${TELEMETRY_DIR}/telemetry.conf"
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
6
71
|
|
|
7
72
|
# Defaults
|
|
8
73
|
TELEMETRY_ENABLED="${TELEMETRY_ENABLED:-true}"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-$(cd "${TELEMETRY_DIR}/../.." && pwd)/.kontourai/telemetry}"
|
|
74
|
+
# Local runtime telemetry defaults under .kontourai/telemetry; explicit
|
|
75
|
+
# TELEMETRY_DATA_DIR still wins.
|
|
76
|
+
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-${TELEMETRY_WORKSPACE_ROOT}/.kontourai/telemetry}"
|
|
13
77
|
TELEMETRY_SESSION_DIR="${TELEMETRY_SESSION_DIR:-${TELEMETRY_DATA_DIR}/sessions}"
|
|
14
78
|
TELEMETRY_ENRICH_SYSTEM="${TELEMETRY_ENRICH_SYSTEM:-true}"
|
|
15
79
|
TELEMETRY_ENRICH_WORKSPACE="${TELEMETRY_ENRICH_WORKSPACE:-true}"
|
|
@@ -37,9 +101,16 @@ CONSOLE_TELEMETRY_URL="${CONSOLE_TELEMETRY_URL:-${CONSOLE_URL:-}}"
|
|
|
37
101
|
CONSOLE_TELEMETRY_ENDPOINT_URL="${CONSOLE_TELEMETRY_ENDPOINT_URL:-}"
|
|
38
102
|
CONSOLE_TELEMETRY_TOKEN="${CONSOLE_TELEMETRY_TOKEN:-${CONSOLE_AUTH_TOKEN:-}}"
|
|
39
103
|
CONSOLE_TENANT_ID="${CONSOLE_TENANT_ID:-}"
|
|
104
|
+
# Economics relay (#469): a caller-pre-set env var is preserved as the starting point; the
|
|
105
|
+
# config-file key (parsed below) can override it, and if no key is present at all the
|
|
106
|
+
# default-on rule below turns it on once a console telemetry sink resolves.
|
|
107
|
+
FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY="${FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY:-}"
|
|
108
|
+
FLOW_AGENTS_CONSOLE_ECONOMICS_ENDPOINT_URL="${FLOW_AGENTS_CONSOLE_ECONOMICS_ENDPOINT_URL:-}"
|
|
109
|
+
# Set (non-empty) only when the config file carries an explicit console_economics_relay key —
|
|
110
|
+
# distinguishes "operator said 0/1" from "key absent" for the default-on rule below.
|
|
111
|
+
console_economics_relay_raw=""
|
|
40
112
|
# Pricing registry source (consumed by lib/pricing.sh). Explicit file/URL win;
|
|
41
|
-
# otherwise
|
|
42
|
-
# live pricing source. Falls back to the bundled pricing.json offline.
|
|
113
|
+
# otherwise lib/pricing.sh uses the bundled pricing.json offline.
|
|
43
114
|
TELEMETRY_PRICING_FILE="${TELEMETRY_PRICING_FILE:-${FLOW_AGENTS_PRICING_FILE:-}}"
|
|
44
115
|
TELEMETRY_PRICING_URL="${TELEMETRY_PRICING_URL:-${FLOW_AGENTS_PRICING_URL:-}}"
|
|
45
116
|
|
|
@@ -82,6 +153,17 @@ if [[ -f "$TELEMETRY_CONFIG_FILE" ]]; then
|
|
|
82
153
|
console_telemetry_token) CONSOLE_TELEMETRY_TOKEN="$value" ;;
|
|
83
154
|
console_tenant_id) CONSOLE_TENANT_ID="$value" ;;
|
|
84
155
|
console_telemetry_redact) CONSOLE_TELEMETRY_REDACT="$value" ;;
|
|
156
|
+
console_economics_relay)
|
|
157
|
+
case "$(echo "$value" | tr '[:upper:]' '[:lower:]')" in
|
|
158
|
+
1|true|yes|on) console_economics_relay_raw="1" ;;
|
|
159
|
+
0|false|no|off) console_economics_relay_raw="0" ;;
|
|
160
|
+
*)
|
|
161
|
+
printf 'warning: config.sh: unrecognized console_economics_relay value %q; treating as off\n' "$value" >&2
|
|
162
|
+
console_economics_relay_raw="$value"
|
|
163
|
+
;;
|
|
164
|
+
esac
|
|
165
|
+
;;
|
|
166
|
+
console_economics_endpoint_url) FLOW_AGENTS_CONSOLE_ECONOMICS_ENDPOINT_URL="$value" ;;
|
|
85
167
|
console_pricing_url) TELEMETRY_PRICING_URL="$value" ;;
|
|
86
168
|
pricing_url) TELEMETRY_PRICING_URL="$value" ;;
|
|
87
169
|
pricing_file) TELEMETRY_PRICING_FILE="$value" ;;
|
|
@@ -92,12 +174,20 @@ fi
|
|
|
92
174
|
|
|
93
175
|
CONSOLE_TELEMETRY_REDACT="${CONSOLE_TELEMETRY_REDACT:-${TELEMETRY_CHANNEL_ANALYTICS_REDACT}}"
|
|
94
176
|
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
177
|
+
# Economics relay default-on rule (#469, opt-out not silent-off): an explicit
|
|
178
|
+
# console_economics_relay config key always wins. Otherwise, once a console telemetry sink is
|
|
179
|
+
# resolved (console_telemetry_url/console_telemetry_endpoint_url non-empty) the relay defaults ON —
|
|
180
|
+
# unless a caller already pre-set FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY in the environment, which is
|
|
181
|
+
# left untouched. economics-record.sh's own opt-in gate (unchanged) reads this exact variable.
|
|
182
|
+
if [[ -n "$console_economics_relay_raw" ]]; then
|
|
183
|
+
FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY="$console_economics_relay_raw"
|
|
184
|
+
elif [[ -z "$FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY" \
|
|
185
|
+
&& ( -n "${CONSOLE_TELEMETRY_URL:-}" || -n "${CONSOLE_TELEMETRY_ENDPOINT_URL:-}" ) ]]; then
|
|
186
|
+
FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY=1
|
|
100
187
|
fi
|
|
101
188
|
|
|
189
|
+
# Pricing URL is explicit-only (env or config). Do not derive the console
|
|
190
|
+
# pricing endpoint by default; the bundled registry is the reliable offline floor.
|
|
191
|
+
|
|
102
192
|
# Ensure directories exist
|
|
103
193
|
mkdir -p "$TELEMETRY_DATA_DIR" "$TELEMETRY_SESSION_DIR" 2>/dev/null
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pricing.sh — single-source pricing registry loader.
|
|
3
|
+
#
|
|
4
|
+
# Resolves the versioned pricing registry (pricing.json) from, in priority:
|
|
5
|
+
# 1. explicit local file TELEMETRY_PRICING_FILE / FLOW_AGENTS_PRICING_FILE
|
|
6
|
+
# 2. remote URL (cached) TELEMETRY_PRICING_URL / FLOW_AGENTS_PRICING_URL
|
|
7
|
+
# 3. bundled snapshot <telemetry>/pricing.json
|
|
8
|
+
# This is the one source every runtime + the console read from — local for
|
|
9
|
+
# air-gapped use, remote for a single live registry shared across machines.
|
|
10
|
+
|
|
11
|
+
PRICING_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
|
|
13
|
+
pricing_registry_valid_json() {
|
|
14
|
+
command -v jq >/dev/null 2>&1 || return 1
|
|
15
|
+
jq -e '.current_version and .versions' >/dev/null 2>&1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Echo the raw registry JSON. Returns non-zero if nothing resolvable.
|
|
19
|
+
pricing_registry() {
|
|
20
|
+
local f="${TELEMETRY_PRICING_FILE:-${FLOW_AGENTS_PRICING_FILE:-}}"
|
|
21
|
+
if [[ -n "$f" && -f "$f" ]]; then cat "$f"; return 0; fi
|
|
22
|
+
|
|
23
|
+
local url="${TELEMETRY_PRICING_URL:-${FLOW_AGENTS_PRICING_URL:-}}"
|
|
24
|
+
if [[ -n "$url" ]] && command -v curl >/dev/null 2>&1; then
|
|
25
|
+
local cache="${TMPDIR:-/tmp}/flow-agents-pricing-cache.json"
|
|
26
|
+
local ttl="${TELEMETRY_PRICING_TTL_SEC:-3600}"
|
|
27
|
+
if [[ -f "$cache" ]]; then
|
|
28
|
+
local mtime now age
|
|
29
|
+
mtime=$(stat -c %Y "$cache" 2>/dev/null || stat -f %m "$cache" 2>/dev/null || echo 0)
|
|
30
|
+
[[ "$mtime" =~ ^[0-9]+$ ]] || mtime=0
|
|
31
|
+
now=$(date +%s)
|
|
32
|
+
age=$(( now - mtime ))
|
|
33
|
+
if [[ "$age" -lt "$ttl" ]] && pricing_registry_valid_json < "$cache"; then cat "$cache"; return 0; fi
|
|
34
|
+
fi
|
|
35
|
+
if curl -fsS --max-time 5 "$url" -o "${cache}.tmp" 2>/dev/null && [[ -s "${cache}.tmp" ]]; then
|
|
36
|
+
if pricing_registry_valid_json < "${cache}.tmp"; then
|
|
37
|
+
mv "${cache}.tmp" "$cache"
|
|
38
|
+
cat "$cache"
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
rm -f "${cache}.tmp" 2>/dev/null
|
|
43
|
+
[[ -f "$cache" ]] && pricing_registry_valid_json < "$cache" && { cat "$cache"; return 0; }
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
local bundled
|
|
47
|
+
bundled="$(cd "${PRICING_LIB_DIR}/.." && pwd)/pricing.json"
|
|
48
|
+
[[ -f "$bundled" ]] && { cat "$bundled"; return 0; }
|
|
49
|
+
return 1
|
|
50
|
+
}
|
|
@@ -60,4 +60,7 @@ session_end() {
|
|
|
60
60
|
|
|
61
61
|
session_cleanup() {
|
|
62
62
|
find "${TELEMETRY_SESSION_DIR}" -name "*.session" -mtime +1 -delete 2>/dev/null || true
|
|
63
|
+
# Bound the per-cwd project-label cache (see console_project_label) so a project rename
|
|
64
|
+
# (package.json name / git remote) self-heals within the same window instead of caching forever.
|
|
65
|
+
find "${TELEMETRY_SESSION_DIR}" -name "project-label.*" -mtime +1 -delete 2>/dev/null || true
|
|
63
66
|
}
|