@kontourai/flow-agents 2.4.0 → 3.1.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/CODEOWNERS +8 -0
- package/.github/workflows/ci.yml +20 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/CHANGELOG.md +38 -0
- package/CONTEXT.md +88 -1
- package/README.md +4 -0
- package/build/src/cli/assignment-provider.d.ts +144 -0
- package/build/src/cli/assignment-provider.js +805 -0
- package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
- package/build/src/cli/effective-assignment-provider-settings.js +125 -0
- package/build/src/cli/validate-workflow-artifacts.js +5 -1
- package/build/src/cli/workflow-sidecar.d.ts +2 -2
- package/build/src/cli/workflow-sidecar.js +549 -145
- package/build/src/cli.js +6 -0
- package/build/src/lib/flow-resolver.d.ts +12 -6
- package/build/src/lib/flow-resolver.js +30 -14
- package/build/src/tools/validate-source-tree.js +2 -0
- package/context/contracts/artifact-contract.md +2 -0
- package/context/contracts/assignment-provider-contract.md +248 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +2 -0
- package/context/contracts/decision-registry-contract.md +2 -0
- package/context/contracts/delivery-contract.md +2 -0
- package/context/contracts/execution-contract.md +25 -0
- package/context/contracts/governance-adapter-contract.md +2 -0
- package/context/contracts/knowledge-store-contract.md +197 -0
- package/context/contracts/planning-contract.md +2 -0
- package/context/contracts/probe-docs-write-contract.md +187 -0
- package/context/contracts/review-contract.md +2 -0
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/contracts/work-item-contract.md +2 -0
- package/context/scripts/hooks/config-protection.js +14 -1
- package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/context/scripts/hooks/stop-goal-fit.js +4 -2
- package/context/scripts/hooks/workflow-steering.js +42 -0
- package/context/settings/assignment-provider-settings.json +33 -0
- package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
- package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
- package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0006-typescript-first-source-policy.md +2 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0007-skill-audit.md +2 -0
- package/docs/adr/0008-kit-operation-boundary.md +2 -0
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
- package/docs/adr/0011-mcp-posture.md +2 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
- package/docs/adr/0013-context-lifecycle.md +2 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
- package/docs/adr/0016-three-hard-boundary-model.md +2 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
- package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
- package/docs/adr/0019-kit-dependency-ownership.md +2 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +182 -0
- package/docs/adr/README.md +49 -0
- package/docs/adr/index.md +34 -0
- package/docs/context-map.md +1 -0
- package/docs/decisions/agent-coordination.md +20 -0
- package/docs/decisions/anti-gaming-trust-security.md +20 -0
- package/docs/decisions/context-lifecycle.md +18 -0
- package/docs/decisions/core-domain-kit-boundary.md +18 -0
- package/docs/decisions/flow-flow-agents-boundary.md +18 -0
- package/docs/decisions/flow-kit.md +20 -0
- package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
- package/docs/decisions/graph-knowledge-provider.md +63 -0
- package/docs/decisions/hook-core-kit-boundary.md +18 -0
- package/docs/decisions/index.md +22 -0
- package/docs/decisions/kit-dependency-ownership.md +18 -0
- package/docs/decisions/kit-operation-boundary.md +18 -0
- package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
- package/docs/decisions/knowledge-store-provider.md +51 -0
- package/docs/decisions/kontour-resource-contract.md +18 -0
- package/docs/decisions/mcp-posture.md +18 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/standing-directives.md +66 -0
- package/docs/decisions/three-hard-boundary-model.md +18 -0
- package/docs/decisions/trust-reconcile.md +20 -0
- package/docs/decisions/typescript-source-policy.md +48 -0
- package/docs/decisions/workflow-enforcement.md +18 -0
- package/docs/decisions/workflow-trust-state.md +20 -0
- package/docs/fixture-ownership.md +1 -0
- package/docs/workflow-shared-contracts.md +2 -1
- package/docs/workflow-usage-guide.md +7 -1
- package/evals/ci/run-baseline.sh +10 -0
- package/evals/fixtures/assignment-provider/actor-a.json +6 -0
- package/evals/fixtures/assignment-provider/actor-b.json +6 -0
- package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
- package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
- package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
- package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/integration/test_assignment_provider_github.sh +318 -0
- package/evals/integration/test_assignment_provider_local_file.sh +222 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- package/evals/integration/test_current_json_per_actor.sh +516 -0
- package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_gate_lockdown.sh +10 -0
- package/evals/integration/test_publish_delivery.sh +21 -4
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +20 -7
- package/evals/integration/test_reconcile_soundness.sh +33 -9
- package/evals/integration/test_trust_reconcile.sh +9 -8
- package/evals/integration/test_trust_reconcile_negatives.sh +608 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +91 -3
- package/evals/run.sh +14 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +6 -0
- package/evals/static/test_knowledge_providers.sh +32 -0
- package/evals/static/test_workflow_skills.sh +15 -2
- package/kits/builder/skills/deliver/SKILL.md +36 -0
- package/kits/builder/skills/design-probe/SKILL.md +37 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
- package/kits/builder/skills/pull-work/SKILL.md +78 -10
- package/kits/knowledge/adapters/default-store/index.js +92 -4
- package/kits/knowledge/adapters/obsidian-store/index.js +69 -4
- package/kits/knowledge/adapters/shared/codec.js +141 -0
- package/kits/knowledge/docs/README.md +121 -2
- package/kits/knowledge/docs/store-contract.md +112 -4
- package/kits/knowledge/evals/contract-suite/suite.test.js +176 -0
- package/kits/knowledge/flows/promote.flow.json +84 -0
- package/kits/knowledge/kit.json +50 -0
- package/kits/knowledge/promote/distill.js +96 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
- package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
- package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
- package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
- package/kits/knowledge/promote/fixtures/session/state.json +19 -0
- package/kits/knowledge/promote/health.js +137 -0
- package/kits/knowledge/promote/index.js +176 -0
- package/kits/knowledge/promote/ingest.js +103 -0
- package/kits/knowledge/promote/lib.js +132 -0
- package/kits/knowledge/promote/link.js +84 -0
- package/kits/knowledge/promote/promote.test.js +174 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
- package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
- package/kits/knowledge/providers/conformance/suite.test.js +143 -0
- package/kits/knowledge/providers/git-repo/index.js +236 -0
- package/kits/knowledge/providers/health/health-pass.test.js +99 -0
- package/kits/knowledge/providers/health/index.js +153 -0
- package/kits/knowledge/providers/index.js +25 -0
- package/kits/knowledge/providers/lib/model.js +91 -0
- package/kits/knowledge/providers/lib/schema-validate.js +119 -0
- package/kits/knowledge/providers/markdown-vault/index.js +169 -0
- package/kits/knowledge/providers/neo4j/connection.js +121 -0
- package/kits/knowledge/providers/neo4j/cypher.js +190 -0
- package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
- package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
- package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
- package/kits/knowledge/providers/neo4j/index.js +280 -0
- package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
- package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
- package/kits/knowledge/providers/neo4j/sync.js +235 -0
- package/kits/knowledge/providers/work-item/index.js +204 -0
- package/package.json +7 -3
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/knowledge/edge.schema.json +54 -0
- package/schemas/knowledge/health-report.schema.json +45 -0
- package/schemas/knowledge/node.schema.json +49 -0
- package/schemas/knowledge/proposal.schema.json +53 -0
- package/scripts/README.md +1 -0
- package/scripts/ci/trust-reconcile.js +521 -24
- package/scripts/freeze-adrs.mjs +364 -0
- package/scripts/hooks/config-protection.js +14 -1
- package/scripts/hooks/evidence-capture.js +4 -1
- package/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/scripts/hooks/lib/current-pointer.js +123 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
- package/scripts/hooks/stop-goal-fit.js +4 -2
- package/scripts/hooks/workflow-steering.js +42 -0
- package/scripts/statusline/flow-agents-statusline.js +3 -1
- package/src/cli/assignment-provider.ts +927 -0
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/validate-workflow-artifacts.ts +5 -1
- package/src/cli/workflow-sidecar.ts +577 -139
- package/src/cli.ts +6 -0
- package/src/lib/flow-resolver.ts +35 -14
- package/src/tools/validate-source-tree.ts +2 -0
|
@@ -18,10 +18,24 @@
|
|
|
18
18
|
* the actor-scoped stream has no shared-write race.
|
|
19
19
|
*
|
|
20
20
|
* Hot-path ordering + bounded read (F3, #288 fix iteration 1, sec-MED +
|
|
21
|
-
* cr-MED): steps run enabled? ->
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* cr-MED; restored by F4, fix-plan iteration 1): steps run enabled? ->
|
|
22
|
+
* mightHaveActiveSession() peek -> resolveActor() -> current.json
|
|
23
|
+
* active_slug present? -> bounded tail read, in that order, so a repo with
|
|
24
|
+
* liveness disabled OR no active session never pays the `resolveActor()`
|
|
25
|
+
* process-ancestry `ps` spawn cost. (#291 initially moved resolveActor()
|
|
26
|
+
* ahead of the active_slug check unconditionally, since the current-pointer
|
|
27
|
+
* read became actor-aware and needs the resolved actor to choose per-actor
|
|
28
|
+
* vs. legacy `current.json` — this silently dropped the "no active session"
|
|
29
|
+
* half of the short-circuit for every liveness-enabled call, not just the
|
|
30
|
+
* "disabled" half. F4 restores it: `mightHaveActiveSession()` is a plain,
|
|
31
|
+
* actor-free `fs.existsSync` peek of the legacy `current.json` file and the
|
|
32
|
+
* `current/` per-actor directory — when NEITHER exists, no actor could
|
|
33
|
+
* possibly have an active session (per-actor files only ever live under
|
|
34
|
+
* `current/`), so resolveActor() is skipped entirely, exactly restoring the
|
|
35
|
+
* pre-#291 short-circuit for the common "enabled, no session yet" case. Only
|
|
36
|
+
* once a current-pointer file might exist does resolveActor() run, still
|
|
37
|
+
* exactly once, reused for both the actor-scoped current-pointer read and the
|
|
38
|
+
* actor-unresolved gate below.)
|
|
25
39
|
*
|
|
26
40
|
* Orphan-heartbeat invariant (F8(ii), #288 fix iteration 2): a heartbeat must
|
|
27
41
|
* never be treated as its own evidence of a claim. The reviewer-reproduced
|
|
@@ -74,12 +88,12 @@
|
|
|
74
88
|
|
|
75
89
|
const fs = require('fs');
|
|
76
90
|
const path = require('path');
|
|
77
|
-
|
|
78
91
|
const { isLivenessEnabled, resolveHeartbeatThrottleSeconds } = require('./liveness-policy');
|
|
79
92
|
const { livenessStreamFile, appendLivenessEvent } = require('./liveness-write');
|
|
80
93
|
const { readLivenessEvents, readLivenessEventsTail, freshHolders } = require('./liveness-read');
|
|
81
94
|
const { resolveActor, isUnresolvedActor, sanitizeSegment } = require('./actor-identity');
|
|
82
95
|
const { flowAgentsArtifactRoot } = require('./local-artifact-paths');
|
|
96
|
+
const { readCurrentPointer } = require('./current-pointer');
|
|
83
97
|
|
|
84
98
|
/**
|
|
85
99
|
* Resolve a caller-supplied `now` (Date, ISO string, or omitted) to epoch ms.
|
|
@@ -101,35 +115,50 @@ function resolveNowMs(now) {
|
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
/**
|
|
104
|
-
* Read
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
118
|
+
* Read the active_slug from the actor-scoped "current" pointer (#291: per-actor
|
|
119
|
+
* `current/<actor>.json` preferred over the legacy global `current.json`, via
|
|
120
|
+
* `readCurrentPointer` — the single choke point for that preference rule), sanitized through the
|
|
121
|
+
* same charset+cap restriction as actor-identity.js's `sanitizeSegment` (F5, #288 fix iteration 1,
|
|
122
|
+
* sec-LOW: this is a local file that could be hand-edited or otherwise hostile — `active_slug`
|
|
123
|
+
* must never be trusted verbatim before it is used as a JSONL grouping key / emitted `subjectId`
|
|
124
|
+
* or compared against event data). Tolerates a missing file or malformed JSON (returns "") exactly
|
|
125
|
+
* like the plain-`fs.readFileSync` read this replaces; when `actorKey` is empty/unresolved,
|
|
126
|
+
* `readCurrentPointer` falls straight to the legacy global file, so behavior for that case is
|
|
127
|
+
* unchanged from before #291.
|
|
110
128
|
*
|
|
111
129
|
* @param {string} root
|
|
130
|
+
* @param {string} [actorKey]
|
|
112
131
|
* @returns {string}
|
|
113
132
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
/**
|
|
134
|
+
* F4 fix (fix-plan iteration 1, MED): a cheap, actor-free existence peek restoring the module's
|
|
135
|
+
* documented "no active session never pays resolveActor()" invariant. `readCurrentPointer`'s
|
|
136
|
+
* actor-aware preference genuinely needs a resolved actor to choose per-actor vs. legacy — but
|
|
137
|
+
* that choice is only ever relevant when SOME current-pointer file might exist at all. When
|
|
138
|
+
* neither the legacy global `<root>/current.json` file NOR the `<root>/current/` per-actor
|
|
139
|
+
* directory exists, there is provably no active session for any actor (per-actor files are only
|
|
140
|
+
* ever written into that directory — see current-pointer.js's writePerActorCurrent), so this
|
|
141
|
+
* function returns false without calling resolveActor() at all, exactly restoring the pre-#291
|
|
142
|
+
* short-circuit for the common "liveness enabled, no session yet" case. Two plain fs.existsSync
|
|
143
|
+
* calls — no ps/proc ancestry cost, no JSON parse.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} root
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
function mightHaveActiveSession(root) {
|
|
149
|
+
return fs.existsSync(path.join(root, 'current.json')) || fs.existsSync(path.join(root, 'current'));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readActiveSlug(root, actorKey) {
|
|
153
|
+
const { payload: current } = readCurrentPointer(root, actorKey);
|
|
154
|
+
if (!current) return '';
|
|
155
|
+
const activeSlug = (current && current.active_slug) || '';
|
|
156
|
+
if (!activeSlug) return '';
|
|
157
|
+
// sanitizeSegment falls back to the literal "unknown" for an all-stripped input; that
|
|
158
|
+
// fallback is not a legitimate slug, so treat it as "no active slug" here rather than
|
|
159
|
+
// matching against a subject that was never really claimed.
|
|
160
|
+
const sanitized = sanitizeSegment(activeSlug);
|
|
161
|
+
return sanitized === 'unknown' ? '' : sanitized;
|
|
133
162
|
}
|
|
134
163
|
|
|
135
164
|
/**
|
|
@@ -221,9 +250,10 @@ function attachConflictField(result, conflict) {
|
|
|
221
250
|
* object, never throws.
|
|
222
251
|
*
|
|
223
252
|
* See the module header for the full F8(ii) throttle/emit split. Summary:
|
|
224
|
-
* - enabled? ->
|
|
225
|
-
*
|
|
226
|
-
*
|
|
253
|
+
* - enabled? -> mightHaveActiveSession() peek -> resolveActor() -> slug
|
|
254
|
+
* present? -> bounded tail read, in that order (F3, restored by F4), so
|
|
255
|
+
* a disabled repo or one with no active session never pays the
|
|
256
|
+
* resolveActor() ancestry `ps` spawn cost.
|
|
227
257
|
* - THROTTLE decision reads only the bounded tail (never a full read):
|
|
228
258
|
* a release found there refuses; a fresh event there throttles.
|
|
229
259
|
* - EMIT decision requires an actual `claim` event for the pair: taken
|
|
@@ -248,12 +278,22 @@ function maybeEmitHeartbeat(opts = {}) {
|
|
|
248
278
|
}
|
|
249
279
|
|
|
250
280
|
const root = flowAgentsArtifactRoot(cwd);
|
|
251
|
-
|
|
252
|
-
|
|
281
|
+
// F4 fix (fix-plan iteration 1, MED): restore the "no active session never pays
|
|
282
|
+
// resolveActor()" invariant. mightHaveActiveSession() is a plain fs.existsSync peek of the
|
|
283
|
+
// LEGACY current.json / current/ dir — no actor needed — so a repo with liveness enabled but
|
|
284
|
+
// no session at all (the common case this invariant exists for) short-circuits here, exactly
|
|
285
|
+
// like pre-#291. Only once a current-pointer file MIGHT exist do we pay for resolveActor(),
|
|
286
|
+
// needed to decide per-actor vs. legacy preference via readActiveSlug/readCurrentPointer.
|
|
287
|
+
if (!mightHaveActiveSession(root)) {
|
|
253
288
|
return { emitted: false, reason: 'no-current' };
|
|
254
289
|
}
|
|
255
290
|
|
|
256
291
|
const { actor } = resolveActor(env);
|
|
292
|
+
const slug = readActiveSlug(root, actor);
|
|
293
|
+
if (!slug) {
|
|
294
|
+
return { emitted: false, reason: 'no-current' };
|
|
295
|
+
}
|
|
296
|
+
|
|
257
297
|
if (isUnresolvedActor(actor)) {
|
|
258
298
|
return { emitted: false, reason: 'actor-unresolved' };
|
|
259
299
|
}
|
|
@@ -42,6 +42,8 @@ const {
|
|
|
42
42
|
flowAgentsArtifactRoot,
|
|
43
43
|
flowAgentsArtifactRootsForRead,
|
|
44
44
|
} = require('./lib/local-artifact-paths');
|
|
45
|
+
const { resolveActor } = require('./lib/actor-identity.js');
|
|
46
|
+
const { readCurrentPointer } = require('./lib/current-pointer.js');
|
|
45
47
|
|
|
46
48
|
const MAX_STDIN = 1024 * 1024;
|
|
47
49
|
const ACTIVE_STATUSES = new Set([
|
|
@@ -1531,7 +1533,7 @@ async function bundleEnforcement(artifactDir, activeFlowStep) {
|
|
|
1531
1533
|
* to scanning all of .kontourai/flow-agents (newest-mtime).
|
|
1532
1534
|
*/
|
|
1533
1535
|
function preferredArtifactDir(flowAgentsDir) {
|
|
1534
|
-
const current =
|
|
1536
|
+
const { payload: current } = readCurrentPointer(flowAgentsDir, resolveActor(process.env).actor);
|
|
1535
1537
|
if (!current) return null;
|
|
1536
1538
|
const slug = current.artifact_dir || current.active_slug;
|
|
1537
1539
|
if (typeof slug !== 'string' || !slug.trim()) return null;
|
|
@@ -1549,7 +1551,7 @@ function hasSidecarPresence(artifactDir) {
|
|
|
1549
1551
|
// return that slug so analyze() can log the staleness rather than silently falling back to
|
|
1550
1552
|
// a global mtime scan that could resurface an abandoned/never-real session as active.
|
|
1551
1553
|
function staleCurrentSlug(flowAgentsDir) {
|
|
1552
|
-
const current =
|
|
1554
|
+
const { payload: current } = readCurrentPointer(flowAgentsDir, resolveActor(process.env).actor);
|
|
1553
1555
|
if (!current) return null;
|
|
1554
1556
|
const slug = current.artifact_dir || current.active_slug;
|
|
1555
1557
|
if (typeof slug !== 'string' || !slug.trim()) return null;
|
|
@@ -19,6 +19,7 @@ const path = require('path');
|
|
|
19
19
|
const { readLivenessEvents, freshHolders } = require('./lib/liveness-read');
|
|
20
20
|
const { resolveActor } = require('./lib/actor-identity');
|
|
21
21
|
const { flowAgentsArtifactRootsForRead } = require('./lib/local-artifact-paths');
|
|
22
|
+
const { readCurrentPointer } = require('./lib/current-pointer');
|
|
22
23
|
|
|
23
24
|
const STEERING = {
|
|
24
25
|
'tool-planner': [
|
|
@@ -102,7 +103,48 @@ function readJson(file) {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/**
|
|
107
|
+
* #291 (Conflict #2, Wave 2 Task 2.4): actor-scoped preference consulted BEFORE the global
|
|
108
|
+
* newest-mtime scan below. For each read-root, resolve the "current" pointer via
|
|
109
|
+
* `readCurrentPointer` (per-actor `current/<actor>.json` preferred, legacy global
|
|
110
|
+
* `current.json` fallback — the same single choke point every other Wave 2 consumer uses) and,
|
|
111
|
+
* if it names a session dir whose `state.json` is still in an ACTIVE_STATE_STATUSES status,
|
|
112
|
+
* return that state immediately — so actor A's own ambient steering hint always prefers A's own
|
|
113
|
+
* current task over a globally-newer-mtime `state.json` some other actor (B) happened to touch
|
|
114
|
+
* more recently. Returns null (never throws) when no root yields an actor-scoped resolution,
|
|
115
|
+
* so the caller falls through to the unchanged global scan below — this is the compat-shim
|
|
116
|
+
* guarantee: when `actorKey` is empty/unresolved and/or no per-actor file exists yet, this
|
|
117
|
+
* degrades to the SAME legacy-`current.json`-or-nothing lookup the compat shim already performs
|
|
118
|
+
* for every other Wave 2 reader, so a single-session/CI/legacy-only fixture cannot fail to
|
|
119
|
+
* resolve here in a way it wouldn't already have resolved via the global scan.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} root
|
|
122
|
+
* @param {string} actorKey
|
|
123
|
+
* @returns {{file: string, payload: object, mtimeMs: number}|null}
|
|
124
|
+
*/
|
|
125
|
+
function actorScopedWorkflowState(root, actorKey) {
|
|
126
|
+
for (const flowAgentsDir of flowAgentsArtifactRootsForRead(root)) {
|
|
127
|
+
const { payload: current } = readCurrentPointer(flowAgentsDir, actorKey);
|
|
128
|
+
if (!current) continue;
|
|
129
|
+
const slug = current.artifact_dir || current.active_slug;
|
|
130
|
+
if (typeof slug !== 'string' || !slug.trim()) continue;
|
|
131
|
+
const safe = slug.replace(/\.\.+/g, '').replace(/^[/\\]+/, '');
|
|
132
|
+
const dir = path.join(flowAgentsDir, safe);
|
|
133
|
+
if (!dir.startsWith(flowAgentsDir + path.sep) || !fs.existsSync(dir)) continue;
|
|
134
|
+
const file = path.join(dir, 'state.json');
|
|
135
|
+
let stat;
|
|
136
|
+
try { stat = fs.statSync(file); } catch { continue; }
|
|
137
|
+
const payload = readJson(file);
|
|
138
|
+
if (!payload || !ACTIVE_STATE_STATUSES.has(payload.status)) continue;
|
|
139
|
+
return { file, payload, mtimeMs: stat.mtimeMs };
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
105
144
|
function latestWorkflowState(root) {
|
|
145
|
+
const preferred = actorScopedWorkflowState(root, resolveActor(process.env).actor);
|
|
146
|
+
if (preferred) return preferred;
|
|
147
|
+
|
|
106
148
|
const states = flowAgentsArtifactRootsForRead(root)
|
|
107
149
|
.flatMap(artifactRoot => walkStateFiles(artifactRoot))
|
|
108
150
|
.map(file => {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
+
const { resolveActor } = require('../hooks/lib/actor-identity.js');
|
|
14
|
+
const { readCurrentPointer } = require('../hooks/lib/current-pointer.js');
|
|
13
15
|
|
|
14
16
|
const MAX_SUMMARY = 72;
|
|
15
17
|
|
|
@@ -76,7 +78,7 @@ function stateFiles(workflowRoot) {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
function chooseWorkflow(workflowRoot) {
|
|
79
|
-
const current =
|
|
81
|
+
const { payload: current } = readCurrentPointer(workflowRoot, resolveActor(process.env).actor);
|
|
80
82
|
if (current?.active_slug || current?.artifact_dir) {
|
|
81
83
|
const slug = current.active_slug || current.artifact_dir;
|
|
82
84
|
const dir = path.join(workflowRoot, slug);
|