brainclaw 1.22.0 → 1.24.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/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-capture.js +15 -0
- package/dist/cli/register-cloud.js +121 -13
- package/dist/commands/cloud.js +534 -39
- package/dist/commands/loops-handlers.js +0 -1
- package/dist/commands/mcp-catalog.js +24 -256
- package/dist/commands/mcp-read-handlers.js +5 -1
- package/dist/commands/mcp-schemas.generated.js +811 -1
- package/dist/commands/mcp-write-coordination.js +16 -7
- package/dist/commands/mcp.js +45 -1
- package/dist/commands/memory-confirm.js +83 -0
- package/dist/commands/switch.js +24 -2
- package/dist/core/assignment-request-schema.js +112 -0
- package/dist/core/capture-schema.js +62 -0
- package/dist/core/claim-request-schema.js +72 -0
- package/dist/core/code-map/aggregate.js +36 -1
- package/dist/core/federation-emit.js +283 -0
- package/dist/core/federation-grant-transport.js +196 -0
- package/dist/core/federation-grant.js +223 -0
- package/dist/core/federation-keyring.js +39 -0
- package/dist/core/federation-opaque-ids.js +111 -0
- package/dist/core/federation-outbox-v2.js +36 -2
- package/dist/core/federation-pairing.js +87 -12
- package/dist/core/federation-pull.js +375 -0
- package/dist/core/federation-push.js +274 -0
- package/dist/core/federation-rotation.js +124 -0
- package/dist/core/federation-state.js +81 -6
- package/dist/core/sequence-request-schema.js +93 -0
- package/dist/core/session-request-schema.js +90 -0
- package/dist/core/step-request-schema.js +112 -0
- package/dist/core/store-resolution.js +34 -5
- package/dist/core/warnings.js +37 -0
- package/dist/facts.js +7 -7
- package/dist/facts.json +6 -6
- package/docs/design/federation-onboarding-usecases.md +254 -0
- package/docs/design/pairing-v3-brief.md +80 -0
- package/docs/integrations/mcp.md +1 -1
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ import { validateMcpField } from '../core/input-validation.js';
|
|
|
22
22
|
import { generateCandidateIdWithLabel, saveCandidate } from '../core/candidates.js';
|
|
23
23
|
import { validateLoopProjectResolution } from '../core/loops/project-resolution.js';
|
|
24
24
|
import { coordinateNextActions, dispatchNextActions } from '../core/next-actions.js';
|
|
25
|
-
import { agentValidationFailedWarning, planAlreadyAssignedWarning, pushStructuredWarning, scopeAlreadyClaimedWarning, } from '../core/warnings.js';
|
|
25
|
+
import { agentValidationFailedWarning, consultAutoExecuteNoOpWarning, planAlreadyAssignedWarning, pushStructuredWarning, scopeAlreadyClaimedWarning, } from '../core/warnings.js';
|
|
26
26
|
import { ackMessage, getThread, hasActiveAssignment, sendMessage } from '../core/messaging.js';
|
|
27
27
|
import { dispatch, dispatchReview, generateDispatchBrief } from '../core/dispatcher.js';
|
|
28
28
|
import { CoordinateRequestSchema } from '../core/facade-schema.js';
|
|
@@ -847,7 +847,10 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
847
847
|
// the target inbox(es) and never spawns. autoExecute is a no-op here, so
|
|
848
848
|
// say so explicitly rather than silently ignoring a caller who set it.
|
|
849
849
|
if (req.autoExecute === true) {
|
|
850
|
-
|
|
850
|
+
// pln#626 phase 3 — le refus passe desormais par le canal STRUCTURE. Un texte libre
|
|
851
|
+
// vaut mieux que le silence, mais un agent ne peut pas brancher dessus ; un code
|
|
852
|
+
// le peut, et la next_action nomme les deux chemins qui spawnent vraiment.
|
|
853
|
+
pushStructuredWarning(warnings, warningDetails, consultAutoExecuteNoOpWarning());
|
|
851
854
|
}
|
|
852
855
|
const consultThreadId = req.threadId ?? `thread_${crypto.randomBytes(4).toString('hex')}`;
|
|
853
856
|
const contacted = [];
|
|
@@ -1972,11 +1975,17 @@ export async function handleBclawLoop(args, ctx) {
|
|
|
1972
1975
|
response: createToolErrorResponse('intent_not_exposed', "bclaw_loop(intent='open') is not exposed standalone: it creates a loop structure without dispatching any turn, so the work never starts. Use bclaw_coordinate(intent='review', open_loop=true, targetAgents=[…]) or bclaw_coordinate(intent='ideate') — they open the loop AND dispatch the first turn."),
|
|
1973
1976
|
};
|
|
1974
1977
|
}
|
|
1975
|
-
// pln#
|
|
1976
|
-
//
|
|
1977
|
-
//
|
|
1978
|
-
//
|
|
1979
|
-
|
|
1978
|
+
// pln#632 — `bind` SPAWNE de vrais workers (il dispatche la séquence liée de la
|
|
1979
|
+
// boucle), donc il est protégé au barreau 'trusted' comme les autres surfaces de
|
|
1980
|
+
// dispatch.
|
|
1981
|
+
//
|
|
1982
|
+
// LA BRANCHE `turn && dispatch === true` A ÉTÉ RETIRÉE (pln#626 phase 4). Le drapeau
|
|
1983
|
+
// `TurnInput.dispatch` était déclaré, transporté jusqu'à `turn()` — et JAMAIS LU. Une
|
|
1984
|
+
// porte de confiance sur un no-op est pire qu'absente : elle fait croire qu'un chemin
|
|
1985
|
+
// sensible est gardé, et un lecteur qui la voit conclut à tort que `dispatch: true` a
|
|
1986
|
+
// un effet. Le drapeau lui-même est supprimé dans le même commit ; garder la porte
|
|
1987
|
+
// aurait laissé la fausse impression intacte.
|
|
1988
|
+
if (args?.intent === 'bind') {
|
|
1980
1989
|
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', cwd, connectionSessionId);
|
|
1981
1990
|
if (resolved.error) {
|
|
1982
1991
|
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1026,6 +1026,50 @@ function resolveWorkspaceAnchor(cwd) {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
// Read handlers moved to mcp-read-handlers.ts
|
|
1028
1028
|
import { handleMcpReadToolCall } from './mcp-read-handlers.js';
|
|
1029
|
+
/**
|
|
1030
|
+
* Projection COMPACTE de `open_work` (pln#598 etape 2).
|
|
1031
|
+
*
|
|
1032
|
+
* POURQUOI TRONQUER LA DESCRIPTION, et pourquoi ICI seulement. Une description
|
|
1033
|
+
* d'assignation porte le BRIEF COMPLET du worker : mesure sur ce depot, des briefs de
|
|
1034
|
+
* dispatch a plus de 1 500 caracteres, plusieurs a la fois dans open_work. Servis en
|
|
1035
|
+
* entier dans une reponse dite « compacte », ils en font l'essentiel du poids — pour un
|
|
1036
|
+
* texte que l'appelant a ecrit lui-meme et peut relire a la demande.
|
|
1037
|
+
*
|
|
1038
|
+
* La troncature vit dans la BRANCHE COMPACTE et non dans context.ts : les autres
|
|
1039
|
+
* consommateurs de `open_work` — session-end, le board, les surfaces de diagnostic —
|
|
1040
|
+
* ont besoin du texte entier, et le tronquer a la source leur retirerait sans le dire.
|
|
1041
|
+
*
|
|
1042
|
+
* LE TEXTE N'EST PAS PERDU, il est DIFFERE : chaque entree tronquee porte de quoi le
|
|
1043
|
+
* recuperer. Un allegement qui supprime l'information au lieu de la deplacer n'allege
|
|
1044
|
+
* rien — il deplace le probleme sur l'appelant, qui devine au lieu de lire.
|
|
1045
|
+
*/
|
|
1046
|
+
const OPEN_WORK_DESCRIPTION_LIMIT = 200;
|
|
1047
|
+
function compactOpenWork(openWork) {
|
|
1048
|
+
if (!openWork || typeof openWork !== 'object')
|
|
1049
|
+
return openWork ?? null;
|
|
1050
|
+
const source = openWork;
|
|
1051
|
+
if (!Array.isArray(source.active_assignments))
|
|
1052
|
+
return openWork;
|
|
1053
|
+
return {
|
|
1054
|
+
...source,
|
|
1055
|
+
active_assignments: source.active_assignments.map((assignment) => {
|
|
1056
|
+
const description = assignment['description'];
|
|
1057
|
+
if (typeof description !== 'string' || description.length <= OPEN_WORK_DESCRIPTION_LIMIT) {
|
|
1058
|
+
return assignment;
|
|
1059
|
+
}
|
|
1060
|
+
return {
|
|
1061
|
+
...assignment,
|
|
1062
|
+
description: `${description.slice(0, OPEN_WORK_DESCRIPTION_LIMIT)}…`,
|
|
1063
|
+
description_truncated: true,
|
|
1064
|
+
// La suite EXACTE, pas une invitation vague : l'agent doit pouvoir la rejouer
|
|
1065
|
+
// telle quelle. C'est la regle de src/core/next-actions.ts — une action qui ne
|
|
1066
|
+
// decoule pas de ce qui s'est passe est du bruit ; celle-ci decoule de la
|
|
1067
|
+
// troncature qu'on vient d'appliquer.
|
|
1068
|
+
full_text_via: { tool: 'bclaw_get', args: { entity: 'assignment', id: assignment['id'] } },
|
|
1069
|
+
};
|
|
1070
|
+
}),
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1029
1073
|
export { handleMcpReadToolCall };
|
|
1030
1074
|
async function _executeMcpToolCallInner(payload) {
|
|
1031
1075
|
const { name, args, cwd, connectionSessionId } = payload;
|
|
@@ -1430,7 +1474,7 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
1430
1474
|
workflow_hints: hintsPool.slice(0, 3),
|
|
1431
1475
|
...(hintsAggregate ? { workflow_hints_aggregate: hintsAggregate } : {}),
|
|
1432
1476
|
claim_conflicts: contextResult.claim_conflicts ?? [],
|
|
1433
|
-
open_work: contextResult.open_work
|
|
1477
|
+
open_work: compactOpenWork(contextResult.open_work),
|
|
1434
1478
|
_compact: true,
|
|
1435
1479
|
_full_context_hint: 'Use bclaw_context(kind="memory") for the full payload.',
|
|
1436
1480
|
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `brainclaw confirm` — attester ou infirmer l'applicabilité d'un item de mémoire
|
|
3
|
+
* (pln#620 étapes 2 et 3).
|
|
4
|
+
*
|
|
5
|
+
* ── POURQUOI CETTE COMMANDE EXISTE ────────────────────────────────────────────
|
|
6
|
+
* `recordMemoryEvent` était déjà écrit, testé, et branché sur un schéma
|
|
7
|
+
* (`MemoryConfirmationEvent`) porté par les traps, décisions et contraintes. Il n'était
|
|
8
|
+
* appelé DEPUIS NULLE PART. Mesuré au moment du correctif : 0 item sur 471 portait la
|
|
9
|
+
* moindre confirmation.
|
|
10
|
+
*
|
|
11
|
+
* C'est exactement la classe de défaut que trp#1292 décrit — un cœur vert et une
|
|
12
|
+
* fonctionnalité inerte, parce que rien ne la tire depuis une surface qu'un agent ou un
|
|
13
|
+
* opérateur appelle réellement.
|
|
14
|
+
*
|
|
15
|
+
* ── CE QUE ÇA CHANGE POUR LA PRIORISATION ─────────────────────────────────────
|
|
16
|
+
* Une mémoire non vérifiée ne peut pas justifier une priorité P0. Un trap écrit en mars,
|
|
17
|
+
* jamais reconfirmé, décrit peut-être un code qui n'existe plus — la démolition de la
|
|
18
|
+
* fédération v1 en a périmé plusieurs d'un coup. Sans trace d'applicabilité, on ne peut
|
|
19
|
+
* pas distinguer « toujours vrai » de « personne n'a revérifié depuis six mois ».
|
|
20
|
+
*
|
|
21
|
+
* L'ÉVIDENCE EST OBLIGATOIRE POUR CONFIRMER, et c'est le cœur du dispositif. Une
|
|
22
|
+
* confirmation sans preuve — un fichier:ligne, un sha, une sortie de commande — ne serait
|
|
23
|
+
* qu'une opinion horodatée, et deux opinions ne valent pas mieux qu'une. Infirmer, en
|
|
24
|
+
* revanche, ne l'exige pas : constater qu'un symbole a disparu est en soi la preuve.
|
|
25
|
+
*/
|
|
26
|
+
import { recordMemoryEvent } from '../core/memory-lifecycle.js';
|
|
27
|
+
import { resolveEffectiveCwd } from '../core/store-resolution.js';
|
|
28
|
+
import { resolveCurrentAgentName } from '../core/agent-registry.js';
|
|
29
|
+
const ENTITIES = ['trap', 'decision', 'constraint'];
|
|
30
|
+
const KINDS = ['confirm', 'infirm', 'saved_me', 'misled_me'];
|
|
31
|
+
export function runMemoryConfirm(options) {
|
|
32
|
+
const entity = options.entity;
|
|
33
|
+
if (!ENTITIES.includes(entity)) {
|
|
34
|
+
console.error(`Entité inconnue '${options.entity}'. Attendu : ${ENTITIES.join(', ')}.`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const kind = options.kind;
|
|
39
|
+
if (!KINDS.includes(kind)) {
|
|
40
|
+
console.error(`Type inconnu '${options.kind}'. Attendu : ${KINDS.join(', ')}.`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// CONFIRMER SANS PREUVE EST REFUSÉ. Le but de ce dispositif est qu'une priorité puisse
|
|
45
|
+
// s'appuyer sur une vérification ; une attestation sans pointeur vers ce qui a été
|
|
46
|
+
// vérifié ne porte aucune information de plus que la date.
|
|
47
|
+
if (kind === 'confirm' && !options.evidence) {
|
|
48
|
+
console.error("Une confirmation exige --evidence : un fichier:ligne, un sha de commit, ou une sortie de commande.\n"
|
|
49
|
+
+ "Sans preuve, l'attestation n'est qu'une opinion horodatée et ne peut pas justifier une priorité.");
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const cwd = options.cwd ?? resolveEffectiveCwd();
|
|
54
|
+
const by = resolveCurrentAgentName(cwd);
|
|
55
|
+
let result;
|
|
56
|
+
try {
|
|
57
|
+
result = recordMemoryEvent({
|
|
58
|
+
entity, id: options.id, kind, by,
|
|
59
|
+
evidence: options.evidence,
|
|
60
|
+
note: options.note,
|
|
61
|
+
cwd,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (options.json) {
|
|
70
|
+
console.log(JSON.stringify(result, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
console.log(`✔ ${result.entity} ${result.id} — ${result.kind} par ${by}`);
|
|
74
|
+
if (options.evidence)
|
|
75
|
+
console.log(` preuve : ${options.evidence}`);
|
|
76
|
+
console.log(` confirmations : ${result.confirmation_count} · infirmations : ${result.infirmation_count}`
|
|
77
|
+
+ ` · a servi : ${result.saved_me_count} · a induit en erreur : ${result.misled_me_count}`);
|
|
78
|
+
if (result.last_confirmed_at)
|
|
79
|
+
console.log(` dernière confirmation : ${result.last_confirmed_at}`);
|
|
80
|
+
if (result.last_infirmed_at)
|
|
81
|
+
console.log(` dernière infirmation : ${result.last_infirmed_at}`);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=memory-confirm.js.map
|
package/dist/commands/switch.js
CHANGED
|
@@ -116,6 +116,7 @@ export function listAvailableProjectsForSession(cwd, sessionId) {
|
|
|
116
116
|
: undefined;
|
|
117
117
|
const projects = [];
|
|
118
118
|
const seen = new Set();
|
|
119
|
+
const sessionDivergence = effective.session_divergence;
|
|
119
120
|
const addProject = (project) => {
|
|
120
121
|
const projectPath = path.resolve(project.path);
|
|
121
122
|
if (seen.has(projectPath))
|
|
@@ -154,7 +155,12 @@ export function listAvailableProjectsForSession(cwd, sessionId) {
|
|
|
154
155
|
relative_path: path.relative(wsRoot, linkPath) || '.',
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
workspace_root: wsRoot,
|
|
160
|
+
active_source: activeSource,
|
|
161
|
+
projects,
|
|
162
|
+
...(sessionDivergence ? { session_divergence: sessionDivergence } : {}),
|
|
163
|
+
};
|
|
158
164
|
}
|
|
159
165
|
export function runSwitch(projectRef, options = {}) {
|
|
160
166
|
// Use real cwd, not effective cwd — switch must see the full workspace
|
|
@@ -308,8 +314,12 @@ function showCurrent(wsRoot, cwd, json) {
|
|
|
308
314
|
}
|
|
309
315
|
const rel = path.relative(wsRoot, active.path) || '.';
|
|
310
316
|
const switchedBy = 'switched_by' in active ? active.switched_by : undefined;
|
|
317
|
+
const divergence = effective.session_divergence;
|
|
311
318
|
if (json) {
|
|
312
|
-
console.log(JSON.stringify({
|
|
319
|
+
console.log(JSON.stringify({
|
|
320
|
+
active: true, ...active, relative_path: rel, scope: source,
|
|
321
|
+
...(divergence ? { session_divergence: divergence } : {}),
|
|
322
|
+
}));
|
|
313
323
|
}
|
|
314
324
|
else {
|
|
315
325
|
const scopeHint = source === 'session' ? ' (session-scoped)' : ' (global — all agents)';
|
|
@@ -317,6 +327,17 @@ function showCurrent(wsRoot, cwd, json) {
|
|
|
317
327
|
console.log(` switched at: ${active.switched_at}`);
|
|
318
328
|
if (switchedBy)
|
|
319
329
|
console.log(` switched by: ${switchedBy}`);
|
|
330
|
+
if (divergence) {
|
|
331
|
+
// Le defaut d'origine n'etait pas qu'un lecteur se trompait : c'est que deux
|
|
332
|
+
// lecteurs pouvaient diverger EN SILENCE. Le dire est tout l'objet de ce bloc.
|
|
333
|
+
const label = divergence.session_project_name
|
|
334
|
+
? `"${divergence.session_project_name}"`
|
|
335
|
+
: divergence.session_project_path;
|
|
336
|
+
console.log('');
|
|
337
|
+
console.log(` ⚠ Divergence : un record de session designe ${label},`);
|
|
338
|
+
console.log(` mais la resolution a retenu ce projet via '${divergence.resolved_via}'.`);
|
|
339
|
+
console.log(` Les ecritures suivent la resolution, pas la session.`);
|
|
340
|
+
}
|
|
320
341
|
}
|
|
321
342
|
}
|
|
322
343
|
function listProjects(wsRoot, cwd, json) {
|
|
@@ -328,6 +349,7 @@ function listProjects(wsRoot, cwd, json) {
|
|
|
328
349
|
workspace: result.workspace_root,
|
|
329
350
|
active_source: result.active_source,
|
|
330
351
|
projects: result.projects,
|
|
352
|
+
...(result.session_divergence ? { session_divergence: result.session_divergence } : {}),
|
|
331
353
|
}, null, 2));
|
|
332
354
|
return;
|
|
333
355
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schémas zod des entrées de la famille ASSIGNMENT — `bclaw_assignment_update`,
|
|
3
|
+
* `bclaw_assignment_action`, `bclaw_assignment_events` (pln#599 batch 2, cinquième famille).
|
|
4
|
+
*
|
|
5
|
+
* ── LA FAMILLE LA PLUS PROFONDE MIGRÉE JUSQU'ICI ──────────────────────────────
|
|
6
|
+
* `assignment_update` porte deux objets imbriqués qui ont LEURS PROPRES `required` :
|
|
7
|
+
* l'item d'`artifacts` (`type`+`ref`) et `action_required` (`kind`+`title`+`prompt`).
|
|
8
|
+
* Ces requis internes font partie du contrat publié et doivent survivre à la migration —
|
|
9
|
+
* c'est le pendant profond du piège de la famille séquence, où un requis de premier niveau
|
|
10
|
+
* était passé optionnel sans que rien d'autre que le fingerprint ne le voie.
|
|
11
|
+
*
|
|
12
|
+
* ── DEUX OBJETS DÉLIBÉRÉMENT LIBRES ───────────────────────────────────────────
|
|
13
|
+
* `payload` et `response_schema` sont publiés comme `{ type: 'object' }` NU : aucune
|
|
14
|
+
* propriété, aucune contrainte. Ce sont des sacs de données dont la forme appartient à
|
|
15
|
+
* l'appelant.
|
|
16
|
+
*
|
|
17
|
+
* Les quatre constructions candidates ont été MESURÉES, pas supposées :
|
|
18
|
+
* z.object({}) -> properties:{} + additionalProperties:false
|
|
19
|
+
* (un objet qui n'accepte plus RIEN — le
|
|
20
|
+
* durcissement le plus radical possible, sur les
|
|
21
|
+
* deux champs les plus ouverts de la famille)
|
|
22
|
+
* z.looseObject({}) -> properties:{} + additionalProperties:{}
|
|
23
|
+
* z.record(z.string(), z.unknown()) -> + propertyNames + additionalProperties
|
|
24
|
+
* z.unknown().meta({ type:'object' }) -> { type: 'object' } <- seul exact
|
|
25
|
+
* La comparaison à la version manuelle a rejeté les trois premiers.
|
|
26
|
+
*
|
|
27
|
+
* ── CE QUI GARDE SON ENUM, ET POURQUOI ────────────────────────────────────────
|
|
28
|
+
* `status`, `outcome` et `action_required.kind` en avaient déjà un dans la version
|
|
29
|
+
* manuelle : le leur retirer serait l'assouplissement symétrique du durcissement qu'on
|
|
30
|
+
* évite ailleurs. En revanche `artifacts[].type` et `eventType` restent des chaînes
|
|
31
|
+
* libres bien que leurs valeurs soient énumérées en description — les resserrer serait un
|
|
32
|
+
* rejet nouveau.
|
|
33
|
+
*/
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
/** Identité de l'appelant — commune à toutes les familles migrées. */
|
|
36
|
+
const CallerIdentity = {
|
|
37
|
+
agent: z.string().describe('Agent name.').optional(),
|
|
38
|
+
agentId: z.string().describe('Registered agent id.').optional(),
|
|
39
|
+
};
|
|
40
|
+
/** Item d'artefact. `type` et `ref` sont REQUIS — requis IMBRIQUÉ, à préserver. */
|
|
41
|
+
const ArtifactSchema = z.object({
|
|
42
|
+
type: z.string().describe('Artifact type: commit, branch, file, pr, test_result.'),
|
|
43
|
+
ref: z.string().describe('Reference: SHA, branch name, file path, PR URL.'),
|
|
44
|
+
description: z.string().describe('Optional description.').optional(),
|
|
45
|
+
});
|
|
46
|
+
/** ActionRequired. `kind`+`title`+`prompt` REQUIS — second requis imbriqué. */
|
|
47
|
+
const ActionRequiredSchema = z
|
|
48
|
+
.object({
|
|
49
|
+
kind: z
|
|
50
|
+
.enum(['approval', 'user_input', 'clarification', 'plan_approval'])
|
|
51
|
+
.describe('Kind of action needed.'),
|
|
52
|
+
title: z.string().describe('Short title shown to supervisors/UI.'),
|
|
53
|
+
prompt: z.string().describe('Question or approval prompt to answer.'),
|
|
54
|
+
options: z.array(z.string()).describe('Optional answer choices.').optional(),
|
|
55
|
+
// Objet NU. Mesure des candidats plutôt que supposition :
|
|
56
|
+
// z.object({}) -> properties:{} + additionalProperties:false (n'accepte RIEN)
|
|
57
|
+
// z.looseObject({}) -> properties:{} + additionalProperties:{} (accepte, mais ajoute
|
|
58
|
+
// deux clés que la version publiée n'avait pas)
|
|
59
|
+
// z.record(...) -> ajoute propertyNames + additionalProperties
|
|
60
|
+
// z.unknown().meta({ type: 'object' }) -> { type: 'object' } exactement.
|
|
61
|
+
// Seul le dernier reproduit la surface manuelle.
|
|
62
|
+
response_schema: z
|
|
63
|
+
.unknown()
|
|
64
|
+
.meta({ type: 'object' })
|
|
65
|
+
.describe('Optional structured response schema hint.')
|
|
66
|
+
.optional(),
|
|
67
|
+
tags: z.array(z.string()).describe('Optional tags.').optional(),
|
|
68
|
+
})
|
|
69
|
+
.describe('Optional ActionRequired payload when status=blocked. Lets the worker request approval, user input, or clarification before resuming.');
|
|
70
|
+
export const AssignmentUpdateRequestSchema = z.object({
|
|
71
|
+
assignment_id: z.string().describe('Assignment ID from the dispatch brief (asgn_xxx).'),
|
|
72
|
+
status: z
|
|
73
|
+
.enum(['accepted', 'started', 'progress', 'completed', 'failed', 'blocked'])
|
|
74
|
+
.describe('Lifecycle status to report.'),
|
|
75
|
+
message: z.string().describe('Human-readable status message or progress note.').optional(),
|
|
76
|
+
artifacts: z
|
|
77
|
+
.array(ArtifactSchema)
|
|
78
|
+
.describe('Artifacts produced. Most useful for completed status.')
|
|
79
|
+
.optional(),
|
|
80
|
+
error_message: z.string().describe('Error details (for failed status).').optional(),
|
|
81
|
+
blocker: z.string().describe('Blocker description (for blocked status).').optional(),
|
|
82
|
+
action_required: ActionRequiredSchema.optional(),
|
|
83
|
+
...CallerIdentity,
|
|
84
|
+
});
|
|
85
|
+
export const AssignmentActionRequestSchema = z.object({
|
|
86
|
+
action_id: z.string().describe('ActionRequired ID (act_xxx).'),
|
|
87
|
+
outcome: z
|
|
88
|
+
.enum(['resolved', 'rejected', 'cancelled'])
|
|
89
|
+
.describe('How the supervisor resolves the pending action.'),
|
|
90
|
+
text: z.string().describe('Human-readable response or rationale.').optional(),
|
|
91
|
+
// Objet NU, même raison et même construction que response_schema.
|
|
92
|
+
payload: z
|
|
93
|
+
.unknown()
|
|
94
|
+
.meta({ type: 'object' })
|
|
95
|
+
.describe('Optional structured response payload.')
|
|
96
|
+
.optional(),
|
|
97
|
+
agent: z.string().describe('Supervisor/agent responding to the action.').optional(),
|
|
98
|
+
agentId: z.string().describe('Registered agent id.').optional(),
|
|
99
|
+
});
|
|
100
|
+
export const AssignmentEventsRequestSchema = z.object({
|
|
101
|
+
assignmentId: z.string().describe('Filter by linked assignment ID.').optional(),
|
|
102
|
+
runId: z.string().describe('Filter by linked run ID.').optional(),
|
|
103
|
+
claimId: z.string().describe('Filter by linked claim ID.').optional(),
|
|
104
|
+
sessionId: z.string().describe('Filter by runtime session ID.').optional(),
|
|
105
|
+
agent: z.string().describe('Filter by agent name.').optional(),
|
|
106
|
+
eventType: z.string().describe('Filter by runtime event type.').optional(),
|
|
107
|
+
id: z.string().describe('Get a single runtime event by ID.').optional(),
|
|
108
|
+
limit: z.number().describe('Maximum number of events to return (default: 20).').optional(),
|
|
109
|
+
offset: z.number().describe('Number of events to skip (for pagination).').optional(),
|
|
110
|
+
compact: z.boolean().describe('Return only key fields to reduce output size.').optional(),
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=assignment-request-schema.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schémas zod des entrées de la famille CAPTURE — `bclaw_write_note` et
|
|
3
|
+
* `bclaw_quick_capture` (pln#599 batch 1, première famille).
|
|
4
|
+
*
|
|
5
|
+
* ── POURQUOI UNE FAMILLE ENTIÈRE, ET PAS TROIS OUTILS AU HASARD ───────────────
|
|
6
|
+
* Une source unique de vérité n'en est une que si elle couvre un ensemble COHÉRENT. Un
|
|
7
|
+
* catalogue à moitié dérivé double la maintenance — deux mécanismes à comprendre, deux
|
|
8
|
+
* endroits où corriger — sans donner le bénéfice, qui est de ne plus pouvoir faire
|
|
9
|
+
* diverger le schéma publié de la validation réelle. Le découpage par famille garantit
|
|
10
|
+
* qu'aucune surface ne reste à cheval sur les deux.
|
|
11
|
+
*
|
|
12
|
+
* ── LA CONTRAINTE QUI GOUVERNE CE FICHIER : FINGERPRINT INCHANGÉ ──────────────
|
|
13
|
+
* Le JSON Schema dérivé de ces objets doit être IDENTIQUE à celui écrit à la main qu'il
|
|
14
|
+
* remplace. Un champ optionnel devenu requis, un `enum` perdu, une `description`
|
|
15
|
+
* reformulée : chacun est une modification de la surface publique que des agents ont déjà
|
|
16
|
+
* apprise. La migration doit être invisible côté fil ; c'est ce que le test de parité
|
|
17
|
+
* vérifie.
|
|
18
|
+
*
|
|
19
|
+
* D'où des choix qui paraîtraient étranges hors de ce contexte — `visibility` reste une
|
|
20
|
+
* chaîne libre et non un enum, `crossProject` et `cross_project` coexistent. Ce ne sont
|
|
21
|
+
* pas des approximations : ce sont les schémas ACTUELS, et les resserrer ici mélangerait
|
|
22
|
+
* une migration mécanique avec un changement de contrat.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
/** Identité de l'appelant — présente à l'identique sur les deux outils de la famille. */
|
|
26
|
+
const CallerIdentity = {
|
|
27
|
+
agent: z.string().describe('Agent name.').optional(),
|
|
28
|
+
agentId: z.string().describe('Registered agent id.').optional(),
|
|
29
|
+
};
|
|
30
|
+
export const WriteNoteRequestSchema = z.object({
|
|
31
|
+
text: z.string().describe('Note content.'),
|
|
32
|
+
...CallerIdentity,
|
|
33
|
+
tags: z.array(z.string()).describe('Optional tags.').optional(),
|
|
34
|
+
// Chaîne libre et NON un enum : c'est l'état actuel du schéma publié. Le resserrer
|
|
35
|
+
// serait un changement de contrat déguisé en migration.
|
|
36
|
+
visibility: z.string().describe('Visibility: shared, machine, private.').optional(),
|
|
37
|
+
ttl: z.string().describe('Optional TTL: 30m, 2h, 7d.').optional(),
|
|
38
|
+
autoReflect: z
|
|
39
|
+
.boolean()
|
|
40
|
+
.describe('Attempt to reflect the runtime note into durable memory immediately.')
|
|
41
|
+
.optional(),
|
|
42
|
+
crossProject: z
|
|
43
|
+
.string()
|
|
44
|
+
.describe('Push note to a linked project (name or path). Requires role: publisher in cross_project_links config.')
|
|
45
|
+
.optional(),
|
|
46
|
+
// L'alias snake_case coexiste avec le camelCase. Le retirer casserait les appelants qui
|
|
47
|
+
// l'utilisent ; la migration ne doit rien retirer.
|
|
48
|
+
cross_project: z.string().describe('Snake_case alias of crossProject.').optional(),
|
|
49
|
+
});
|
|
50
|
+
export const QuickCaptureRequestSchema = z.object({
|
|
51
|
+
text: z.string().describe('Free-form capture text.'),
|
|
52
|
+
type: z
|
|
53
|
+
.enum(['decision', 'trap', 'constraint', 'note'])
|
|
54
|
+
.describe('Caller-asserted classification. Strongly recommended — the calling agent knows the nature of the capture better than keyword heuristics (cnd_abe61d68: 18 false contradiction positives on a review summary).')
|
|
55
|
+
.optional(),
|
|
56
|
+
context: z
|
|
57
|
+
.string()
|
|
58
|
+
.describe('Optional file/path/scope context to associate with the capture.')
|
|
59
|
+
.optional(),
|
|
60
|
+
...CallerIdentity,
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=capture-schema.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schémas zod des entrées de la famille CLAIM — `bclaw_claim` et `bclaw_release_claim`
|
|
3
|
+
* (pln#599 batch 2, deuxième famille composite).
|
|
4
|
+
*
|
|
5
|
+
* ── CONTRAINTE, INCHANGÉE DEPUIS LA FAMILLE CAPTURE ───────────────────────────
|
|
6
|
+
* Le JSON Schema produit doit être byte-identique à celui écrit à la main : le fingerprint
|
|
7
|
+
* de gouvernance (tests/unit/mcp-governance.test.ts) ne doit pas bouger. Une migration qui
|
|
8
|
+
* déplace le fingerprint n'est plus une migration, c'est un changement de surface publique.
|
|
9
|
+
*
|
|
10
|
+
* ── LES DEUX PIÈGES PAYÉS COMPTANT SUR #220/#221, REPRODUITS ICI EN GARDE ──────
|
|
11
|
+
* 1. zod émet `additionalProperties: false` d'office. Le laisser DURCIT le contrat : un
|
|
12
|
+
* client passant une clé inconnue était ACCEPTÉ (clé ignorée) et se ferait désormais
|
|
13
|
+
* rejeter. Le générateur le retire — À LA RACINE UNIQUEMENT, cf. OPEN_SCHEMAS.
|
|
14
|
+
* 2. Un champ REQUIS dans la version manuelle doit le rester. `rank` était passé optionnel
|
|
15
|
+
* par inadvertance sur la famille séquence : assouplissement du contrat, attrapé par le
|
|
16
|
+
* seul fingerprint. Ici les requis sont `scope`+`description` (claim) et `id` (release).
|
|
17
|
+
*
|
|
18
|
+
* Ni l'un ni l'autre n'avait été vu par mes propres vérifications, ni par le snapshot du
|
|
19
|
+
* registre CLI — qui mesure quelque chose de plus faible et donne une fausse assurance.
|
|
20
|
+
*
|
|
21
|
+
* ── CE QUI N'EST PAS RESSERRÉ, DÉLIBÉRÉMENT ───────────────────────────────────
|
|
22
|
+
* `store` et `planStatus` restent des chaînes libres et non des enums, bien que leurs
|
|
23
|
+
* valeurs utiles soient énumérées dans leur description. Les resserrer mérite sa propre
|
|
24
|
+
* décision : ce serait un rejet nouveau sur des appels aujourd'hui acceptés.
|
|
25
|
+
* `handoffMode` garde en revanche son enum, parce qu'il en avait DÉJÀ un.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
/** Identité de l'appelant — commune à la famille, comme pour capture et séquence. */
|
|
29
|
+
const CallerIdentity = {
|
|
30
|
+
agent: z.string().describe('Agent or person name.').optional(),
|
|
31
|
+
agentId: z.string().describe('Registered agent id.').optional(),
|
|
32
|
+
};
|
|
33
|
+
export const ClaimRequestSchema = z.object({
|
|
34
|
+
scope: z.string().describe('Scope being claimed.'),
|
|
35
|
+
description: z.string().describe('Description of the work.'),
|
|
36
|
+
...CallerIdentity,
|
|
37
|
+
planId: z.string().describe('Optional linked plan item ID.').optional(),
|
|
38
|
+
project: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe('Project name or path. Use this when working on a project different from the MCP server workspace (e.g. CLI agents in a different directory).')
|
|
41
|
+
.optional(),
|
|
42
|
+
// Chaîne libre, PAS un enum : les trois niveaux sont documentés mais la valeur reste
|
|
43
|
+
// ouverte côté schéma publié.
|
|
44
|
+
store: z.string().describe('Target store level: local (default), repo, workspace.').optional(),
|
|
45
|
+
worktreeBranch: z
|
|
46
|
+
.string()
|
|
47
|
+
.describe('Branch name for the worktree. Defaults to feat/<scope-slug>.')
|
|
48
|
+
.optional(),
|
|
49
|
+
worktree: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.describe('Whether to create an isolated git worktree (default true). Pass false for an advisory-only lock with no worktree (trp#431) — for in-place work in the main tree.')
|
|
52
|
+
.optional(),
|
|
53
|
+
advisory: z
|
|
54
|
+
.boolean()
|
|
55
|
+
.describe('Alias for worktree:false — advisory-only lock with no worktree (trp#431).')
|
|
56
|
+
.optional(),
|
|
57
|
+
// Enum CONSERVÉ : il existait déjà dans le schéma manuel. Le retirer serait
|
|
58
|
+
// l'assouplissement symétrique du durcissement qu'on évite ailleurs.
|
|
59
|
+
handoffMode: z
|
|
60
|
+
.enum(['self-commit', 'integrator'])
|
|
61
|
+
.describe('Handoff mode: "self-commit" (worker commits+merges) or "integrator" (another agent reviews+merges). Default: self-commit.')
|
|
62
|
+
.optional(),
|
|
63
|
+
});
|
|
64
|
+
export const ReleaseClaimRequestSchema = z.object({
|
|
65
|
+
id: z.string().describe('Claim ID to release.'),
|
|
66
|
+
planStatus: z.string().describe('Optional: update linked plan status.').optional(),
|
|
67
|
+
coordinator_override: z
|
|
68
|
+
.boolean()
|
|
69
|
+
.describe('Opt-in override for a trusted+ caller releasing a claim they do NOT own (cross-agent teardown, ghost-claim cleanup). Rejected for contributor-level callers; audited when used. trp#928.')
|
|
70
|
+
.optional(),
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=claim-request-schema.js.map
|
|
@@ -468,6 +468,41 @@ export function aggregateBrief(target, limit, resolved, currentHead, memoryReade
|
|
|
468
468
|
...(capped[i].cross_package ? { cross_package: true } : {}),
|
|
469
469
|
...(capped[i].local ? { local: true } : {}),
|
|
470
470
|
}));
|
|
471
|
-
return {
|
|
471
|
+
return {
|
|
472
|
+
target,
|
|
473
|
+
suggested_files_to_read: suggested,
|
|
474
|
+
related_memory: summarizeRelatedMemory(related),
|
|
475
|
+
freshness_badge: mergeBadges(perStore.map((p) => ({ ref: p.ref, badge: p.badge, hasIndex: p.r.hasIndex }))),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Resume la memoire liee servie par `code_brief` (pln#598 etape 3).
|
|
480
|
+
*
|
|
481
|
+
* POURQUOI. Un trap ou une decision de ce depot depasse regulierement 2 000 caracteres —
|
|
482
|
+
* plusieurs des textes ecrits pendant la refonte federation v2 en font le double. Un
|
|
483
|
+
* `code_brief` qui attache trois d'entre eux sert des milliers de caracteres avant meme
|
|
484
|
+
* que l'agent n'ait ouvert un fichier, pour un contenu qu'il ne lira peut-etre pas.
|
|
485
|
+
*
|
|
486
|
+
* LE TEXTE N'EST PAS PERDU, IL EST DIFFERE. Chaque entree raccourcie porte l'appel EXACT
|
|
487
|
+
* qui rend l'integralite. Un allegement qui supprime l'information au lieu de la deplacer
|
|
488
|
+
* force l'agent a deviner — et deviner sur un trap est precisement ce que les traps
|
|
489
|
+
* existent pour eviter.
|
|
490
|
+
*
|
|
491
|
+
* `id`, `kind`, `tags` et `related_paths` restent ENTIERS : ce sont eux qui permettent de
|
|
492
|
+
* decider s'il vaut la peine d'aller lire. Les tronquer ferait economiser des octets sur
|
|
493
|
+
* la seule partie qui sert a trier.
|
|
494
|
+
*/
|
|
495
|
+
const RELATED_MEMORY_TEXT_LIMIT = 300;
|
|
496
|
+
export function summarizeRelatedMemory(items) {
|
|
497
|
+
return items.map((item) => {
|
|
498
|
+
if (typeof item.text !== 'string' || item.text.length <= RELATED_MEMORY_TEXT_LIMIT)
|
|
499
|
+
return item;
|
|
500
|
+
return {
|
|
501
|
+
...item,
|
|
502
|
+
text: `${item.text.slice(0, RELATED_MEMORY_TEXT_LIMIT)}…`,
|
|
503
|
+
text_truncated: true,
|
|
504
|
+
full_text_via: { tool: 'bclaw_get', args: { entity: item.kind, id: item.id } },
|
|
505
|
+
};
|
|
506
|
+
});
|
|
472
507
|
}
|
|
473
508
|
//# sourceMappingURL=aggregate.js.map
|