@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
|
};
|
|
@@ -51,6 +51,41 @@ while [[ $# -gt 0 ]]; do
|
|
|
51
51
|
esac
|
|
52
52
|
done
|
|
53
53
|
REAL_CODEX_HOME="${CODEX_REAL_HOME:-$HOME/.codex}"
|
|
54
|
+
|
|
55
|
+
canonicalize_path() {
|
|
56
|
+
node - "$1" <<'NODE'
|
|
57
|
+
const fs = require("node:fs");
|
|
58
|
+
const path = require("node:path");
|
|
59
|
+
let current = path.resolve(process.argv[2]);
|
|
60
|
+
const missing = [];
|
|
61
|
+
while (!fs.existsSync(current)) {
|
|
62
|
+
missing.unshift(path.basename(current));
|
|
63
|
+
const parent = path.dirname(current);
|
|
64
|
+
if (parent === current) break;
|
|
65
|
+
current = parent;
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(path.resolve(fs.realpathSync(current), ...missing));
|
|
68
|
+
NODE
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
[[ ! -L "$DEST" ]] || { echo "install-codex-home.sh: refusing symlink destination root: $DEST" >&2; exit 1; }
|
|
72
|
+
ROOT_REAL="$(canonicalize_path "$ROOT_DIR")"
|
|
73
|
+
BUNDLE_SOURCE_REAL="$(canonicalize_path "$ROOT_DIR/dist/codex")"
|
|
74
|
+
DEST_REAL="$(canonicalize_path "$DEST")"
|
|
75
|
+
REAL_CODEX_HOME_REAL="$(canonicalize_path "$REAL_CODEX_HOME")"
|
|
76
|
+
case "$DEST_REAL/" in
|
|
77
|
+
"$ROOT_REAL/"*|"$BUNDLE_SOURCE_REAL/"*)
|
|
78
|
+
echo "install-codex-home.sh: destination overlaps Flow Agents source: $DEST_REAL" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
case "$ROOT_REAL/" in
|
|
83
|
+
"$DEST_REAL/"*)
|
|
84
|
+
echo "install-codex-home.sh: Flow Agents source overlaps destination: $DEST_REAL" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
;;
|
|
87
|
+
esac
|
|
88
|
+
|
|
54
89
|
if command -v npm >/dev/null 2>&1; then
|
|
55
90
|
(cd "$ROOT_DIR" && FLOW_AGENTS_EXPORT_DIAGNOSTICS=0 npm run build:bundles --silent >/dev/null)
|
|
56
91
|
else
|
|
@@ -59,7 +94,8 @@ else
|
|
|
59
94
|
fi
|
|
60
95
|
|
|
61
96
|
mkdir -p "$DEST"
|
|
62
|
-
|
|
97
|
+
DEST="$(cd "$DEST" && pwd -P)"
|
|
98
|
+
DEST_REAL="$DEST"
|
|
63
99
|
|
|
64
100
|
assert_safe_dest_path() {
|
|
65
101
|
local rel="$1"
|
|
@@ -95,10 +131,17 @@ if [[ -f "$DEST/hooks.json" ]]; then
|
|
|
95
131
|
cp "$DEST/hooks.json" "$FA_USER_HOOKS_STASH"
|
|
96
132
|
fi
|
|
97
133
|
|
|
98
|
-
# A real Codex home can contain user-owned
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
134
|
+
# A real Codex home can contain user-owned files in every shared directory.
|
|
135
|
+
# Prepare an exact Flow Agents overlay, then synchronize only files recorded in
|
|
136
|
+
# the ownership manifest. The synchronizer never uses directory-wide --delete:
|
|
137
|
+
# it removes only unchanged files from its prior manifest and refuses collisions.
|
|
138
|
+
FA_OWNED_OVERLAY="$(mktemp -d /tmp/fa-codex-overlay.XXXXXX)"
|
|
139
|
+
cleanup_install_temps() {
|
|
140
|
+
rm -rf "$FA_OWNED_OVERLAY"
|
|
141
|
+
[[ -z "${FA_USER_HOOKS_STASH:-}" ]] || rm -f "$FA_USER_HOOKS_STASH"
|
|
142
|
+
}
|
|
143
|
+
trap cleanup_install_temps EXIT
|
|
144
|
+
|
|
102
145
|
for managed_dir in \
|
|
103
146
|
.flow-agents \
|
|
104
147
|
agent-cards \
|
|
@@ -115,52 +158,65 @@ for managed_dir in \
|
|
|
115
158
|
do
|
|
116
159
|
if [[ -d "$ROOT_DIR/dist/codex/$managed_dir" ]]; then
|
|
117
160
|
assert_safe_dest_path "$managed_dir"
|
|
118
|
-
mkdir -p "$
|
|
119
|
-
rsync -a
|
|
161
|
+
mkdir -p "$FA_OWNED_OVERLAY/$managed_dir"
|
|
162
|
+
rsync -a "$ROOT_DIR/dist/codex/$managed_dir/" "$FA_OWNED_OVERLAY/$managed_dir/"
|
|
120
163
|
fi
|
|
121
164
|
done
|
|
122
165
|
|
|
123
166
|
# Skills are user-extensible in a real Codex home. Merge both bundle skill
|
|
124
167
|
# layers into the flattened destination without deleting user-owned skills.
|
|
125
168
|
if [[ -d "$ROOT_DIR/dist/codex/skills" ]]; then
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
rsync -a "$ROOT_DIR/dist/codex/skills/" "$DEST/skills/"
|
|
169
|
+
mkdir -p "$FA_OWNED_OVERLAY/skills"
|
|
170
|
+
rsync -a "$ROOT_DIR/dist/codex/skills/" "$FA_OWNED_OVERLAY/skills/"
|
|
129
171
|
fi
|
|
130
172
|
|
|
131
173
|
if [[ -d "$ROOT_DIR/dist/codex/.codex/skills" ]]; then
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
rsync -a "$ROOT_DIR/dist/codex/.codex/skills/" "$DEST/skills/"
|
|
174
|
+
mkdir -p "$FA_OWNED_OVERLAY/skills"
|
|
175
|
+
rsync -a "$ROOT_DIR/dist/codex/.codex/skills/" "$FA_OWNED_OVERLAY/skills/"
|
|
135
176
|
fi
|
|
136
177
|
|
|
137
178
|
if [[ -d "$ROOT_DIR/dist/codex/kits" ]]; then
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
rsync -a --delete --exclude 'local/' "$ROOT_DIR/dist/codex/kits/" "$DEST/kits/"
|
|
179
|
+
mkdir -p "$FA_OWNED_OVERLAY/kits"
|
|
180
|
+
rsync -a --exclude 'local/' "$ROOT_DIR/dist/codex/kits/" "$FA_OWNED_OVERLAY/kits/"
|
|
141
181
|
fi
|
|
142
182
|
|
|
143
183
|
# Agents are user-extensible in a real Codex home. Merge generated agents
|
|
144
184
|
# without deleting user-owned agents.
|
|
145
185
|
if [[ -d "$ROOT_DIR/dist/codex/.codex/agents" ]]; then
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
rsync -a "$ROOT_DIR/dist/codex/.codex/agents/" "$DEST/agents/"
|
|
186
|
+
mkdir -p "$FA_OWNED_OVERLAY/agents"
|
|
187
|
+
rsync -a "$ROOT_DIR/dist/codex/.codex/agents/" "$FA_OWNED_OVERLAY/agents/"
|
|
149
188
|
fi
|
|
150
189
|
|
|
151
190
|
for bundle_file in README.md console.telemetry.json install.sh; do
|
|
152
191
|
if [[ -f "$ROOT_DIR/dist/codex/$bundle_file" ]]; then
|
|
153
|
-
|
|
154
|
-
cp "$ROOT_DIR/dist/codex/$bundle_file" "$DEST/$bundle_file"
|
|
192
|
+
cp "$ROOT_DIR/dist/codex/$bundle_file" "$FA_OWNED_OVERLAY/$bundle_file"
|
|
155
193
|
fi
|
|
156
194
|
done
|
|
157
195
|
|
|
196
|
+
assert_safe_dest_path ".flow-agents/codex-install-manifest.json"
|
|
197
|
+
node "$ROOT_DIR/scripts/install-owned-files.js" \
|
|
198
|
+
"$FA_OWNED_OVERLAY" "$DEST" ".flow-agents/codex-install-manifest.json"
|
|
199
|
+
|
|
200
|
+
atomic_copy() {
|
|
201
|
+
local source="$1"
|
|
202
|
+
local rel="$2"
|
|
203
|
+
assert_safe_dest_path "$rel"
|
|
204
|
+
local target="$DEST/$rel"
|
|
205
|
+
if [[ -e "$target" && ! -f "$target" ]]; then
|
|
206
|
+
echo "install-codex-home.sh: refusing to replace non-file: $target" >&2
|
|
207
|
+
exit 1
|
|
208
|
+
fi
|
|
209
|
+
local temp
|
|
210
|
+
temp="$(mktemp "$(dirname "$target")/.flow-agents-install.XXXXXX")"
|
|
211
|
+
cp "$source" "$temp"
|
|
212
|
+
mv -f "$temp" "$target"
|
|
213
|
+
}
|
|
214
|
+
|
|
158
215
|
# Root Codex config/profiles and AGENTS.md may be user-owned in ~/.codex.
|
|
159
216
|
# Seed Flow Agents defaults only when absent.
|
|
160
217
|
for seed_file in AGENTS.md; do
|
|
161
218
|
if [[ -f "$ROOT_DIR/dist/codex/$seed_file" && ! -e "$DEST/$seed_file" ]]; then
|
|
162
|
-
|
|
163
|
-
cp "$ROOT_DIR/dist/codex/$seed_file" "$DEST/$seed_file"
|
|
219
|
+
atomic_copy "$ROOT_DIR/dist/codex/$seed_file" "$seed_file"
|
|
164
220
|
fi
|
|
165
221
|
done
|
|
166
222
|
generated_profile_files=()
|
|
@@ -173,8 +229,7 @@ for seed_source in "$ROOT_DIR/dist/codex/.codex/config.toml" "$ROOT_DIR"/dist/co
|
|
|
173
229
|
profile_names+=("${seed_file%.config.toml}")
|
|
174
230
|
fi
|
|
175
231
|
if [[ ! -e "$DEST/$seed_file" ]] || grep -q 'Generated from packaging/manifest.json' "$DEST/$seed_file"; then
|
|
176
|
-
|
|
177
|
-
cp "$seed_source" "$DEST/$seed_file"
|
|
232
|
+
atomic_copy "$seed_source" "$seed_file"
|
|
178
233
|
fi
|
|
179
234
|
done
|
|
180
235
|
for profile_path in "$DEST"/*.config.toml; do
|
|
@@ -195,9 +250,8 @@ done
|
|
|
195
250
|
rmdir "$DEST/.codex" 2>/dev/null || true
|
|
196
251
|
|
|
197
252
|
for auth_file in auth.json version.json installation_id models_cache.json; do
|
|
198
|
-
if [[ "$
|
|
199
|
-
|
|
200
|
-
cp "$REAL_CODEX_HOME/$auth_file" "$DEST/$auth_file"
|
|
253
|
+
if [[ "$REAL_CODEX_HOME_REAL" != "$DEST_REAL" && -f "$REAL_CODEX_HOME_REAL/$auth_file" ]]; then
|
|
254
|
+
atomic_copy "$REAL_CODEX_HOME_REAL/$auth_file" "$auth_file"
|
|
201
255
|
fi
|
|
202
256
|
done
|
|
203
257
|
|
|
@@ -212,26 +266,22 @@ chmod 700 "$DEST" 2>/dev/null || true
|
|
|
212
266
|
FA_VERSION="$(node -p "require('$ROOT_DIR/package.json').version" 2>/dev/null || echo unknown)"
|
|
213
267
|
FA_MANAGED_HOOKS="$ROOT_DIR/dist/codex/.codex/hooks.json"
|
|
214
268
|
if command -v node >/dev/null 2>&1 && [[ -f "$FA_MANAGED_HOOKS" ]]; then
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
--managed-hooks "$FA_MANAGED_HOOKS" \
|
|
220
|
-
--version "$FA_VERSION" \
|
|
221
|
-
--install-record "$DEST/.flow-agents/install.json" \
|
|
222
|
-
--runtime "codex"
|
|
223
|
-
# Move the merged result into the destination.
|
|
224
|
-
cp "$FA_USER_HOOKS_STASH" "$DEST/hooks.json"
|
|
225
|
-
rm -f "$FA_USER_HOOKS_STASH"
|
|
226
|
-
else
|
|
227
|
-
# No prior user hooks: just write the version stamp (FA hooks are already correct from rsync).
|
|
228
|
-
node "$ROOT_DIR/scripts/install-merge.js" \
|
|
229
|
-
--config "$DEST/hooks.json" \
|
|
230
|
-
--managed-hooks "$FA_MANAGED_HOOKS" \
|
|
231
|
-
--version "$FA_VERSION" \
|
|
232
|
-
--install-record "$DEST/.flow-agents/install.json" \
|
|
233
|
-
--runtime "codex"
|
|
269
|
+
FA_HOOKS_WORK="${FA_USER_HOOKS_STASH:-}"
|
|
270
|
+
if [[ -z "$FA_HOOKS_WORK" ]]; then
|
|
271
|
+
FA_HOOKS_WORK="$(mktemp /tmp/fa-hooks-work.XXXXXX.json)"
|
|
272
|
+
printf '{"hooks":{}}\n' > "$FA_HOOKS_WORK"
|
|
234
273
|
fi
|
|
274
|
+
FA_INSTALL_RECORD_WORK="$(mktemp /tmp/fa-install-record.XXXXXX.json)"
|
|
275
|
+
node "$ROOT_DIR/scripts/install-merge.js" \
|
|
276
|
+
--config "$FA_HOOKS_WORK" \
|
|
277
|
+
--managed-hooks "$FA_MANAGED_HOOKS" \
|
|
278
|
+
--version "$FA_VERSION" \
|
|
279
|
+
--install-record "$FA_INSTALL_RECORD_WORK" \
|
|
280
|
+
--runtime "codex"
|
|
281
|
+
atomic_copy "$FA_HOOKS_WORK" "hooks.json"
|
|
282
|
+
atomic_copy "$FA_INSTALL_RECORD_WORK" ".flow-agents/install.json"
|
|
283
|
+
rm -f "$FA_HOOKS_WORK" "$FA_INSTALL_RECORD_WORK"
|
|
284
|
+
FA_USER_HOOKS_STASH=""
|
|
235
285
|
fi
|
|
236
286
|
|
|
237
287
|
if [[ ${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "${FLOW_AGENTS_TELEMETRY_SINK:-}" || -n "${FLOW_AGENTS_TELEMETRY_SINKS:-}" || -n "${FLOW_AGENTS_CONSOLE_URL:-}" || -n "${CONSOLE_TELEMETRY_URL:-}" || -n "${CONSOLE_URL:-}" || -n "${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-}" || -n "${CONSOLE_TELEMETRY_TOKEN_FILE:-}" ]]; then
|