@cohortapp/agent-sdk 2.4.1 → 2.5.1
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/bin/maestro.mjs +9 -0
- package/lib/backlog.mjs +35 -0
- package/lib/backlog.test.mjs +36 -0
- package/lib/channels/contract.mjs +1 -0
- package/lib/channels/contract.test.mjs +2 -1
- package/lib/channels/inbox-item.mjs +54 -0
- package/lib/comms/send-gate.mjs +56 -1
- package/lib/comms/send-gate.test.mjs +56 -0
- package/lib/execution/disposition.mjs +62 -2
- package/lib/execution/disposition.test.mjs +54 -0
- package/lib/execution/drive.mjs +1 -1
- package/lib/execution/effects.mjs +282 -24
- package/lib/execution/effects.test.mjs +112 -0
- package/lib/execution/index.mjs +1 -0
- package/lib/execution/intake.mjs +43 -9
- package/lib/execution/intake.test.mjs +46 -0
- package/lib/execution/pipeline.mjs +5 -0
- package/lib/execution/surface-policy.mjs +80 -30
- package/lib/goals/classify.mjs +49 -5
- package/lib/goals/classify.test.mjs +58 -0
- package/lib/goals/collaborate.mjs +131 -17
- package/lib/goals/collaborate.test.mjs +16 -4
- package/lib/goals/loop.mjs +160 -9
- package/lib/goals/loop.test.mjs +129 -3
- package/lib/kpi-sensors.mjs +666 -0
- package/lib/kpi-sensors.test.mjs +275 -0
- package/lib/kpi.mjs +23 -0
- package/lib/mandate/audit.mjs +3 -0
- package/lib/mandate/contract.mjs +277 -0
- package/lib/mandate/contract.test.mjs +185 -0
- package/lib/mandate/derive.mjs +49 -5
- package/lib/mandate/derive.test.mjs +7 -1
- package/lib/mandate/model.mjs +10 -1
- package/lib/mandate/model.test.mjs +22 -3
- package/lib/mandate/refresh.mjs +53 -5
- package/lib/mandate/refresh.test.mjs +83 -1
- package/lib/org/doctor.mjs +66 -0
- package/lib/org/doctor.test.mjs +73 -1
- package/lib/org/inbound/directedness.mjs +119 -1
- package/lib/org/inbound/directedness.test.mjs +67 -0
- package/lib/org/inbound/facts.mjs +132 -9
- package/lib/org/inbound/facts.test.mjs +96 -0
- package/lib/org/inbound/hydrate.mjs +147 -15
- package/lib/org/inbound/hydrate.test.mjs +127 -0
- package/lib/org/inbound/index.test.mjs +83 -0
- package/lib/org/inbound/project.mjs +8 -0
- package/lib/org/inbound/surfaces.mjs +20 -0
- package/lib/org/param-contract.mjs +16 -2
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +214 -2
- package/lib/org/protocol.test.mjs +11 -2
- package/lib/org/push.mjs +213 -49
- package/lib/org/push.test.mjs +112 -10
- package/lib/plan/compile.mjs +85 -8
- package/lib/plan/compile.test.mjs +82 -0
- package/lib/plan/emit.test.mjs +6 -1
- package/lib/setup/sections/mandate.mjs +43 -1
- package/lib/subagents/schema.mjs +14 -2
- package/lib/subagents/schema.test.mjs +22 -0
- package/package.json +1 -1
- package/scripts/ci/check-subagent-frontmatter.mjs +139 -0
- package/scripts/ci/check-subagent-frontmatter.test.mjs +124 -0
- package/scripts/ci/check.mjs +3 -0
- package/scripts/ci/conformance-org-api.mjs +16 -0
- package/scripts/ci/journey-approval-escalation.mjs +341 -0
- package/scripts/daemon/agent-daemon.mjs +582 -28
- package/scripts/daemon/cadence-handlers.mjs +273 -17
- package/scripts/daemon/cadence-handlers.test.mjs +101 -0
- package/scripts/daemon/execution-ladder.test.mjs +430 -0
- package/scripts/daemon/goal-steward-cadence.test.mjs +69 -0
- package/scripts/daemon/maestro-daemon.mjs +53 -0
- package/scripts/daemon/prompt-builder.mjs +47 -0
- package/scripts/daemon/responder.mjs +70 -3
- package/scripts/poller/imap-client.mjs +20 -1
- package/scripts/poller/inbox-scan-poller.mjs +15 -0
- package/scripts/poller/utils.mjs +51 -0
- package/scripts/setup/generate-capability.mjs +120 -11
- package/scripts/setup/generate-capability.test.mjs +134 -0
- package/scripts/setup/generate-plan.mjs +6 -1
- package/scripts/setup/repair-subagent-frontmatter.mjs +231 -0
package/lib/org/protocol.mjs
CHANGED
|
@@ -55,6 +55,27 @@ export const FAMILIES = Object.freeze([
|
|
|
55
55
|
// the Directory (2026-07): the canonical registry of external people and
|
|
56
56
|
// organisations every other desk resolves its counterparties through.
|
|
57
57
|
"directory",
|
|
58
|
+
// the sub-agent registry (2026-08): layer 1 of the SDK / workspace /
|
|
59
|
+
// agent-local resolution model — org-scoped, versioned sub-agent definitions
|
|
60
|
+
// pinned per seat. Governs the DISTRIBUTION and provenance of agent prose;
|
|
61
|
+
// resolution itself runs locally on the agent, never here.
|
|
62
|
+
"subagent",
|
|
63
|
+
// the agent's own control lane (2026-08): agent.wait, the bounded long-poll a
|
|
64
|
+
// daemon parks on instead of polling every 45s. Read-only — nothing in this
|
|
65
|
+
// family appends to the chain; it only READS the chain by seq.
|
|
66
|
+
"agent",
|
|
67
|
+
// the MANDATE spine (2026-08, SPEC "Three-Dimensional Agent Autonomy"): the
|
|
68
|
+
// objective tree, its KPI samples, the published per-seat mandate snapshot,
|
|
69
|
+
// and the agent's own capability/plan/drift reports. hq owns the MANDATE
|
|
70
|
+
// (outcomes, owners, targets, budgets, kill-switch); the agent owns the PLAN
|
|
71
|
+
// (obligations, schedule, rung) and compiles it locally. This family is the
|
|
72
|
+
// wire between the two halves.
|
|
73
|
+
"mandate",
|
|
74
|
+
// member-scoped PREFERENCES (2026-08, SPEC §9 reverse parity). hq has always
|
|
75
|
+
// injected these into responder context automatically; the family exists so a
|
|
76
|
+
// seat can PERSIST one it learned rather than re-deriving it every session.
|
|
77
|
+
// The chain rows carry subject + domain + key only — never the value.
|
|
78
|
+
"preference",
|
|
58
79
|
]);
|
|
59
80
|
|
|
60
81
|
/**
|
|
@@ -131,6 +152,17 @@ export const SCOPES = Object.freeze([
|
|
|
131
152
|
"directory.write", // Directory writes: create/update parties, channels, captures, links, lists. Structural verbs (merge execution, awareness change, archive) refuse an agent actor in-domain and create a review item instead
|
|
132
153
|
"design.read", // Design reads: the SAVED brand foundation snapshot (never a draft), voice, visible templates. Renders and voice rewrites are AUDITED side-effecting reads under this scope (files.exportRequest precedent)
|
|
133
154
|
"design.write", // Design writes: generate imagery, propose a foundation change. Foundation mutation itself is admin-only and human-gated -- a proposal creates a reviewable diff, it does not write
|
|
155
|
+
// mandate spine (2026-08). The read/propose/write split is the anti-Goodhart
|
|
156
|
+
// law expressed as scopes, and it deliberately mirrors approval.request vs
|
|
157
|
+
// approval.decide: an agent may READ its mandate and PROPOSE against it, but
|
|
158
|
+
// ADOPTING an objective (making it live, work-creating and graded) is a
|
|
159
|
+
// human/manager act the seat's own credential cannot perform.
|
|
160
|
+
"mandate.read", // read own mandate snapshot + version + KPI series
|
|
161
|
+
"mandate.propose", // propose/update objectives — `state:'proposed'` ONLY, never an adopted row
|
|
162
|
+
"mandate.write", // adopt / retire an objective. NOT in DEFAULT_AGENT_SCOPES — human/manager only
|
|
163
|
+
"kpi.write", // report a KPI sample against an objective. Honesty is enforced by the
|
|
164
|
+
// server-set `source` field, never by scope: a seat may always report,
|
|
165
|
+
// but an `llm`-sourced number is advisory and cannot create work.
|
|
134
166
|
"admin", // pairing approval, deactivate (kill switch), governance, policy, credential put/revoke
|
|
135
167
|
]);
|
|
136
168
|
|
|
@@ -148,6 +180,11 @@ export const DEFAULT_AGENT_SCOPES = Object.freeze([
|
|
|
148
180
|
"files.read", "files.write", "calendar.read", "calendar.write",
|
|
149
181
|
"crm.read", "crm.write", "books.read", "books.write",
|
|
150
182
|
"directory.read", "directory.write", "design.read", "design.write",
|
|
183
|
+
// mandate spine (2026-08): a seat reads its own mandate, proposes against it,
|
|
184
|
+
// and reports its own samples. `mandate.write` (adopt/retire) is POINTEDLY
|
|
185
|
+
// ABSENT — that is the whole anti-Goodhart law. A seat cannot define, measure
|
|
186
|
+
// AND be graded on the same number.
|
|
187
|
+
"mandate.read", "mandate.propose", "kpi.write",
|
|
151
188
|
]);
|
|
152
189
|
|
|
153
190
|
/** Knowledge group scopes: org-wide, per-unit, or an information-barrier cell (deny-override). */
|
|
@@ -240,6 +277,13 @@ export const METHODS = Object.freeze({
|
|
|
240
277
|
"messaging.react": { family: "messaging", scope: "messaging.write", sideEffecting: true },
|
|
241
278
|
"messaging.edit": { family: "messaging", scope: "messaging.write", sideEffecting: true },
|
|
242
279
|
"messaging.history": { family: "messaging", scope: "messaging.read", sideEffecting: false },
|
|
280
|
+
// --- messaging.search (2026-08 REVERSE PARITY, SPEC §9): the family carried
|
|
281
|
+
// thirteen methods and no search, so an SDK-driven seat could not answer
|
|
282
|
+
// "what did we decide about X anywhere?" while the hq responder in the
|
|
283
|
+
// same workspace could (its `search_messages` tool). Postgres FTS across
|
|
284
|
+
// every channel the paired member can read; the ACL floor is computed
|
|
285
|
+
// server-side, so `channelId`/`channelSlug` narrow and can never widen. ---
|
|
286
|
+
"messaging.search": { family: "messaging", scope: "messaging.read", sideEffecting: false },
|
|
243
287
|
// --- messaging.typing (2026-08): the "is composing…" indicator an agent raises
|
|
244
288
|
// while it works. The ONE write-less method in the family — it appends NO
|
|
245
289
|
// chain event and writes NO row, it publishes an EPHEMERAL frame that
|
|
@@ -357,6 +401,14 @@ export const METHODS = Object.freeze({
|
|
|
357
401
|
"escalation.create": { family: "escalation", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
358
402
|
"escalation.resolve": { family: "escalation", scope: "org.write", sideEffecting: true },
|
|
359
403
|
"escalation.list": { family: "escalation", scope: "org.read", sideEffecting: false },
|
|
404
|
+
// `OpenQuestion`'s FIRST WRITERS. The model has had readers and a UI for
|
|
405
|
+
// months with no producer, so a charter `escalationTrigger` firing could only
|
|
406
|
+
// ever emit prose. `ask` requires >= 2 concrete options (a one-option question
|
|
407
|
+
// is a notification, not a decision); `answer` is deliberately NOT idempotent
|
|
408
|
+
// — a second answer is a CONFLICT so the first decision stands — and refuses
|
|
409
|
+
// the asker, the same law as `Approval.requester ≠ approver`.
|
|
410
|
+
"escalation.ask": { family: "escalation", scope: "org.write", sideEffecting: true },
|
|
411
|
+
"escalation.answer": { family: "escalation", scope: "org.write", sideEffecting: true },
|
|
360
412
|
// --- annotation ---
|
|
361
413
|
"annotation.create": { family: "annotation", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
362
414
|
"annotation.comment": { family: "annotation", scope: "org.write", sideEffecting: true },
|
|
@@ -380,6 +432,26 @@ export const METHODS = Object.freeze({
|
|
|
380
432
|
"memory.proposeAmendment": { family: "memory", scope: "knowledge.write", sideEffecting: true },
|
|
381
433
|
"memory.mergeAmendment": { family: "memory", scope: "knowledge.write", sideEffecting: true },
|
|
382
434
|
"memory.list": { family: "memory", scope: "knowledge.read", sideEffecting: false },
|
|
435
|
+
// --- memory.recall (2026-08 REVERSE PARITY, SPEC §9): layered semantic recall
|
|
436
|
+
// over the unified agent memory, with the FULL contract the hq responder's
|
|
437
|
+
// `recall_memory` tool has had since the agent-memory program shipped —
|
|
438
|
+
// scope (org|self|channel) / refType+refId / time range / depth /
|
|
439
|
+
// drill-down pointers. `knowledge.search` is a narrowed projection of this
|
|
440
|
+
// (shared facts only, no controls), so until now an SDK seat reasoned off
|
|
441
|
+
// a strictly smaller memory than the chat lane in the same workspace.
|
|
442
|
+
// ACL is server-side off the paired member — fail-closed, never a param. ---
|
|
443
|
+
"memory.recall": { family: "memory", scope: "knowledge.read", sideEffecting: false },
|
|
444
|
+
// --- preference (2026-08 REVERSE PARITY, SPEC §9): there was NO preference.*
|
|
445
|
+
// family at all. An SDK agent could LEARN a durable member-scoped fact and
|
|
446
|
+
// had nowhere to put it, while the hq responder's `remember_preference`
|
|
447
|
+
// wrote straight into PreferenceMemory — which hq then injects into every
|
|
448
|
+
// future conversation. Same workspace, two memories. `upsert` converges on
|
|
449
|
+
// one row per (subject, domain, key) and REQUIRES evidence; the chain
|
|
450
|
+
// append carries whose/which-key and never the value (a preference value
|
|
451
|
+
// is content). `list` ships with it because a write-only family is not
|
|
452
|
+
// parity: hq SEES injected preferences for free, a seat has to ask. ---
|
|
453
|
+
"preference.upsert": { family: "preference", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
454
|
+
"preference.list": { family: "preference", scope: "org.read", sideEffecting: false },
|
|
383
455
|
// --- compact ---
|
|
384
456
|
"compact.upsert": { family: "compact", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
385
457
|
// --- notification ---
|
|
@@ -775,8 +847,124 @@ export const METHODS = Object.freeze({
|
|
|
775
847
|
"files.comments": { family: "files", scope: "files.read", sideEffecting: false },
|
|
776
848
|
"files.sheetFedRange": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
|
|
777
849
|
"files.ask": { family: "files", scope: "files.read", sideEffecting: false },
|
|
850
|
+
// --- subagent (2026-08): the org-scoped sub-agent registry. NO NEW SCOPES —
|
|
851
|
+
// a sub-agent definition is org-structural authored content exactly like
|
|
852
|
+
// a persona, an SOP or an annotation, which is what `org.write` covers,
|
|
853
|
+
// and both org.read/org.write are already in DEFAULT_AGENT_SCOPES.
|
|
854
|
+
// Curation (`yank`) is admin, like every other destructive verb.
|
|
855
|
+
//
|
|
856
|
+
// `resolve` is the EXPENSIVE read (bodies + provenance + rewire hints);
|
|
857
|
+
// the cheap poll is the `subagent.roster` GET in READS, which returns
|
|
858
|
+
// {slug, definitionId, version, contentHash} and no bodies. Naming a
|
|
859
|
+
// memberId other than the caller's own seat requires `admin` — enforced
|
|
860
|
+
// in-domain (methods/subagent/_shared.ts), the invokeTool doctrine. ---
|
|
861
|
+
"subagent.list": { family: "subagent", scope: "org.read", sideEffecting: false },
|
|
862
|
+
"subagent.get": { family: "subagent", scope: "org.read", sideEffecting: false },
|
|
863
|
+
"subagent.resolve": { family: "subagent", scope: "org.read", sideEffecting: false },
|
|
864
|
+
"subagent.create": { family: "subagent", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
865
|
+
"subagent.publishVersion": { family: "subagent", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
866
|
+
"subagent.fork": { family: "subagent", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
867
|
+
"subagent.pin": { family: "subagent", scope: "org.write", sideEffecting: true, idempotent: true },
|
|
868
|
+
"subagent.unpin": { family: "subagent", scope: "org.write", sideEffecting: true },
|
|
869
|
+
"subagent.yank": { family: "subagent", scope: "admin", sideEffecting: true },
|
|
870
|
+
// --- agent control lane (2026-08): the bounded long-poll a daemon parks on so
|
|
871
|
+
// it learns about its own work in ~0s instead of on a 45s poll cadence.
|
|
872
|
+
// Modelled on the `approval.wait` precedent (READS, a held request), but
|
|
873
|
+
// registered here as a POST method because it takes a CURSOR + a page of
|
|
874
|
+
// structured results, which is the METHODS shape — and because a held GET
|
|
875
|
+
// is far more likely to be cached/coalesced by an intermediary.
|
|
876
|
+
//
|
|
877
|
+
// NO NEW SCOPE. `org.read` is the floor to call it at all (it is in
|
|
878
|
+
// DEFAULT_AGENT_SCOPES, so every paired agent can park), and each LANE it
|
|
879
|
+
// reports on is gated separately in-domain against the key's own scopes
|
|
880
|
+
// (messaging.read / board.read / email.read) — a key minted without
|
|
881
|
+
// email.read is never told that mail arrived. sideEffecting:false: it
|
|
882
|
+
// appends NOTHING to the chain, it only reads the chain by seq. ---
|
|
883
|
+
"agent.wait": { family: "agent", scope: "org.read", sideEffecting: false },
|
|
884
|
+
// --- mandate (2026-08): the MANDATE half of the mandate/plan split. hq is
|
|
885
|
+
// authoritative for WHAT the outcomes are; the agent compiles the plan.
|
|
886
|
+
//
|
|
887
|
+
// The scope split is the design: `mandate.read`/`mandate.propose`/
|
|
888
|
+
// `kpi.write` are in DEFAULT_AGENT_SCOPES, `mandate.write` is not.
|
|
889
|
+
// `adopt`/`retire` are ALSO governanceGated — the decision-rights matrix
|
|
890
|
+
// must exist before a seat can be handed a live, graded objective, the
|
|
891
|
+
// same ordering rule that gates board.assign and handoff.*.
|
|
892
|
+
//
|
|
893
|
+
// `declareCapability`/`publishPlan`/`reportDrift` reuse `registry.write`:
|
|
894
|
+
// they are a seat reporting facts about ITSELF, exactly like
|
|
895
|
+
// registry.register, and every paired agent already holds it. ---
|
|
896
|
+
"mandate.get": { family: "mandate", scope: "mandate.read", sideEffecting: false },
|
|
897
|
+
"mandate.version": { family: "mandate", scope: "mandate.read", sideEffecting: false },
|
|
898
|
+
"mandate.tree": { family: "mandate", scope: "org.read", sideEffecting: false },
|
|
899
|
+
"mandate.series": { family: "mandate", scope: "mandate.read", sideEffecting: false },
|
|
900
|
+
"mandate.propose": { family: "mandate", scope: "mandate.propose", sideEffecting: true, idempotent: true },
|
|
901
|
+
"mandate.adopt": { family: "mandate", scope: "mandate.write", sideEffecting: true, governanceGated: true },
|
|
902
|
+
"mandate.retire": { family: "mandate", scope: "mandate.write", sideEffecting: true, governanceGated: true },
|
|
903
|
+
"mandate.sample": { family: "mandate", scope: "kpi.write", sideEffecting: true, idempotent: true },
|
|
904
|
+
"mandate.declareCapability": { family: "mandate", scope: "registry.write", sideEffecting: true, idempotent: true },
|
|
905
|
+
"mandate.publishPlan": { family: "mandate", scope: "registry.write", sideEffecting: true, idempotent: true },
|
|
906
|
+
"mandate.reportDrift": { family: "mandate", scope: "registry.write", sideEffecting: true },
|
|
907
|
+
// --- board review lane (2026-08 consensus fix): `Task.reviewerId` has existed
|
|
908
|
+
// since T19 and had NO writer on the agent plane, so "send it for review"
|
|
909
|
+
// was unreachable from a daemon and every review round-trip degraded to a
|
|
910
|
+
// comment. These two methods are the writer. ---
|
|
911
|
+
"board.requestReview": { family: "board", scope: "board.write", sideEffecting: true },
|
|
912
|
+
"board.resolveReview": { family: "board", scope: "board.write", sideEffecting: true },
|
|
913
|
+
// --- escalation as a QUESTION (2026-08): `OpenQuestion` shipped with readers
|
|
914
|
+
// and no writer at all. `escalation.ask` is its first one. The difference
|
|
915
|
+
// from `escalation.create` matters: an Escalation is "a human is needed
|
|
916
|
+
// here"; an OpenQuestion is "here are 2-3 concrete options, pick one",
|
|
917
|
+
// which is what a Charter.escalationTriggers hit should actually produce. ---
|
|
918
|
+
// NOTE: `escalation.ask` / `escalation.answer` (raise an OpenQuestion with
|
|
919
|
+
// concrete options, and the human picking one) are deliberately NOT registered
|
|
920
|
+
// yet — `ask` needs a `_question` projector that is not written. A METHODS
|
|
921
|
+
// entry with no working handler is not a placeholder, it is a live promise the
|
|
922
|
+
// dispatcher cannot keep: the protocol advertises the method, the caller gets
|
|
923
|
+
// through scope resolution, and the call 500s. Register each in the same
|
|
924
|
+
// change that lands its handler.
|
|
925
|
+
// NOTE: `escalation.answer` (the human picking one of the offered options) is
|
|
926
|
+
// deliberately NOT registered yet — its handler is not written. A METHODS
|
|
927
|
+
// entry with no handler file is not a placeholder, it is a live promise the
|
|
928
|
+
// dispatcher cannot keep: the protocol advertises the method, callers get
|
|
929
|
+
// through scope resolution, and the call 500s. Register it in the same change
|
|
930
|
+
// that adds src/server/methods/escalation/answer.ts.
|
|
778
931
|
});
|
|
779
932
|
|
|
933
|
+
/**
|
|
934
|
+
* The ONE action-class table (SPEC §8 "Approvals"). Replaces maestro's local
|
|
935
|
+
* `GATED_CLASSES` and hq's free-form `Approval.actionClass` strings with a
|
|
936
|
+
* shared vocabulary both sides validate against.
|
|
937
|
+
*
|
|
938
|
+
* `GATED_ACTION_CLASSES` is the blast-radius gate: an action in one of these
|
|
939
|
+
* classes forces an approval BEFORE any execution rung runs, at every rung.
|
|
940
|
+
* @type {readonly string[]}
|
|
941
|
+
*/
|
|
942
|
+
export const ACTION_CLASSES = Object.freeze([
|
|
943
|
+
"internal", // stays inside the workspace; reversible; no spend
|
|
944
|
+
"external", // leaves the org (email to a third party, a public post)
|
|
945
|
+
"irreversible", // cannot be undone by a subsequent method call
|
|
946
|
+
"financial", // moves money or commits spend
|
|
947
|
+
]);
|
|
948
|
+
|
|
949
|
+
/** Action classes that force a human approval before execution. */
|
|
950
|
+
export const GATED_ACTION_CLASSES = Object.freeze(["external", "irreversible", "financial"]);
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* PlanDrift kinds — the reconciler's vocabulary for "the plan and the world
|
|
954
|
+
* disagree". Shared so hq can index/report on them without string drift.
|
|
955
|
+
* @type {readonly string[]}
|
|
956
|
+
*/
|
|
957
|
+
export const DRIFT_KINDS = Object.freeze([
|
|
958
|
+
"uncovered_event", // a directed event matched no REACT obligation
|
|
959
|
+
"unreachable_capability", // an obligation cites a capability that failed its probe
|
|
960
|
+
"stale_sensor", // no sample within 2x the objective's cadence
|
|
961
|
+
"schedule_missing", // a SCHEDULE obligation has no launchd trigger on disk
|
|
962
|
+
"kpi_gap", // measured value is outside tolerance of target
|
|
963
|
+
"budget_breach", // an obligation exhausted its envelope
|
|
964
|
+
"orphan_work", // open work with no objective behind it
|
|
965
|
+
"mandate_stale", // the cached mandate aged past the staleness ladder
|
|
966
|
+
]);
|
|
967
|
+
|
|
780
968
|
/**
|
|
781
969
|
* Is a method refused until the org server is `governance_ready` (decision_rights
|
|
782
970
|
* populated AND signing live)? A partially deployed control plane is worse than
|
|
@@ -806,6 +994,12 @@ export const READS = Object.freeze({
|
|
|
806
994
|
// phase 4 reads
|
|
807
995
|
"contacts.list": "knowledge.read", // org contacts registry (ACL-filtered)
|
|
808
996
|
"meetings.list": "knowledge.read", // org meetings registry (ACL-filtered)
|
|
997
|
+
// sub-agent registry (2026-08): the CHEAP roster poll. Returns the bare
|
|
998
|
+
// {rosterVersion, entries:[{slug, definitionId, version, contentHash}]} — no
|
|
999
|
+
// bodies — so a seat can diff its `.maestro/subagents.lock.json` on every
|
|
1000
|
+
// beat without paying for prose. `subagent.resolve` (METHODS) is the
|
|
1001
|
+
// expensive twin that carries bodies.
|
|
1002
|
+
"subagent.roster": "org.read",
|
|
809
1003
|
});
|
|
810
1004
|
|
|
811
1005
|
/** Error codes (the error contract; HTTP status hints are advisory). */
|
|
@@ -822,8 +1016,26 @@ export const ERROR_CODES = Object.freeze({
|
|
|
822
1016
|
INTERNAL: { http: 500 },
|
|
823
1017
|
});
|
|
824
1018
|
|
|
825
|
-
/**
|
|
826
|
-
|
|
1019
|
+
/**
|
|
1020
|
+
* Directive kinds the presence.beat response may carry (kill-switch + nudges).
|
|
1021
|
+
*
|
|
1022
|
+
* Levels 1-3 of the four-level kill-switch (SPEC §8) live here and are
|
|
1023
|
+
* COOPERATIVE: they only work while the daemon is healthy enough to beat and
|
|
1024
|
+
* honest enough to obey. Level 4 — `credential.revoke` + API-key disable — is
|
|
1025
|
+
* the only control that works against a wedged or compromised laptop. Say that
|
|
1026
|
+
* out loud rather than implying the beat is a hard stop.
|
|
1027
|
+
*/
|
|
1028
|
+
export const DIRECTIVES = Object.freeze([
|
|
1029
|
+
"halt", // stop entirely. hq ALSO closes the SSE lane (`event: bye`) so a
|
|
1030
|
+
// daemon that stopped beating stops receiving work to do.
|
|
1031
|
+
"rotate_token",
|
|
1032
|
+
"policy_stale",
|
|
1033
|
+
"resync",
|
|
1034
|
+
// --- mandate spine (2026-08) ---
|
|
1035
|
+
"pause_schedules", // stop INITIATING (Loop B + Loop C); keep answering when addressed
|
|
1036
|
+
"plan_stale", // the seat's mandate moved — refetch `mandate.get` and recompile
|
|
1037
|
+
"revoke_scope", // a scope was withdrawn; drop it locally and re-probe capabilities
|
|
1038
|
+
]);
|
|
827
1039
|
|
|
828
1040
|
// ---------------------------------------------------------------------------
|
|
829
1041
|
// Helpers (shared, pure)
|
|
@@ -131,7 +131,10 @@ test("protocol: frozen family + method counts (additive evolution guard)", () =>
|
|
|
131
131
|
// from the UI-parity "file" pins/comments family), and directory (external
|
|
132
132
|
// people + organisations; the Design half of that program rides the existing
|
|
133
133
|
// `branding` family — the section is renamed, the wire name is not).
|
|
134
|
-
|
|
134
|
+
// The 2026-08 protocol-convergence pass adds 4: subagent (the org-scoped
|
|
135
|
+
// sub-agent registry), agent (the agent.wait control lane), mandate (the
|
|
136
|
+
// MANDATE spine) and preference (member-scoped preferences) → 51.
|
|
137
|
+
assert.equal(FAMILIES.length, 51, "family count");
|
|
135
138
|
// 357 = the mesh-protocol + agent UI-parity + calling + branding + email
|
|
136
139
|
// methods, plus the Live Integrations surface: integration.toolsetVersion +
|
|
137
140
|
// integration.listAgentTools (SP1) and integration.invokeTool (SP5, execute a
|
|
@@ -160,8 +163,14 @@ test("protocol: frozen family + method counts (additive evolution guard)", () =>
|
|
|
160
163
|
// "is composing…" indicator an SDK-driven agent raises while it works —
|
|
161
164
|
// sideEffecting:false, no chain event, expires client-side) → 431.
|
|
162
165
|
// Bumped deliberately alongside
|
|
166
|
+
// The 2026-08 protocol-convergence pass closes the 29-method gap against hq's
|
|
167
|
+
// vendored table (the client rejected every one of them BEFORE the network,
|
|
168
|
+
// so mandate.* et al were unreachable from a daemon): messaging.search,
|
|
169
|
+
// escalation.ask/answer, memory.recall, preference.upsert/list (6 reverse-
|
|
170
|
+
// parity methods), the 9 subagent.* registry methods, agent.wait, the 11
|
|
171
|
+
// mandate.* spine methods, and board.requestReview/resolveReview → 460.
|
|
163
172
|
// the descriptors; the checksum (read at runtime) is the primary drift guard.
|
|
164
|
-
assert.equal(Object.keys(METHODS).length,
|
|
173
|
+
assert.equal(Object.keys(METHODS).length, 460, "method count");
|
|
165
174
|
});
|
|
166
175
|
|
|
167
176
|
test("protocol SP3: messaging + calling families/methods/scopes", async () => {
|