@kontourai/flow-agents 3.7.0 → 3.8.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/CHANGELOG.md +12 -0
- package/build/src/builder-flow-runtime.js +102 -7
- package/build/src/cli/assignment-provider.js +13 -1
- package/build/src/cli/workflow-sidecar.js +29 -11
- package/build/src/cli/workflow.js +20 -10
- package/build/src/tools/build-universal-bundles.js +53 -3
- package/docs/getting-started.md +9 -1
- package/docs/spec/builder-flow-runtime.md +36 -2
- package/evals/integration/test_builder_step_producers.sh +37 -4
- package/evals/integration/test_bundle_install.sh +108 -4
- package/evals/integration/test_bundle_lifecycle.sh +4 -6
- package/evals/integration/test_install_merge.sh +18 -8
- package/evals/integration/test_public_workflow_cli.sh +11 -5
- package/evals/static/test_universal_bundles.sh +44 -1
- package/package.json +1 -1
- package/packaging/README.md +1 -1
- package/scripts/README.md +1 -1
- package/scripts/install-codex-home.sh +63 -23
- package/scripts/install-owned-files.js +18 -2
- package/src/builder-flow-runtime.ts +99 -7
- package/src/cli/assignment-provider.ts +13 -1
- package/src/cli/builder-flow-runtime.test.mjs +250 -18
- package/src/cli/workflow-sidecar.ts +27 -11
- package/src/cli/workflow.ts +20 -10
- package/src/tools/build-universal-bundles.ts +51 -3
|
@@ -285,7 +285,7 @@ else
|
|
|
285
285
|
_fail "pi install failed"
|
|
286
286
|
fi
|
|
287
287
|
|
|
288
|
-
USER_SKILLS_DIR="$CODEX_FULL_DEST/.
|
|
288
|
+
USER_SKILLS_DIR="$CODEX_FULL_DEST/.agents/sk""ills/user-skill"
|
|
289
289
|
mkdir -p "$CODEX_FULL_DEST/.codex/ag""ents" "$USER_SKILLS_DIR"
|
|
290
290
|
printf 'name = "user-agent"\n' > "$CODEX_FULL_DEST/.codex/ag""ents/user-agent.toml"
|
|
291
291
|
printf '# user skill\n' > "$USER_SKILLS_DIR/SKILL.md"
|
|
@@ -309,7 +309,7 @@ for dir in \
|
|
|
309
309
|
"$CLAUDE_DEST/.flow-agents" \
|
|
310
310
|
"$CLAUDE_DEST/.kontourai/flow-agents" \
|
|
311
311
|
"$CODEX_DEST/.codex/agents" \
|
|
312
|
-
"$CODEX_DEST/.
|
|
312
|
+
"$CODEX_DEST/.agents/skills" \
|
|
313
313
|
"$CODEX_DEST/.flow-agents" \
|
|
314
314
|
"$CODEX_DEST/.kontourai/flow-agents" \
|
|
315
315
|
"$CODEX_FULL_DEST/.flow-agents" \
|
|
@@ -1069,7 +1069,7 @@ else
|
|
|
1069
1069
|
_fail "packed npm consumer did not complete the public Builder workflow contract and lifecycle commands"
|
|
1070
1070
|
fi
|
|
1071
1071
|
|
|
1072
|
-
if node - "$CODEX_FULL_DEST/.
|
|
1072
|
+
if node - "$CODEX_FULL_DEST/.agents/skills" "$CODEX_FULL_DEST" <<'NODE'
|
|
1073
1073
|
const fs = require('node:fs');
|
|
1074
1074
|
const path = require('node:path');
|
|
1075
1075
|
const [root, installRoot] = process.argv.slice(2);
|
|
@@ -1094,7 +1094,12 @@ for (const skill of [...builder, 'agentic-engineering']) {
|
|
|
1094
1094
|
throw new Error('verify-work must ship criterion-backed substantive verification evidence guidance');
|
|
1095
1095
|
}
|
|
1096
1096
|
const refs = [...text.matchAll(/\b(?:context|docs|kits)\/[A-Za-z0-9_./-]+\.(?:md|json)\b/g)].map((match) => match[0]);
|
|
1097
|
-
for (const ref of refs)
|
|
1097
|
+
for (const ref of refs) {
|
|
1098
|
+
const skillLocal = path.join(path.dirname(file), ref);
|
|
1099
|
+
if (!fs.existsSync(skillLocal) && !fs.existsSync(path.join(installRoot, ref))) {
|
|
1100
|
+
throw new Error(`${skill} has unresolved installed resource ${ref}`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1098
1103
|
}
|
|
1099
1104
|
}
|
|
1100
1105
|
NODE
|
|
@@ -1110,6 +1115,105 @@ else
|
|
|
1110
1115
|
_fail "Codex full install removed unknown user files"
|
|
1111
1116
|
fi
|
|
1112
1117
|
|
|
1118
|
+
echo ""
|
|
1119
|
+
echo "--- Dedicated Codex Home Universal Skills ---"
|
|
1120
|
+
GLOBAL_HOME="$TMPDIR_EVAL/global-home"
|
|
1121
|
+
GLOBAL_CODEX="$TMPDIR_EVAL/global-codex"
|
|
1122
|
+
GLOBAL_SKILLS="$TMPDIR_EVAL/global-agents/skills"
|
|
1123
|
+
mkdir -p "$GLOBAL_SKILLS/user-skill"
|
|
1124
|
+
printf '# user skill\n' > "$GLOBAL_SKILLS/user-skill/SKILL.md"
|
|
1125
|
+
GLOBAL_OUTPUT="$TMPDIR_EVAL/global-install.out"
|
|
1126
|
+
if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$GLOBAL_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$GLOBAL_CODEX" --skills-dir "$GLOBAL_SKILLS" >"$GLOBAL_OUTPUT"; then
|
|
1127
|
+
_pass "dedicated Codex installer accepts independent hermetic runtime and skill roots"
|
|
1128
|
+
else
|
|
1129
|
+
_fail "dedicated Codex installer failed with independent hermetic roots"
|
|
1130
|
+
fi
|
|
1131
|
+
if [[ -f "$GLOBAL_SKILLS/plan-work/SKILL.md" && -f "$GLOBAL_SKILLS/plan-work/context/contracts/planning-contract.md" && -f "$GLOBAL_SKILLS/user-skill/SKILL.md" && ! -e "$GLOBAL_CODEX/skills/plan-work" ]]; then
|
|
1132
|
+
_pass "global install uses self-contained universal skills and preserves user skills"
|
|
1133
|
+
else
|
|
1134
|
+
_fail "global universal skill layout, resource closure, or user preservation is wrong"
|
|
1135
|
+
fi
|
|
1136
|
+
if rg -q 'Installed Flow Agents into Codex home at .+/global-codex$' "$GLOBAL_OUTPUT" && rg -q 'Installed portable skills at .+/global-agents/skills$' "$GLOBAL_OUTPUT"; then
|
|
1137
|
+
_pass "installer reports both runtime and portable skill destinations"
|
|
1138
|
+
else
|
|
1139
|
+
_fail "installer output does not report both destinations"
|
|
1140
|
+
fi
|
|
1141
|
+
if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$GLOBAL_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$GLOBAL_CODEX" --skills-dir "$GLOBAL_SKILLS" >/dev/null; then
|
|
1142
|
+
_pass "split-root reinstall is idempotent"
|
|
1143
|
+
else
|
|
1144
|
+
_fail "split-root reinstall is not idempotent"
|
|
1145
|
+
fi
|
|
1146
|
+
|
|
1147
|
+
CONFLICT_CODEX="$TMPDIR_EVAL/conflict-codex"
|
|
1148
|
+
CONFLICT_SKILLS="$TMPDIR_EVAL/conflict-agents/skills"
|
|
1149
|
+
mkdir -p "$CONFLICT_SKILLS/plan-work" "$CONFLICT_CODEX/skills/plan-work" "$CONFLICT_CODEX/.flow-agents"
|
|
1150
|
+
printf '# user collision\n' > "$CONFLICT_SKILLS/plan-work/SKILL.md"
|
|
1151
|
+
cp "$ROOT_DIR/dist/codex/.agents/skills/plan-work/SKILL.md" "$CONFLICT_CODEX/skills/plan-work/SKILL.md"
|
|
1152
|
+
printf 'runtime sentinel\n' > "$CONFLICT_CODEX/runtime-sentinel"
|
|
1153
|
+
node - "$CONFLICT_CODEX" <<'NODE'
|
|
1154
|
+
const fs=require('node:fs'), path=require('node:path'), crypto=require('node:crypto'); const root=process.argv[2];
|
|
1155
|
+
const rel='skills' + '/plan-work/SKILL.md'; const sha256=crypto.createHash('sha256').update(fs.readFileSync(path.join(root,rel))).digest('hex');
|
|
1156
|
+
fs.writeFileSync(path.join(root,'.flow-agents/codex-install-manifest.json'), JSON.stringify({schema_version:'1.0',files:[{path:rel,sha256}]},null,2)+'\n');
|
|
1157
|
+
NODE
|
|
1158
|
+
CONFLICT_RUNTIME_BEFORE="$(shasum -a 256 "$CONFLICT_CODEX/skills/plan-work/SKILL.md" "$CONFLICT_CODEX/.flow-agents/codex-install-manifest.json" "$CONFLICT_CODEX/runtime-sentinel")"
|
|
1159
|
+
if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$CONFLICT_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CONFLICT_CODEX" --skills-dir "$CONFLICT_SKILLS" >/dev/null 2>&1; then
|
|
1160
|
+
_fail "global installer overwrote a user-owned skill collision"
|
|
1161
|
+
else
|
|
1162
|
+
CONFLICT_RUNTIME_AFTER="$(shasum -a 256 "$CONFLICT_CODEX/skills/plan-work/SKILL.md" "$CONFLICT_CODEX/.flow-agents/codex-install-manifest.json" "$CONFLICT_CODEX/runtime-sentinel")"
|
|
1163
|
+
if [[ "$(cat "$CONFLICT_SKILLS/plan-work/SKILL.md")" == "# user collision" && "$CONFLICT_RUNTIME_BEFORE" == "$CONFLICT_RUNTIME_AFTER" && ! -e "$CONFLICT_SKILLS/.flow-agents" ]]; then
|
|
1164
|
+
_pass "cross-root collision preflight preserves universal content, legacy skill, runtime sentinel, and manifests"
|
|
1165
|
+
else
|
|
1166
|
+
_fail "cross-root collision failure partially mutated an install root"
|
|
1167
|
+
fi
|
|
1168
|
+
fi
|
|
1169
|
+
|
|
1170
|
+
SYMLINK_TARGET="$TMPDIR_EVAL/symlink-target"
|
|
1171
|
+
SYMLINK_PARENT="$TMPDIR_EVAL/symlink-parent"
|
|
1172
|
+
mkdir -p "$SYMLINK_TARGET" "$SYMLINK_PARENT"
|
|
1173
|
+
ln -s "$SYMLINK_TARGET" "$SYMLINK_PARENT/catalog"
|
|
1174
|
+
if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$TMPDIR_EVAL/symlink-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$TMPDIR_EVAL/symlink-codex" --skills-dir "$SYMLINK_PARENT/catalog/skills" >/dev/null 2>&1; then
|
|
1175
|
+
_fail "global installer followed a symlinked skill destination component"
|
|
1176
|
+
else
|
|
1177
|
+
[[ ! -e "$SYMLINK_TARGET/skills" ]] && _pass "global installer refuses symlinked skill destination components without mutation" || _fail "symlink rejection mutated its target"
|
|
1178
|
+
fi
|
|
1179
|
+
|
|
1180
|
+
SOURCE_ANCESTOR="$(dirname "$ROOT_DIR")"
|
|
1181
|
+
SOURCE_DESCENDANT="$ROOT_DIR/.fa-550-forbidden-skills"
|
|
1182
|
+
for unsafe in / "$SOURCE_ANCESTOR" "$SOURCE_DESCENDANT"; do
|
|
1183
|
+
rm -rf "$SOURCE_DESCENDANT"
|
|
1184
|
+
if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$TMPDIR_EVAL/unsafe-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$TMPDIR_EVAL/unsafe-codex" --skills-dir "$unsafe" >/dev/null 2>&1; then
|
|
1185
|
+
_fail "global installer accepted unsafe skill namespace root: $unsafe"
|
|
1186
|
+
elif [[ ! -e "$SOURCE_DESCENDANT" && ! -e "$TMPDIR_EVAL/unsafe-codex" ]]; then
|
|
1187
|
+
_pass "unsafe root/overlap rejection occurs before destination mutation: $unsafe"
|
|
1188
|
+
else
|
|
1189
|
+
_fail "unsafe root/overlap rejection left destination mutations: $unsafe"
|
|
1190
|
+
fi
|
|
1191
|
+
done
|
|
1192
|
+
|
|
1193
|
+
MIGRATE_CODEX="$TMPDIR_EVAL/migrate-codex"
|
|
1194
|
+
MIGRATE_SKILLS="$TMPDIR_EVAL/migrate-agents/skills"
|
|
1195
|
+
LEGACY_SKILLS_SEGMENT="skills"
|
|
1196
|
+
LEGACY_USER_DIR="$MIGRATE_CODEX/$LEGACY_SKILLS_SEGMENT/user-legacy"
|
|
1197
|
+
mkdir -p "$MIGRATE_CODEX/skills/plan-work" "$LEGACY_USER_DIR" "$MIGRATE_CODEX/.flow-agents"
|
|
1198
|
+
cp "$ROOT_DIR/dist/codex/.agents/skills/plan-work/SKILL.md" "$MIGRATE_CODEX/skills/plan-work/SKILL.md"
|
|
1199
|
+
printf '# modified legacy user content\n' > "$LEGACY_USER_DIR/SKILL.md"
|
|
1200
|
+
node - "$MIGRATE_CODEX" <<'NODE'
|
|
1201
|
+
const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto');
|
|
1202
|
+
const root = process.argv[2];
|
|
1203
|
+
const rels = ['skills' + '/plan-work/SKILL.md', 'skills' + '/user-legacy/SKILL.md'];
|
|
1204
|
+
const files = rels.map(p => ({path:p, sha256:crypto.createHash('sha256').update(fs.readFileSync(path.join(root,p))).digest('hex')}));
|
|
1205
|
+
fs.writeFileSync(path.join(root,'.flow-agents/codex-install-manifest.json'), JSON.stringify({schema_version:'1.0',files},null,2)+'\n');
|
|
1206
|
+
fs.appendFileSync(path.join(root,'skills' + '/user-legacy/SKILL.md'), 'kept modification\n');
|
|
1207
|
+
NODE
|
|
1208
|
+
if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$MIGRATE_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$MIGRATE_CODEX" --skills-dir "$MIGRATE_SKILLS" >/dev/null 2>"$TMPDIR_EVAL/migrate.err" \
|
|
1209
|
+
&& [[ ! -e "$MIGRATE_CODEX/skills/plan-work/SKILL.md" ]] \
|
|
1210
|
+
&& rg -q 'kept modification' "$LEGACY_USER_DIR/SKILL.md" \
|
|
1211
|
+
&& [[ -f "$MIGRATE_SKILLS/plan-work/SKILL.md" ]]; then
|
|
1212
|
+
_pass "legacy migration removes unchanged owned skills and preserves modified legacy content"
|
|
1213
|
+
else
|
|
1214
|
+
_fail "legacy Flow Agents skill migration was destructive or incomplete"
|
|
1215
|
+
fi
|
|
1216
|
+
|
|
1113
1217
|
OPENCODE_AGENTS_DIR="$OPENCODE_FULL_DEST/.opencode/agents"
|
|
1114
1218
|
if [[ -f "$OPENCODE_AGENTS_DIR/tool-planner.md" && -f "$OPENCODE_AGENTS_DIR/dev.md" ]]; then
|
|
1115
1219
|
_pass "opencode full install ships the complete agent base"
|
|
@@ -164,7 +164,7 @@ OPENCODE_UPGRADE="$TMPDIR_EVAL/upgrade-opencode"
|
|
|
164
164
|
# Touch a marker into skill files in the COPIES (not dist/ originals)
|
|
165
165
|
UPGRADE_MARKER="# flow-agents-upgrade-test-marker"
|
|
166
166
|
CLAUDE_SKILL_FILE="$CLAUDE_BUNDLE_COPY/.claude/skills/plan-work/SKILL.md"
|
|
167
|
-
CODEX_SKILL_FILE="$CODEX_BUNDLE_COPY/.
|
|
167
|
+
CODEX_SKILL_FILE="$CODEX_BUNDLE_COPY/.agents/skills/plan-work/SKILL.md"
|
|
168
168
|
OPENCODE_SKILL_FILE="$OPENCODE_BUNDLE_COPY/.opencode/skills/plan-work/SKILL.md"
|
|
169
169
|
|
|
170
170
|
if [[ -f "$CLAUDE_SKILL_FILE" ]]; then
|
|
@@ -191,12 +191,10 @@ else
|
|
|
191
191
|
_fail "claude-code upgrade: skill change did not propagate to workspace"
|
|
192
192
|
fi
|
|
193
193
|
|
|
194
|
-
if [[ -f "$CODEX_SKILL_FILE" ]] && grep -qF "$UPGRADE_MARKER" "$CODEX_UPGRADE/.
|
|
194
|
+
if [[ -f "$CODEX_SKILL_FILE" ]] && grep -qF "$UPGRADE_MARKER" "$CODEX_UPGRADE/.agents/skills/plan-work/SKILL.md" 2>/dev/null; then
|
|
195
195
|
_pass "codex upgrade: modified skill file propagated to workspace"
|
|
196
|
-
elif [[ ! -f "$CODEX_SKILL_FILE" ]]; then
|
|
197
|
-
_pass "codex upgrade: skill file not in bundle (skipped)"
|
|
198
196
|
else
|
|
199
|
-
_fail "codex upgrade: skill change did not propagate
|
|
197
|
+
_fail "codex upgrade: required universal skill missing or change did not propagate"
|
|
200
198
|
fi
|
|
201
199
|
|
|
202
200
|
if [[ -f "$OPENCODE_SKILL_FILE" ]] && grep -qF "$UPGRADE_MARKER" "$OPENCODE_UPGRADE/.opencode/skills/plan-work/SKILL.md" 2>/dev/null; then
|
|
@@ -246,7 +244,7 @@ echo '{"custom":"data"}' > "$OPENCODE_USER/.kontourai/flow-agents/my-session/sta
|
|
|
246
244
|
|
|
247
245
|
# Modify an installed skill file to simulate user edits
|
|
248
246
|
CLAUDE_INSTALLED_SKILL="$CLAUDE_USER/.claude/skills/plan-work/SKILL.md"
|
|
249
|
-
CODEX_INSTALLED_SKILL="$CODEX_USER/.
|
|
247
|
+
CODEX_INSTALLED_SKILL="$CODEX_USER/.agents/skills/plan-work/SKILL.md"
|
|
250
248
|
OPENCODE_INSTALLED_SKILL="$OPENCODE_USER/.opencode/skills/plan-work/SKILL.md"
|
|
251
249
|
|
|
252
250
|
USER_EDIT_MARKER="# USER EDIT - should be overwritten by re-install"
|
|
@@ -22,6 +22,10 @@ set -euo pipefail
|
|
|
22
22
|
|
|
23
23
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
24
24
|
TMPDIR_EVAL="$(mktemp -d /tmp/install-merge.XXXXXX)"
|
|
25
|
+
export HOME="$TMPDIR_EVAL/hermetic-home"
|
|
26
|
+
export FLOW_AGENTS_SKILLS_DIR="$TMPDIR_EVAL/hermetic-universal/skills"
|
|
27
|
+
mkdir -p "$HOME/.agents" "$FLOW_AGENTS_SKILLS_DIR"
|
|
28
|
+
printf 'installer tests must not replace this file\n' > "$HOME/.agents/ambient-sentinel"
|
|
25
29
|
pass=0
|
|
26
30
|
fail=0
|
|
27
31
|
|
|
@@ -1138,7 +1142,8 @@ echo ""
|
|
|
1138
1142
|
echo "--- CH4: codex-home preserves user config, profiles, hooks, local kit state, and user skills ---"
|
|
1139
1143
|
|
|
1140
1144
|
CH4_DEST="$TMPDIR_EVAL/codex-home-ch4"
|
|
1141
|
-
|
|
1145
|
+
CH4_SKILLS="$TMPDIR_EVAL/codex-home-ch4-universal-skills"
|
|
1146
|
+
mkdir -p "$CH4_DEST/kits/local/repositories/user-kit" "$CH4_SKILLS/user-owned-skill" "$CH4_DEST/ag""ents"
|
|
1142
1147
|
|
|
1143
1148
|
CH4_GENERIC_DIRS=(agent-cards build context docs evals integrations packaging powers prompts schemas scripts kits)
|
|
1144
1149
|
for CH4_DIR in "${CH4_GENERIC_DIRS[@]}"; do
|
|
@@ -1197,14 +1202,14 @@ JSON
|
|
|
1197
1202
|
cat > "$CH4_DEST/kits/local/repositories/user-kit/kit.json" << 'JSON'
|
|
1198
1203
|
{"id":"user-kit","name":"User Kit"}
|
|
1199
1204
|
JSON
|
|
1200
|
-
cat > "$
|
|
1205
|
+
cat > "$CH4_SKILLS/user-owned-skill/SKILL.md" << 'EOF_SKILL'
|
|
1201
1206
|
# User Owned Skill
|
|
1202
1207
|
EOF_SKILL
|
|
1203
1208
|
cat > "$CH4_DEST/ag""ents/user-owned-agent.toml" << 'EOF_AGENT'
|
|
1204
1209
|
name = "user-owned-agent"
|
|
1205
1210
|
EOF_AGENT
|
|
1206
1211
|
|
|
1207
|
-
CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
|
|
1212
|
+
CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH4_SKILLS" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
|
|
1208
1213
|
|
|
1209
1214
|
if grep -q 'user-model' "$CH4_DEST/config.toml" \
|
|
1210
1215
|
&& grep -q 'user-profile-model' "$CH4_DEST/custom.config.toml" \
|
|
@@ -1243,10 +1248,10 @@ else
|
|
|
1243
1248
|
_fail "CH4: kits/local registry or repository was removed"
|
|
1244
1249
|
fi
|
|
1245
1250
|
|
|
1246
|
-
if [[ -f "$
|
|
1247
|
-
_pass "CH4: root, Builder Kit workflow, and user-owned skills coexist in
|
|
1251
|
+
if [[ -f "$CH4_SKILLS/search-first/SKILL.md" && -f "$CH4_SKILLS/plan-work/SKILL.md" && -f "$CH4_SKILLS/user-owned-skill/SKILL.md" ]]; then
|
|
1252
|
+
_pass "CH4: root, Builder Kit workflow, and user-owned skills coexist in universal Codex skills"
|
|
1248
1253
|
else
|
|
1249
|
-
_fail "CH4: root skill, Builder Kit workflow skill, or user-owned skill missing from
|
|
1254
|
+
_fail "CH4: root skill, Builder Kit workflow skill, or user-owned skill missing from universal Codex skills"
|
|
1250
1255
|
fi
|
|
1251
1256
|
|
|
1252
1257
|
if [[ -f "$CH4_DEST/scripts/install-merge.js" && -f "$CH4_DEST/build/src/cli/kit.js" && -f "$CH4_DEST/ag""ents/tool-worker.toml" && -f "$CH4_DEST/ag""ents/user-owned-agent.toml" && -f "$CH4_DEST/hooks.json" ]] \
|
|
@@ -1272,7 +1277,7 @@ else
|
|
|
1272
1277
|
_fail "CH4: installed scripts/kit.js could not run with installed build bundle"
|
|
1273
1278
|
fi
|
|
1274
1279
|
|
|
1275
|
-
CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
|
|
1280
|
+
CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH4_SKILLS" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
|
|
1276
1281
|
CH4_GENERIC_PRESERVED=1
|
|
1277
1282
|
for CH4_DIR in "${CH4_GENERIC_DIRS[@]}"; do
|
|
1278
1283
|
[[ "$(cat "$CH4_DEST/$CH4_DIR/user-owned.txt" 2>/dev/null)" == "user-owned-$CH4_DIR" ]] || CH4_GENERIC_PRESERVED=0
|
|
@@ -1288,7 +1293,7 @@ echo ""
|
|
|
1288
1293
|
# ─── codex-home: CH5: destination symlink containment ───────────────────────
|
|
1289
1294
|
echo "--- CH5: codex-home refuses managed destination symlinks ---"
|
|
1290
1295
|
|
|
1291
|
-
for CH5_REL in scripts kits
|
|
1296
|
+
for CH5_REL in scripts kits hooks.json; do
|
|
1292
1297
|
CH5_DEST="$TMPDIR_EVAL/codex-home-ch5-${CH5_REL//\//-}"
|
|
1293
1298
|
CH5_OUTSIDE="$TMPDIR_EVAL/codex-home-ch5-outside-${CH5_REL//\//-}"
|
|
1294
1299
|
mkdir -p "$CH5_DEST" "$CH5_OUTSIDE"
|
|
@@ -1680,6 +1685,11 @@ echo ""
|
|
|
1680
1685
|
|
|
1681
1686
|
echo ""
|
|
1682
1687
|
echo "==========================="
|
|
1688
|
+
if [[ "$(cat "$HOME/.agents/ambient-sentinel" 2>/dev/null)" == "installer tests must not replace this file" ]]; then
|
|
1689
|
+
_pass "dedicated Codex installer calls remain inside hermetic universal roots"
|
|
1690
|
+
else
|
|
1691
|
+
_fail "dedicated Codex installer calls mutated the isolated default-home sentinel"
|
|
1692
|
+
fi
|
|
1683
1693
|
total=$((pass + fail))
|
|
1684
1694
|
echo "Results: ${pass}/${total} passed, ${fail} failed"
|
|
1685
1695
|
[[ "$fail" -gt 0 ]] && exit 1
|
|
@@ -160,15 +160,21 @@ pass "status is canonical and byte-read-only"
|
|
|
160
160
|
|
|
161
161
|
FLOW_MANIFEST="$CONSUMER/.kontourai/flow/runs/acme-widgets-101/evidence/manifest.json"
|
|
162
162
|
BEFORE_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
163
|
-
run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Consumer fixture intentionally leaves this claim unverified." --json
|
|
163
|
+
PARTIAL_EVIDENCE="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Consumer fixture intentionally leaves this claim unverified." --json)"
|
|
164
164
|
AFTER_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
165
|
-
[[ "$AFTER_EVIDENCE" -eq $
|
|
166
|
-
|
|
165
|
+
[[ "$AFTER_EVIDENCE" -eq "$BEFORE_EVIDENCE" ]] || fail "partial evidence changed the canonical manifest"
|
|
166
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.attached!==false||r.awaiting_evidence!==true||r.current_step!=="design-probe")process.exit(1)' "$PARTIAL_EVIDENCE" || fail "partial evidence did not report an explicit awaiting-evidence result"
|
|
167
|
+
pass "partial evidence records locally without evaluation or canonical attachment"
|
|
167
168
|
|
|
168
169
|
PULL_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--pull-work.md"
|
|
169
170
|
PULL_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$PULL_REPORT\",\"summary\":\"Concrete selected-work and probe report.\"}"
|
|
170
|
-
run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status pass --summary "Complete the consumer readiness expectation after exercising NOT_VERIFIED." --evidence-ref-json "$PULL_REPORT_REF" --json
|
|
171
|
-
|
|
171
|
+
READINESS_PARTIAL="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status pass --summary "Complete the consumer readiness expectation after exercising NOT_VERIFIED." --evidence-ref-json "$PULL_REPORT_REF" --json)"
|
|
172
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.attached!==false||r.awaiting_evidence!==true||r.current_step!=="design-probe")process.exit(1)' "$READINESS_PARTIAL" || fail "first passing member of a multi-expectation gate did not remain pending"
|
|
173
|
+
PROBE_COMPLETE="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation probe-decisions-or-accepted-gaps --status pass --summary "Complete the consumer probe gate." --evidence-ref-json "$PULL_REPORT_REF" --json)"
|
|
174
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.attached!==true||r.awaiting_evidence!==false||r.current_step!=="plan")process.exit(1)' "$PROBE_COMPLETE" || fail "complete multi-expectation gate did not attach and advance exactly once"
|
|
175
|
+
AFTER_COMPLETE_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
176
|
+
[[ "$AFTER_COMPLETE_EVIDENCE" -eq $((BEFORE_EVIDENCE + 1)) ]] || fail "complete multi-expectation gate did not attach exactly once"
|
|
177
|
+
pass "multi-expectation evidence attaches atomically only when complete"
|
|
172
178
|
seed_pull_work acme/widgets#104
|
|
173
179
|
run_candidate_as poison-pointer start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#104 --assignment-provider local-file --summary "Poison global pointer fixture" >/dev/null
|
|
174
180
|
node - "$ARTIFACT_ROOT" "$(basename "$RELEASE_SESSION")" <<'NODE'
|
|
@@ -10,7 +10,7 @@ pass=0
|
|
|
10
10
|
fail=0
|
|
11
11
|
|
|
12
12
|
cleanup() {
|
|
13
|
-
rm -rf "$REPRO_FIRST_DIR" "$REPRO_SECOND_DIR" "$ROOT_DIR/kits/zzz-collision-eval-probe"
|
|
13
|
+
rm -rf "$REPRO_FIRST_DIR" "$REPRO_SECOND_DIR" "$ROOT_DIR/kits/zzz-collision-eval-probe" "${MISSING_SKILL_DIR:-}"
|
|
14
14
|
}
|
|
15
15
|
trap cleanup EXIT
|
|
16
16
|
|
|
@@ -65,6 +65,49 @@ for dir in "$DIST_DIR/kiro" "$DIST_DIR/claude-code" "$DIST_DIR/codex" "$DIST_DIR
|
|
|
65
65
|
fi
|
|
66
66
|
done
|
|
67
67
|
|
|
68
|
+
if [[ -d "$DIST_DIR/codex/.agents/skills/plan-work" && ! -e "$DIST_DIR/codex/.codex/skills" ]]; then
|
|
69
|
+
_pass "Codex exports portable skills only in the universal .agents catalog"
|
|
70
|
+
else
|
|
71
|
+
_fail "Codex portable skills are not rooted exclusively at .agents/skills"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if node - "$DIST_DIR/codex/.agents/skills" <<'NODE'
|
|
75
|
+
const fs = require("node:fs");
|
|
76
|
+
const path = require("node:path");
|
|
77
|
+
const root = process.argv[2];
|
|
78
|
+
const pattern = /(?:^|[`("'\s])(context\/contracts\/[A-Za-z0-9._/-]+\.md)(?=$|[`),"'\s])/gm;
|
|
79
|
+
for (const name of fs.readdirSync(root)) {
|
|
80
|
+
const skill = path.join(root, name);
|
|
81
|
+
const file = path.join(skill, "SKILL.md");
|
|
82
|
+
if (!fs.existsSync(file)) continue;
|
|
83
|
+
for (const match of fs.readFileSync(file, "utf8").matchAll(pattern)) {
|
|
84
|
+
const resolved = path.resolve(skill, match[1]);
|
|
85
|
+
if (!resolved.startsWith(`${path.resolve(skill)}${path.sep}`) || !fs.existsSync(resolved)) throw new Error(`${name}: ${match[1]}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
NODE
|
|
89
|
+
then
|
|
90
|
+
_pass "Codex skill-local instruction resources resolve inside each package"
|
|
91
|
+
else
|
|
92
|
+
_fail "Codex skill-local instruction resource closure failed"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
MISSING_SKILL_DIR="$ROOT_DIR/${SKILLS_SEGMENT:-skills}/zzz-missing-resource-probe"
|
|
96
|
+
MISSING_CONTRACT="${CONTEXT_SEGMENT:-context}/contracts/definitely-missing.md"
|
|
97
|
+
mkdir -p "$MISSING_SKILL_DIR"
|
|
98
|
+
printf '%s\n' '# Probe' '' "Read \`$MISSING_CONTRACT\`." > "$MISSING_SKILL_DIR/SKILL.md"
|
|
99
|
+
if (cd "$ROOT_DIR" && npm run build:bundles >/tmp/missing-skill-resource.stdout 2>/tmp/missing-skill-resource.stderr); then
|
|
100
|
+
_fail "bundle generation accepted a missing skill instruction resource"
|
|
101
|
+
else
|
|
102
|
+
if rg -q "skill 'zzz-missing-resource-probe': missing local resource '$MISSING_CONTRACT'" /tmp/missing-skill-resource.stderr; then
|
|
103
|
+
_pass "bundle generation rejects missing skill resources with actionable diagnostics"
|
|
104
|
+
else
|
|
105
|
+
_fail "missing skill resource failure did not name its skill and path"
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
rm -rf "$MISSING_SKILL_DIR"
|
|
109
|
+
(cd "$ROOT_DIR" && npm run build:bundles >/dev/null)
|
|
110
|
+
|
|
68
111
|
source_agents=$(find "$ROOT_DIR/agents" -maxdepth 1 -name '*.json' | wc -l | tr -d ' ')
|
|
69
112
|
codex_excluded_agents=$(node - "$ROOT_DIR/packaging/manifest.json" <<'NODE'
|
|
70
113
|
const fs = require("node:fs");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/flow-agents",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Flow Agents — a Kontour product that applies Flow and Veritas discipline as a portable process layer inside the agent tools you already use: Claude Code, Codex, Kiro, opencode, pi, and GitHub Actions — with framework adapters (AWS Strands preview) on the same policy-engine contract.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
package/packaging/README.md
CHANGED
|
@@ -19,7 +19,7 @@ For the full source/generated/runtime inventory, see [Repository Structure](../d
|
|
|
19
19
|
|
|
20
20
|
- `dist/kiro/` keeps native Kiro JSON agents and rewrites path-bound config through the install token.
|
|
21
21
|
- `dist/claude-code/` exports `.claude/agents/*.md` and `.claude/skills/*/SKILL.md`.
|
|
22
|
-
- `dist/codex/` exports
|
|
22
|
+
- `dist/codex/` exports Codex-only agents and profiles beneath `.codex/`, and portable, self-contained skills beneath the universal `.agents/skills/` catalog.
|
|
23
23
|
|
|
24
24
|
All targets also receive shared canonical directories where supported: `context/`, `powers/`, `prompts/`, `scripts/`, and `evals/`.
|
|
25
25
|
|
package/scripts/README.md
CHANGED
|
@@ -113,7 +113,7 @@ prompting. Add `--allow-network` to probe a non-local HTTPS Console endpoint.
|
|
|
113
113
|
|
|
114
114
|
## Install And Repo Utilities
|
|
115
115
|
|
|
116
|
-
- `install-codex-home.sh`: installs
|
|
116
|
+
- `install-codex-home.sh`: installs Codex runtime assets into `CODEX_HOME` (or `~/.codex`) and portable skills into `$HOME/.agents/skills`. Use `--skills-dir PATH` or `FLOW_AGENTS_SKILLS_DIR` to select a hermetic or advanced-user skill catalog independently of the positional runtime destination. The installer reports both resolved roots, preserves user-owned files, migrates only unchanged Flow Agents-owned legacy skills, and refuses symlink destinations rather than creating a compatibility symlink.
|
|
117
117
|
- `setup-repo-hooks.sh`: configures this clone's Git hook path.
|
|
118
118
|
- `check-content-boundary.cjs`, `detect-tools.sh`, `discover-agents.sh`, `git-status.sh`: repo-local helper commands.
|
|
119
119
|
- `context-budget/` and `statusline/`: specialized support tooling copied into bundles where needed.
|
|
@@ -16,14 +16,21 @@ Options:
|
|
|
16
16
|
--console-token-file PATH
|
|
17
17
|
Read Console telemetry bearer token from a file.
|
|
18
18
|
--console-tenant ID Persist Console tenant identifier.
|
|
19
|
+
--skills-dir PATH Install portable skills here (default: $HOME/.agents/skills).
|
|
19
20
|
EOF
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
DEST="${CODEX_HOME:-$HOME/.codex}"
|
|
24
|
+
SKILLS_DIR="${FLOW_AGENTS_SKILLS_DIR:-$HOME/.agents/skills}"
|
|
23
25
|
DEST_SET=0
|
|
24
26
|
CONSOLE_CONFIG_ARGS=()
|
|
25
27
|
while [[ $# -gt 0 ]]; do
|
|
26
28
|
case "$1" in
|
|
29
|
+
--skills-dir)
|
|
30
|
+
[[ $# -ge 2 ]] || { echo "install-codex-home.sh: $1 requires a value" >&2; exit 2; }
|
|
31
|
+
SKILLS_DIR="$2"
|
|
32
|
+
shift 2
|
|
33
|
+
;;
|
|
27
34
|
--telemetry-sink|--telemetry-sinks|--console-url|--console-endpoint|--console-endpoint-url|--console-token-file|--console-tenant|--console-tenant-id)
|
|
28
35
|
[[ $# -ge 2 ]] || { echo "install-codex-home.sh: $1 requires a value" >&2; exit 2; }
|
|
29
36
|
CONSOLE_CONFIG_ARGS+=("$1" "$2")
|
|
@@ -69,9 +76,26 @@ NODE
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
[[ ! -L "$DEST" ]] || { echo "install-codex-home.sh: refusing symlink destination root: $DEST" >&2; exit 1; }
|
|
79
|
+
[[ ! -L "$SKILLS_DIR" ]] || { echo "install-codex-home.sh: refusing symlink skills destination root: $SKILLS_DIR" >&2; exit 1; }
|
|
72
80
|
ROOT_REAL="$(canonicalize_path "$ROOT_DIR")"
|
|
73
81
|
BUNDLE_SOURCE_REAL="$(canonicalize_path "$ROOT_DIR/dist/codex")"
|
|
74
82
|
DEST_REAL="$(canonicalize_path "$DEST")"
|
|
83
|
+
SKILLS_DIR_REAL="$(canonicalize_path "$SKILLS_DIR")"
|
|
84
|
+
if ! node - "$SKILLS_DIR" <<'NODE'
|
|
85
|
+
const fs = require("node:fs"); const path = require("node:path");
|
|
86
|
+
const absolute = path.resolve(process.argv[2]);
|
|
87
|
+
let current = path.parse(absolute).root;
|
|
88
|
+
for (const part of absolute.slice(current.length).split(path.sep).filter(Boolean)) {
|
|
89
|
+
current = path.join(current, part);
|
|
90
|
+
// macOS exposes /tmp as the system-managed /private/tmp symlink. Treat that
|
|
91
|
+
// mount alias as trusted while rejecting caller-controlled components.
|
|
92
|
+
if (current !== "/tmp" && fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
NODE
|
|
95
|
+
then
|
|
96
|
+
echo "install-codex-home.sh: refusing symlink component in skills destination: $SKILLS_DIR" >&2
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
75
99
|
REAL_CODEX_HOME_REAL="$(canonicalize_path "$REAL_CODEX_HOME")"
|
|
76
100
|
case "$DEST_REAL/" in
|
|
77
101
|
"$ROOT_REAL/"*|"$BUNDLE_SOURCE_REAL/"*)
|
|
@@ -85,6 +109,17 @@ case "$ROOT_REAL/" in
|
|
|
85
109
|
exit 1
|
|
86
110
|
;;
|
|
87
111
|
esac
|
|
112
|
+
case "$SKILLS_DIR_REAL/" in
|
|
113
|
+
"$ROOT_REAL/"*|"$BUNDLE_SOURCE_REAL/"*) echo "install-codex-home.sh: skills destination overlaps Flow Agents source: $SKILLS_DIR_REAL" >&2; exit 1 ;;
|
|
114
|
+
esac
|
|
115
|
+
case "$ROOT_REAL/" in
|
|
116
|
+
"$SKILLS_DIR_REAL/"*) echo "install-codex-home.sh: Flow Agents source overlaps skills destination: $SKILLS_DIR_REAL" >&2; exit 1 ;;
|
|
117
|
+
esac
|
|
118
|
+
case "$BUNDLE_SOURCE_REAL/" in
|
|
119
|
+
"$SKILLS_DIR_REAL/"*) echo "install-codex-home.sh: generated bundle source overlaps skills destination: $SKILLS_DIR_REAL" >&2; exit 1 ;;
|
|
120
|
+
esac
|
|
121
|
+
SKILLS_PATH_ROOT="$(node -e 'process.stdout.write(require("node:path").parse(process.argv[1]).root)' "$SKILLS_DIR_REAL")"
|
|
122
|
+
[[ "$SKILLS_DIR_REAL" != "$SKILLS_PATH_ROOT" ]] || { echo "install-codex-home.sh: refusing filesystem-root skills destination: $SKILLS_DIR_REAL" >&2; exit 1; }
|
|
88
123
|
|
|
89
124
|
if command -v npm >/dev/null 2>&1; then
|
|
90
125
|
(cd "$ROOT_DIR" && FLOW_AGENTS_EXPORT_DIAGNOSTICS=0 npm run build:bundles --silent >/dev/null)
|
|
@@ -93,10 +128,6 @@ else
|
|
|
93
128
|
exit 1
|
|
94
129
|
fi
|
|
95
130
|
|
|
96
|
-
mkdir -p "$DEST"
|
|
97
|
-
DEST="$(cd "$DEST" && pwd -P)"
|
|
98
|
-
DEST_REAL="$DEST"
|
|
99
|
-
|
|
100
131
|
assert_safe_dest_path() {
|
|
101
132
|
local rel="$1"
|
|
102
133
|
local current="$DEST"
|
|
@@ -122,22 +153,17 @@ assert_safe_dest_path() {
|
|
|
122
153
|
esac
|
|
123
154
|
}
|
|
124
155
|
|
|
125
|
-
# Stash the user's existing hooks.json (if any), so the merge step below can
|
|
126
|
-
# preserve user hooks across installs while replacing Flow Agents-managed groups.
|
|
127
156
|
FA_USER_HOOKS_STASH=""
|
|
128
|
-
assert_safe_dest_path "hooks.json"
|
|
129
|
-
if [[ -f "$DEST/hooks.json" ]]; then
|
|
130
|
-
FA_USER_HOOKS_STASH="$(mktemp /tmp/fa-user-hooks.XXXXXX.json)"
|
|
131
|
-
cp "$DEST/hooks.json" "$FA_USER_HOOKS_STASH"
|
|
132
|
-
fi
|
|
133
157
|
|
|
134
158
|
# A real Codex home can contain user-owned files in every shared directory.
|
|
135
159
|
# Prepare an exact Flow Agents overlay, then synchronize only files recorded in
|
|
136
160
|
# the ownership manifest. The synchronizer never uses directory-wide --delete:
|
|
137
161
|
# it removes only unchanged files from its prior manifest and refuses collisions.
|
|
138
162
|
FA_OWNED_OVERLAY="$(mktemp -d /tmp/fa-codex-overlay.XXXXXX)"
|
|
163
|
+
FA_SKILLS_OVERLAY="$(mktemp -d /tmp/fa-codex-skills-overlay.XXXXXX)"
|
|
139
164
|
cleanup_install_temps() {
|
|
140
165
|
rm -rf "$FA_OWNED_OVERLAY"
|
|
166
|
+
rm -rf "$FA_SKILLS_OVERLAY"
|
|
141
167
|
[[ -z "${FA_USER_HOOKS_STASH:-}" ]] || rm -f "$FA_USER_HOOKS_STASH"
|
|
142
168
|
}
|
|
143
169
|
trap cleanup_install_temps EXIT
|
|
@@ -157,22 +183,14 @@ for managed_dir in \
|
|
|
157
183
|
scripts
|
|
158
184
|
do
|
|
159
185
|
if [[ -d "$ROOT_DIR/dist/codex/$managed_dir" ]]; then
|
|
160
|
-
assert_safe_dest_path "$managed_dir"
|
|
161
186
|
mkdir -p "$FA_OWNED_OVERLAY/$managed_dir"
|
|
162
187
|
rsync -a "$ROOT_DIR/dist/codex/$managed_dir/" "$FA_OWNED_OVERLAY/$managed_dir/"
|
|
163
188
|
fi
|
|
164
189
|
done
|
|
165
190
|
|
|
166
|
-
#
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
mkdir -p "$FA_OWNED_OVERLAY/skills"
|
|
170
|
-
rsync -a "$ROOT_DIR/dist/codex/skills/" "$FA_OWNED_OVERLAY/skills/"
|
|
171
|
-
fi
|
|
172
|
-
|
|
173
|
-
if [[ -d "$ROOT_DIR/dist/codex/.codex/skills" ]]; then
|
|
174
|
-
mkdir -p "$FA_OWNED_OVERLAY/skills"
|
|
175
|
-
rsync -a "$ROOT_DIR/dist/codex/.codex/skills/" "$FA_OWNED_OVERLAY/skills/"
|
|
191
|
+
# Portable skills use Codex's universal catalog, independently of CODEX_HOME.
|
|
192
|
+
if [[ -d "$ROOT_DIR/dist/codex/.agents/skills" ]]; then
|
|
193
|
+
rsync -a "$ROOT_DIR/dist/codex/.agents/skills/" "$FA_SKILLS_OVERLAY/"
|
|
176
194
|
fi
|
|
177
195
|
|
|
178
196
|
if [[ -d "$ROOT_DIR/dist/codex/kits" ]]; then
|
|
@@ -193,10 +211,31 @@ for bundle_file in README.md console.telemetry.json install.sh; do
|
|
|
193
211
|
fi
|
|
194
212
|
done
|
|
195
213
|
|
|
196
|
-
|
|
214
|
+
# Check both destinations with the exact synchronizer before either can mutate.
|
|
215
|
+
node "$ROOT_DIR/scripts/install-owned-files.js" \
|
|
216
|
+
--check "$FA_SKILLS_OVERLAY" "$SKILLS_DIR_REAL" ".flow-agents/codex-universal-skills-install-manifest.json"
|
|
217
|
+
node "$ROOT_DIR/scripts/install-owned-files.js" \
|
|
218
|
+
--check "$FA_OWNED_OVERLAY" "$DEST_REAL" ".flow-agents/codex-install-manifest.json"
|
|
219
|
+
|
|
220
|
+
mkdir -p "$SKILLS_DIR"
|
|
221
|
+
SKILLS_DIR="$(cd "$SKILLS_DIR" && pwd -P)"
|
|
222
|
+
node "$ROOT_DIR/scripts/install-owned-files.js" \
|
|
223
|
+
"$FA_SKILLS_OVERLAY" "$SKILLS_DIR" ".flow-agents/codex-universal-skills-install-manifest.json"
|
|
224
|
+
|
|
225
|
+
mkdir -p "$DEST"
|
|
226
|
+
DEST="$(cd "$DEST" && pwd -P)"
|
|
227
|
+
DEST_REAL="$DEST"
|
|
197
228
|
node "$ROOT_DIR/scripts/install-owned-files.js" \
|
|
198
229
|
"$FA_OWNED_OVERLAY" "$DEST" ".flow-agents/codex-install-manifest.json"
|
|
199
230
|
|
|
231
|
+
# Stash the user's existing hooks.json (if any), so the merge step below can
|
|
232
|
+
# preserve user hooks across installs while replacing Flow Agents-managed groups.
|
|
233
|
+
assert_safe_dest_path "hooks.json"
|
|
234
|
+
if [[ -f "$DEST/hooks.json" ]]; then
|
|
235
|
+
FA_USER_HOOKS_STASH="$(mktemp /tmp/fa-user-hooks.XXXXXX.json)"
|
|
236
|
+
cp "$DEST/hooks.json" "$FA_USER_HOOKS_STASH"
|
|
237
|
+
fi
|
|
238
|
+
|
|
200
239
|
atomic_copy() {
|
|
201
240
|
local source="$1"
|
|
202
241
|
local rel="$2"
|
|
@@ -289,6 +328,7 @@ if [[ ${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "${FLOW_AGENTS_TELEMETRY_SINK:-}" |
|
|
|
289
328
|
fi
|
|
290
329
|
|
|
291
330
|
echo "Installed Flow Agents into Codex home at $DEST"
|
|
331
|
+
echo "Installed portable skills at $SKILLS_DIR"
|
|
292
332
|
if [[ "${#profile_names[@]}" -gt 0 ]]; then
|
|
293
333
|
echo "Profiles: ${profile_names[*]}"
|
|
294
334
|
fi
|
|
@@ -9,10 +9,24 @@ function fail(message) {
|
|
|
9
9
|
process.exit(1);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const checkOnly = args[0] === "--check";
|
|
14
|
+
if (checkOnly) args.shift();
|
|
15
|
+
const [sourceArg, destArg, manifestArg] = args;
|
|
13
16
|
if (!sourceArg || !destArg || !manifestArg) fail("usage: install-owned-files.js <overlay> <destination> <manifest-relative-path>");
|
|
14
17
|
const source = fs.realpathSync(sourceArg);
|
|
15
|
-
|
|
18
|
+
function canonicalizeMissing(value) {
|
|
19
|
+
let current = path.resolve(value);
|
|
20
|
+
const missing = [];
|
|
21
|
+
while (!fs.existsSync(current)) {
|
|
22
|
+
missing.unshift(path.basename(current));
|
|
23
|
+
const parent = path.dirname(current);
|
|
24
|
+
if (parent === current) break;
|
|
25
|
+
current = parent;
|
|
26
|
+
}
|
|
27
|
+
return path.resolve(fs.realpathSync(current), ...missing);
|
|
28
|
+
}
|
|
29
|
+
const dest = canonicalizeMissing(destArg);
|
|
16
30
|
const manifestPath = path.join(dest, manifestArg);
|
|
17
31
|
|
|
18
32
|
function hashFile(file) {
|
|
@@ -132,6 +146,8 @@ for (const [rel] of previous) {
|
|
|
132
146
|
if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) fail(`refusing to remove symlink replacing owned file: ${target}`);
|
|
133
147
|
}
|
|
134
148
|
|
|
149
|
+
if (checkOnly) process.exit(0);
|
|
150
|
+
|
|
135
151
|
for (const entry of incoming) {
|
|
136
152
|
const target = targetFor(entry.rel);
|
|
137
153
|
ensureSafeParent(target, true);
|