@cohortapp/agent-sdk 2.3.2 → 2.4.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/framework-features.json +30 -0
- package/lib/backlog.mjs +136 -0
- package/lib/cadences.mjs +63 -2
- package/lib/cadences.test.mjs +105 -0
- package/lib/capability/inventory.mjs +542 -0
- package/lib/capability/inventory.test.mjs +232 -0
- package/lib/capability/probe.mjs +255 -0
- package/lib/channels/contract.mjs +37 -1
- package/lib/channels/contract.test.mjs +25 -1
- package/lib/claude-bin.mjs +37 -3
- package/lib/claude-bin.test.mjs +42 -8
- package/lib/execution/disposition.mjs +501 -0
- package/lib/execution/disposition.test.mjs +482 -0
- package/lib/execution/drive.mjs +352 -0
- package/lib/execution/drive.test.mjs +270 -0
- package/lib/execution/effects.mjs +340 -0
- package/lib/execution/effects.test.mjs +193 -0
- package/lib/execution/index.mjs +152 -0
- package/lib/execution/intake.mjs +581 -0
- package/lib/execution/intake.test.mjs +343 -0
- package/lib/execution/journal.mjs +374 -0
- package/lib/execution/journal.test.mjs +261 -0
- package/lib/execution/match.mjs +331 -0
- package/lib/execution/match.test.mjs +235 -0
- package/lib/execution/pipeline.mjs +341 -0
- package/lib/execution/pipeline.test.mjs +389 -0
- package/lib/execution/route.mjs +332 -0
- package/lib/execution/route.test.mjs +186 -0
- package/lib/execution/surface-policy.mjs +446 -0
- package/lib/execution/surface-policy.test.mjs +162 -0
- package/lib/goals/admission.mjs +209 -0
- package/lib/goals/admission.test.mjs +139 -0
- package/lib/goals/classify.mjs +206 -0
- package/lib/goals/classify.test.mjs +109 -0
- package/lib/goals/collaborate.mjs +415 -0
- package/lib/goals/collaborate.test.mjs +324 -0
- package/lib/goals/gaps.mjs +111 -0
- package/lib/goals/gaps.test.mjs +284 -0
- package/lib/goals/loop.mjs +537 -0
- package/lib/goals/loop.test.mjs +719 -0
- package/lib/identity/persona.mjs +247 -0
- package/lib/identity/persona.test.mjs +117 -0
- package/lib/kpi.mjs +469 -0
- package/lib/kpi.test.mjs +244 -0
- package/lib/mandate/audit.mjs +168 -0
- package/lib/mandate/audit.test.mjs +195 -0
- package/lib/mandate/cache.mjs +162 -0
- package/lib/mandate/derive.mjs +317 -0
- package/lib/mandate/derive.test.mjs +224 -0
- package/lib/mandate/model.mjs +352 -0
- package/lib/mandate/model.test.mjs +145 -0
- package/lib/mandate/refresh.mjs +187 -0
- package/lib/mandate/refresh.test.mjs +293 -0
- package/lib/mcp/server.test.mjs +4 -4
- package/lib/org/approvals.mjs +14 -2
- package/lib/org/client.mjs +58 -22
- package/lib/org/client.test.mjs +3 -1
- package/lib/org/inbound/directedness.mjs +720 -0
- package/lib/org/inbound/directedness.test.mjs +543 -0
- package/lib/org/inbound/facts.mjs +501 -0
- package/lib/org/inbound/facts.test.mjs +375 -0
- package/lib/org/inbound/hydrate.mjs +535 -0
- package/lib/org/inbound/hydrate.test.mjs +326 -0
- package/lib/org/inbound/index.mjs +233 -0
- package/lib/org/inbound/index.test.mjs +324 -0
- package/lib/org/inbound/io.mjs +141 -0
- package/lib/org/inbound/project.mjs +201 -0
- package/lib/org/inbound/project.test.mjs +287 -0
- package/lib/org/inbound/surfaces.mjs +257 -0
- package/lib/org/knowledge.mjs +10 -1
- package/lib/org/knowledge.test.mjs +8 -1
- package/lib/org/leases.mjs +5 -0
- package/lib/org/mesh.mjs +17 -2
- package/lib/org/messaging.mjs +40 -4
- package/lib/org/messaging.test.mjs +40 -0
- package/lib/org/param-contract.mjs +694 -0
- package/lib/org/param-contract.test.mjs +451 -0
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +8 -0
- package/lib/org/protocol.test.mjs +5 -1
- package/lib/org/push.mjs +1025 -0
- package/lib/org/push.test.mjs +690 -0
- package/lib/org/tool-surface.mjs +138 -38
- package/lib/org/tool-surface.test.mjs +13 -8
- package/lib/org/typing.mjs +341 -0
- package/lib/org/typing.test.mjs +291 -0
- package/lib/plan/compile.mjs +510 -0
- package/lib/plan/compile.test.mjs +286 -0
- package/lib/plan/emit.mjs +256 -0
- package/lib/plan/emit.test.mjs +246 -0
- package/lib/plan/explain.mjs +226 -0
- package/lib/plan/explain.test.mjs +188 -0
- package/lib/plan/schema.mjs +140 -0
- package/lib/resource-governor.mjs +47 -1
- package/lib/resource-governor.test.mjs +21 -1
- package/lib/setup/enroll-from-cohort.mjs +105 -17
- package/lib/setup/enroll-from-cohort.test.mjs +68 -1
- package/lib/setup/sections/identity.mjs +15 -4
- package/lib/setup/sections/identity.test.mjs +94 -0
- package/lib/setup/sections/inventory.mjs +178 -0
- package/lib/setup/sections/inventory.test.mjs +198 -0
- package/lib/setup/sections/mandate.mjs +392 -0
- package/lib/setup/sections/mandate.test.mjs +373 -0
- package/lib/setup/sections/subagents.mjs +427 -0
- package/lib/setup/sections/subagents.test.mjs +429 -0
- package/lib/setup/sections/verify.mjs +121 -0
- package/lib/setup/sections/verify.test.mjs +175 -0
- package/lib/setup/sot.mjs +2 -0
- package/lib/subagents/cli.mjs +463 -0
- package/lib/subagents/cli.test.mjs +389 -0
- package/lib/subagents/client.mjs +373 -0
- package/lib/subagents/client.test.mjs +309 -0
- package/lib/subagents/gap.mjs +268 -0
- package/lib/subagents/gap.test.mjs +234 -0
- package/lib/subagents/lock.mjs +296 -0
- package/lib/subagents/lock.test.mjs +248 -0
- package/lib/subagents/manifest.mjs +224 -0
- package/lib/subagents/manifest.test.mjs +175 -0
- package/lib/subagents/refs.mjs +274 -0
- package/lib/subagents/refs.test.mjs +204 -0
- package/lib/subagents/resolve.mjs +455 -0
- package/lib/subagents/resolve.test.mjs +422 -0
- package/lib/subagents/schema.mjs +467 -0
- package/lib/subagents/schema.test.mjs +306 -0
- package/package.json +8 -3
- package/plugins/maestro-skills/.claude-plugin/marketplace.json +16 -0
- package/policies/ai-disclosure.yaml +42 -2
- package/scaffold/CLAUDE.md +16 -2
- package/schedules/triggers/goal-steward.md +79 -0
- package/scripts/ci/conformance-org-api.mjs +792 -0
- package/scripts/ci/conformance-org-api.test.mjs +417 -0
- package/scripts/daemon/agent-daemon.mjs +36 -4
- package/scripts/daemon/cadence-handlers.mjs +145 -1
- package/scripts/daemon/goal-steward-cadence.test.mjs +243 -0
- package/scripts/daemon/inbox-deferral.mjs +45 -2
- package/scripts/daemon/inbox-deferral.test.mjs +56 -0
- package/scripts/daemon/inbox-wake.mjs +282 -0
- package/scripts/daemon/inbox-wake.test.mjs +199 -0
- package/scripts/daemon/prompt-builder.mjs +41 -1
- package/scripts/daemon/typing-registry.mjs +55 -2
- package/scripts/daemon/typing-registry.test.mjs +25 -0
- package/scripts/local-triggers/generate-plists.test.mjs +5 -5
- package/scripts/setup/gen-subagent-manifest.mjs +95 -0
- package/scripts/setup/gen-subagent-manifest.test.mjs +124 -0
- package/scripts/setup/generate-plan.mjs +108 -0
- package/scripts/setup/init-capability-manifest.mjs +70 -0
- package/scripts/setup/init-skill-marketplace.mjs +155 -0
- package/scripts/setup/init-skill-marketplace.test.mjs +193 -0
package/lib/org/tool-surface.mjs
CHANGED
|
@@ -67,6 +67,15 @@ import {
|
|
|
67
67
|
approvalWait,
|
|
68
68
|
fetchSelfProfile,
|
|
69
69
|
} from "./client.mjs";
|
|
70
|
+
// The hq vocabularies the model must pick from (an out-of-vocabulary enum value
|
|
71
|
+
// is a BAD_REQUEST). Sourced from the wire contract so the tool table and the
|
|
72
|
+
// normaliser can never disagree about what hq accepts.
|
|
73
|
+
import {
|
|
74
|
+
BOARD_COLS,
|
|
75
|
+
TASK_PRIORITIES,
|
|
76
|
+
ESCALATION_SEVERITIES,
|
|
77
|
+
MEMORY_CLASSES,
|
|
78
|
+
} from "./param-contract.mjs";
|
|
70
79
|
|
|
71
80
|
/** Default org server origin when neither env nor config names one. */
|
|
72
81
|
export const DEFAULT_COHORT_BASE = "https://os.cohortapp.com";
|
|
@@ -265,7 +274,7 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
265
274
|
title: "Send org message",
|
|
266
275
|
description:
|
|
267
276
|
"Post a message into an org channel (messaging.send). Send-gate screened locally; " +
|
|
268
|
-
"the server enforces channel ACLs + pairing.
|
|
277
|
+
"the server enforces channel ACLs + pairing. An idempotencyId is auto-minted for dedup.",
|
|
269
278
|
input_schema: S(
|
|
270
279
|
{
|
|
271
280
|
channelId: str("Channel id (use messaging_open_dm for a member DM)."),
|
|
@@ -308,23 +317,46 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
308
317
|
{
|
|
309
318
|
name: "board_complete",
|
|
310
319
|
title: "Complete work item",
|
|
311
|
-
description:
|
|
312
|
-
|
|
320
|
+
description:
|
|
321
|
+
"Mark a claimed board item complete (board.complete). The server's schema is strict: " +
|
|
322
|
+
"evidence goes in `proof` (a free-text note is wrapped into proof.note).",
|
|
323
|
+
input_schema: S(
|
|
324
|
+
{
|
|
325
|
+
itemId: str("Board item id."),
|
|
326
|
+
proof: {
|
|
327
|
+
type: "object",
|
|
328
|
+
description: "Evidence of completion (optional) — e.g. {note, url, commit}.",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
["itemId"],
|
|
332
|
+
),
|
|
313
333
|
access: "write",
|
|
314
334
|
binding: { kind: "rpc", method: "board.complete" },
|
|
315
335
|
},
|
|
316
336
|
{
|
|
317
337
|
name: "task_create",
|
|
318
338
|
title: "Create task",
|
|
319
|
-
description:
|
|
339
|
+
description:
|
|
340
|
+
"Create a task on the org board (board.createTask). Due dates are NOT settable at " +
|
|
341
|
+
"create time — create the task, then set dueAt with task_update.",
|
|
320
342
|
input_schema: S(
|
|
321
343
|
{
|
|
322
344
|
title: str("Task title."),
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
345
|
+
detail: str("Task body/description (optional)."),
|
|
346
|
+
assigneeId: str("Member id to assign (optional) — an id, not a name."),
|
|
347
|
+
col: {
|
|
348
|
+
type: "string",
|
|
349
|
+
enum: BOARD_COLS,
|
|
350
|
+
description: "Initial board column (optional; defaults to triage).",
|
|
351
|
+
},
|
|
352
|
+
priority: {
|
|
353
|
+
type: "string",
|
|
354
|
+
enum: TASK_PRIORITIES,
|
|
355
|
+
description: "Priority P0 (highest) .. P4 (optional; defaults to P2).",
|
|
356
|
+
},
|
|
357
|
+
channelId: str("The Space/DM whose board this task belongs to (optional)."),
|
|
358
|
+
workstreamId: str("Workstream/lane id (optional)."),
|
|
359
|
+
reviewerId: str("Reviewer member id (optional)."),
|
|
328
360
|
},
|
|
329
361
|
["title"],
|
|
330
362
|
),
|
|
@@ -335,16 +367,26 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
335
367
|
name: "task_update",
|
|
336
368
|
title: "Update task",
|
|
337
369
|
description:
|
|
338
|
-
"Update a task's
|
|
370
|
+
"Update a task's column/fields, or move it into a workstream lane (board.updateTask). Pass " +
|
|
371
|
+
"workstreamName to assign a task that's sitting in no lane — reuses an existing lane by name " +
|
|
372
|
+
"or creates it. This verb CANNOT reassign a task — use task_assign for that.",
|
|
339
373
|
input_schema: S(
|
|
340
374
|
{
|
|
341
375
|
taskId: str("Task id."),
|
|
342
|
-
|
|
376
|
+
col: {
|
|
377
|
+
type: "string",
|
|
378
|
+
enum: BOARD_COLS,
|
|
379
|
+
description: "Move the task to this board column (optional).",
|
|
380
|
+
},
|
|
343
381
|
title: str("New title (optional)."),
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
382
|
+
detail: str("New description (optional)."),
|
|
383
|
+
priority: {
|
|
384
|
+
type: "string",
|
|
385
|
+
enum: TASK_PRIORITIES,
|
|
386
|
+
description: "New priority P0..P4 (optional).",
|
|
387
|
+
},
|
|
388
|
+
dueAt: str("New due date ISO 8601 (optional; null clears it)."),
|
|
389
|
+
blockedBy: str("Free-text reason this task is waiting (optional)."),
|
|
348
390
|
workstreamName: str(
|
|
349
391
|
"Move the task into this workstream lane — reuses an existing lane by name or creates it (optional).",
|
|
350
392
|
),
|
|
@@ -354,6 +396,20 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
354
396
|
access: "write",
|
|
355
397
|
binding: { kind: "rpc", method: "board.updateTask" },
|
|
356
398
|
},
|
|
399
|
+
{
|
|
400
|
+
name: "task_assign",
|
|
401
|
+
title: "Assign task",
|
|
402
|
+
description:
|
|
403
|
+
"Set or clear a task's owner (board.assignTask). The assignee must be an ACTIVE member id " +
|
|
404
|
+
"in this org; pass null to unassign. task_update cannot do this — its schema has no " +
|
|
405
|
+
"assignee field.",
|
|
406
|
+
input_schema: S(
|
|
407
|
+
{ taskId: str("Task id."), assigneeId: str("Member id to own the task (null clears it).") },
|
|
408
|
+
["taskId", "assigneeId"],
|
|
409
|
+
),
|
|
410
|
+
access: "write",
|
|
411
|
+
binding: { kind: "rpc", method: "board.assignTask" },
|
|
412
|
+
},
|
|
357
413
|
{
|
|
358
414
|
name: "task_comment",
|
|
359
415
|
title: "Comment on task",
|
|
@@ -374,12 +430,23 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
374
430
|
{
|
|
375
431
|
name: "decision_propose",
|
|
376
432
|
title: "Propose decision",
|
|
377
|
-
description:
|
|
433
|
+
description:
|
|
434
|
+
"Propose a decision into the org registry (decision.propose). The rationale is recorded " +
|
|
435
|
+
"in the explain-yourself `why` block — a decision proposed without it is registered with " +
|
|
436
|
+
"no rationale at all.",
|
|
378
437
|
input_schema: S(
|
|
379
438
|
{
|
|
380
439
|
title: str("Decision title."),
|
|
381
|
-
|
|
382
|
-
|
|
440
|
+
body: str("The decision text (optional)."),
|
|
441
|
+
why: {
|
|
442
|
+
type: "object",
|
|
443
|
+
description:
|
|
444
|
+
"Explain-yourself provenance: {reason, options[], clause, wouldChange}. `reason` is " +
|
|
445
|
+
"the rationale the registry displays.",
|
|
446
|
+
},
|
|
447
|
+
tag: str("Scope/tag for the registry, e.g. \"eng\" (optional)."),
|
|
448
|
+
clause: str("The charter clause this decision rests on (optional)."),
|
|
449
|
+
supersedes: str("Id of the decision this replaces (optional)."),
|
|
383
450
|
},
|
|
384
451
|
["title"],
|
|
385
452
|
),
|
|
@@ -398,7 +465,7 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
398
465
|
name: "decision_comment",
|
|
399
466
|
title: "Comment on decision",
|
|
400
467
|
description: "Comment on a decision (decision.comment).",
|
|
401
|
-
input_schema: S({ decisionId: str("Decision id."),
|
|
468
|
+
input_schema: S({ decisionId: str("Decision id."), text: str("Comment text.") }, ["decisionId", "text"]),
|
|
402
469
|
access: "write",
|
|
403
470
|
binding: { kind: "rpc", method: "decision.comment" },
|
|
404
471
|
},
|
|
@@ -413,7 +480,9 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
413
480
|
actionClass: str("Action class, e.g. \"external_comms\"."),
|
|
414
481
|
payload: { type: "object", description: "The intent payload the approval binds (hash-bound server-side)." },
|
|
415
482
|
subject: str("Human-readable subject (optional)."),
|
|
416
|
-
|
|
483
|
+
payloadHash: str("Precomputed sha256 payload hash (64 hex chars) — pass through from a structured approval_required result. Supply this OR payload."),
|
|
484
|
+
itemId: str("Board item this approval blocks (optional — the task flips to `blocked`)."),
|
|
485
|
+
expiresAt: str("ISO 8601 expiry (optional)."),
|
|
417
486
|
},
|
|
418
487
|
["actionClass"],
|
|
419
488
|
),
|
|
@@ -444,14 +513,19 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
444
513
|
{
|
|
445
514
|
name: "knowledge_append",
|
|
446
515
|
title: "Append org knowledge",
|
|
447
|
-
description:
|
|
516
|
+
description:
|
|
517
|
+
"Append a knowledge episode to the shared plane (knowledge.append). Enrichment " +
|
|
518
|
+
"(kind/participants/links/refs) is persisted inside `metadata`.",
|
|
448
519
|
input_schema: S(
|
|
449
520
|
{
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
521
|
+
body: str("The fact/episode text."),
|
|
522
|
+
group: str("Group hint (server enforces ACL; optional) — \"org\" | \"unit:<id>\" | \"barrier:<cell>\"."),
|
|
523
|
+
metadata: {
|
|
524
|
+
type: "object",
|
|
525
|
+
description: "Structured enrichment stored with the episode (optional) — e.g. {kind, participants, links, refs}.",
|
|
526
|
+
},
|
|
453
527
|
},
|
|
454
|
-
["
|
|
528
|
+
["body"],
|
|
455
529
|
),
|
|
456
530
|
access: "write",
|
|
457
531
|
binding: { kind: "rpc", method: "knowledge.append" },
|
|
@@ -459,14 +533,24 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
459
533
|
{
|
|
460
534
|
name: "memory_author",
|
|
461
535
|
title: "Author org memory",
|
|
462
|
-
description:
|
|
536
|
+
description:
|
|
537
|
+
"Write a Cortex index-hint entry to the org memory plane (memory.author). `scopes` is the " +
|
|
538
|
+
"ACL field (who may recall this), NOT a tag list. Amend an existing entry with " +
|
|
539
|
+
"{amendOf, note}.",
|
|
463
540
|
input_schema: S(
|
|
464
541
|
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
542
|
+
memoryClass: {
|
|
543
|
+
type: "string",
|
|
544
|
+
enum: MEMORY_CLASSES,
|
|
545
|
+
description: "Which memory plane this entry belongs to.",
|
|
546
|
+
},
|
|
547
|
+
title: str("Short title (<=200 chars) — the entry's headline."),
|
|
548
|
+
summary: str("The memory content (optional but expected)."),
|
|
549
|
+
scopes: arr({ type: "string" }, "ACL scopes that may recall this (optional; empty = org-wide)."),
|
|
550
|
+
amendOf: str("Id of the entry this amends (optional)."),
|
|
551
|
+
note: str("Revision note — REQUIRED when amendOf is set."),
|
|
468
552
|
},
|
|
469
|
-
["
|
|
553
|
+
["memoryClass", "title"],
|
|
470
554
|
),
|
|
471
555
|
access: "write",
|
|
472
556
|
binding: { kind: "rpc", method: "memory.author" },
|
|
@@ -475,22 +559,31 @@ export const ORG_TOOLS = Object.freeze([
|
|
|
475
559
|
{
|
|
476
560
|
name: "member_profile",
|
|
477
561
|
title: "Member profile",
|
|
478
|
-
description: "One member's full profile (member.get).",
|
|
479
|
-
input_schema: S({
|
|
562
|
+
description: "One member's full profile (member.get). Resolved by SLUG, not by member id.",
|
|
563
|
+
input_schema: S({ slug: str("Member slug (e.g. \"isla-roselli\").") }, ["slug"]),
|
|
480
564
|
access: "read",
|
|
481
565
|
binding: { kind: "rpc", method: "member.get" },
|
|
482
566
|
},
|
|
483
567
|
{
|
|
484
568
|
name: "escalation_raise",
|
|
485
569
|
title: "Raise escalation",
|
|
486
|
-
description:
|
|
570
|
+
description:
|
|
571
|
+
"Raise an escalation to the org (escalation.create). An escalation must reference " +
|
|
572
|
+
"something — supply a taskId or a channelId (or both).",
|
|
487
573
|
input_schema: S(
|
|
488
574
|
{
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
575
|
+
title: str("Escalation title (<=500 chars)."),
|
|
576
|
+
severity: {
|
|
577
|
+
type: "string",
|
|
578
|
+
enum: ESCALATION_SEVERITIES,
|
|
579
|
+
description: "Severity (optional; defaults to medium).",
|
|
580
|
+
},
|
|
581
|
+
detail: str("Details (optional)."),
|
|
582
|
+
taskId: str("The task being escalated (supply this or channelId)."),
|
|
583
|
+
channelId: str("The channel being escalated (supply this or taskId)."),
|
|
584
|
+
waitingOn: str("Who/what this is blocked on (optional)."),
|
|
492
585
|
},
|
|
493
|
-
["
|
|
586
|
+
["title"],
|
|
494
587
|
),
|
|
495
588
|
access: "write",
|
|
496
589
|
binding: { kind: "rpc", method: "escalation.create" },
|
|
@@ -2495,7 +2588,14 @@ export async function executeOrgTool(name, input = {}, o = {}) {
|
|
|
2495
2588
|
// Business-dedup ids auto-minted where hq expects them. (artifact.*
|
|
2496
2589
|
// is sideEffecting:false at the dispatcher — the hq artifact service
|
|
2497
2590
|
// self-manages its writes — so these params ARE the dedup lane.)
|
|
2498
|
-
|
|
2591
|
+
//
|
|
2592
|
+
// messaging.send's dedup key is `idempotencyId` — hq's
|
|
2593
|
+
// sendMessageSchemaV1 REQUIRES it and never reads `clientMsgId` (that
|
|
2594
|
+
// is the COLUMN it de-dups into, not the wire name). Minting the wrong
|
|
2595
|
+
// name meant every message the model sent through this plane came back
|
|
2596
|
+
// BAD_REQUEST. `call()` would mint it now anyway (param-contract), but
|
|
2597
|
+
// minting here keeps the id available to the caller's audit row.
|
|
2598
|
+
if (name === "messaging_send" && !params.idempotencyId) params.idempotencyId = randomUUID();
|
|
2499
2599
|
if (name === "email_send" && !params.idempotencyId) params.idempotencyId = randomUUID();
|
|
2500
2600
|
if (name === "artifact_create" && !params.clientMsgId) params.clientMsgId = randomUUID();
|
|
2501
2601
|
if (name === "artifact_act" && !params.idempotencyKey) params.idempotencyKey = randomUUID();
|
|
@@ -59,11 +59,13 @@ function fakeFetch(body = { ok: true, result: { fine: true } }, { status = 200,
|
|
|
59
59
|
|
|
60
60
|
// ── table shape ────────────────────────────────────────────────────────────
|
|
61
61
|
|
|
62
|
-
test("table: curated
|
|
62
|
+
test("table: curated 29 + 5 email + 5 artifact + 69 desk tools, snake_case names, valid access + schemas", () => {
|
|
63
63
|
// 2026-08 mobile-parity delta: +5 mail (report_spam/react/move_mailbox/
|
|
64
64
|
// summarise/ask) and +3 asks (files/calendar/crm) → desk 56 → 64; the
|
|
65
65
|
// call-working-sessions delta adds the 5 calls-desk tools → 69.
|
|
66
|
-
|
|
66
|
+
// The wire-contract fix adds task_assign: hq's editTaskSchema (board.updateTask)
|
|
67
|
+
// has NO assignee field, so reassignment needs board.assignTask — curated 28 → 29.
|
|
68
|
+
assert.equal(ORG_TOOLS.length, 108, "29 curated + 5 email + 5 artifact + 69 desk");
|
|
67
69
|
assert.equal(ORG_TOOLS.filter((t) => t.desk).length, 69, "exactly sixty-nine desk tools");
|
|
68
70
|
assert.equal(ORG_TOOLS.filter((t) => t.email).length, 5, "exactly five email tools");
|
|
69
71
|
assert.equal(ORG_TOOLS.filter((t) => t.artifact).length, 5, "exactly five artifact tools");
|
|
@@ -88,19 +90,19 @@ test("OUTBOUND_METHODS is DERIVED from outbound:true tools (messaging/email/mail
|
|
|
88
90
|
test("email gating: family present in the vendored protocol → tools active; override excludes", () => {
|
|
89
91
|
// The email family landed in the vendored protocol (sync-protocol phase 1).
|
|
90
92
|
assert.equal(emailFamilyAvailable(), true, "vendored protocol carries email.send");
|
|
91
|
-
assert.equal(getOrgTools().length,
|
|
93
|
+
assert.equal(getOrgTools().length, 108);
|
|
92
94
|
const without = getOrgTools({ emailAvailable: false });
|
|
93
|
-
assert.equal(without.length,
|
|
95
|
+
assert.equal(without.length, 103);
|
|
94
96
|
assert.ok(!without.some((t) => t.email), "email tools excluded when family absent");
|
|
95
97
|
});
|
|
96
98
|
|
|
97
99
|
test("artifact gating: family present → tools active; override excludes (email precedent)", () => {
|
|
98
100
|
assert.equal(artifactFamilyAvailable(), true, "vendored protocol carries artifact.act");
|
|
99
101
|
const without = getOrgTools({ artifactAvailable: false });
|
|
100
|
-
assert.equal(without.length,
|
|
102
|
+
assert.equal(without.length, 103);
|
|
101
103
|
assert.ok(!without.some((t) => t.artifact), "artifact tools excluded when family absent");
|
|
102
104
|
const neither = getOrgTools({ emailAvailable: false, artifactAvailable: false, desksAvailable: false });
|
|
103
|
-
assert.equal(neither.length,
|
|
105
|
+
assert.equal(neither.length, 29, "all additive families off → the 29 always-on tools");
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
test("isOrgTool / orgToolDef cover the full table; unknown names rejected", () => {
|
|
@@ -203,7 +205,7 @@ test("approval_wait: requires approvalId; dispatches the bounded long-poll", asy
|
|
|
203
205
|
|
|
204
206
|
// ── executor: send-gate parity (§1.5a) ─────────────────────────────────────
|
|
205
207
|
|
|
206
|
-
test("messaging_send: screened through the send-gate,
|
|
208
|
+
test("messaging_send: screened through the send-gate, idempotencyId auto-minted", async () => {
|
|
207
209
|
const screened = [];
|
|
208
210
|
const fetchImpl = fakeFetch({ ok: true, result: { messageId: "m1" } });
|
|
209
211
|
const frame = await executeOrgTool(
|
|
@@ -216,7 +218,10 @@ test("messaging_send: screened through the send-gate, clientMsgId auto-minted",
|
|
|
216
218
|
assert.equal(screened[0].recipient, "C9");
|
|
217
219
|
assert.equal(screened[0].text, "hello org");
|
|
218
220
|
const body = JSON.parse(fetchImpl.calls[0].init.body);
|
|
219
|
-
|
|
221
|
+
// hq's sendMessageSchemaV1 REQUIRES `idempotencyId`. Minting `clientMsgId`
|
|
222
|
+
// (the column name, not the wire name) 400'd every message this plane sent.
|
|
223
|
+
assert.ok(body.idempotencyId && body.idempotencyId.length >= 8, "idempotencyId auto-minted");
|
|
224
|
+
assert.equal(body.clientMsgId, undefined, "the column name is NOT the wire name");
|
|
220
225
|
assert.ok(fetchImpl.calls[0].init.headers["x-idempotency-key"], "dispatcher idempotency key present");
|
|
221
226
|
});
|
|
222
227
|
|