@cadraos/sdk 0.3.12 → 0.3.14-p102.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/dist/board-host/index.cjs +217 -129
- package/dist/board-host/index.cjs.map +1 -1
- package/dist/board-host/index.d.cts +268 -3
- package/dist/board-host/index.d.ts +268 -3
- package/dist/board-host/index.js +218 -131
- package/dist/board-host/index.js.map +1 -1
- package/dist/board-host/server/index.d.cts +218 -2
- package/dist/board-host/server/index.d.ts +218 -2
- package/dist/chat/index.cjs +528 -47
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +29 -0
- package/dist/chat/index.d.ts +29 -0
- package/dist/chat/index.js +528 -47
- package/dist/chat/index.js.map +1 -1
- package/dist/index.cjs +453 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.js +453 -38
- package/dist/index.js.map +1 -1
- package/dist/kanban/engine/index.cjs +1535 -434
- package/dist/kanban/engine/index.cjs.map +1 -1
- package/dist/kanban/engine/index.d.cts +628 -6
- package/dist/kanban/engine/index.d.ts +628 -6
- package/dist/kanban/engine/index.js +1520 -435
- package/dist/kanban/engine/index.js.map +1 -1
- package/dist/kanban/index.cjs +751 -60
- package/dist/kanban/index.cjs.map +1 -1
- package/dist/kanban/index.d.cts +555 -9
- package/dist/kanban/index.d.ts +555 -9
- package/dist/kanban/index.js +747 -61
- package/dist/kanban/index.js.map +1 -1
- package/dist/teams/index.cjs +453 -38
- package/dist/teams/index.cjs.map +1 -1
- package/dist/teams/index.d.cts +27 -0
- package/dist/teams/index.d.ts +27 -0
- package/dist/teams/index.js +453 -38
- package/dist/teams/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -213,7 +213,17 @@ interface KanbanLane {
|
|
|
213
213
|
* a tick fans out ⌈cap / chunkSize⌉ runs. Ignored for `per-card`/`event` lanes.
|
|
214
214
|
*/
|
|
215
215
|
chunkSize?: number;
|
|
216
|
+
/**
|
|
217
|
+
* p46 Handoff contract, OPT-IN per lane (D1). Absent ⇒ `off` (existing boards
|
|
218
|
+
* are byte-identical). `verdict` ⇒ the engine sends
|
|
219
|
+
* `HANDOFF_VERDICT_JSON_SCHEMA` as the run's `context.outputSchema` and a
|
|
220
|
+
* completion MUST carry a valid verdict (else the run fails with a FAILED
|
|
221
|
+
* handoff). Active only on hosts whose adapter implements the ledger group.
|
|
222
|
+
*/
|
|
223
|
+
handoff?: KanbanLaneHandoffMode;
|
|
216
224
|
}
|
|
225
|
+
/** Per-lane Handoff mode (p46 D1). */
|
|
226
|
+
type KanbanLaneHandoffMode = "off" | "verdict";
|
|
217
227
|
/**
|
|
218
228
|
* A declared edge between two lanes in the lifecycle.
|
|
219
229
|
*
|
|
@@ -309,6 +319,11 @@ interface KanbanCard {
|
|
|
309
319
|
configVersion?: number;
|
|
310
320
|
/** Arbitrary shared context carried across transitions. */
|
|
311
321
|
sharedContext?: Record<string, unknown>;
|
|
322
|
+
/**
|
|
323
|
+
* p46 (P46-002): the card's id in an external host (a Jira issue key).
|
|
324
|
+
* Absent on native / crm / yobo cards.
|
|
325
|
+
*/
|
|
326
|
+
externalRef?: string;
|
|
312
327
|
/**
|
|
313
328
|
* Execution UUID of the card's current/last run, if any.
|
|
314
329
|
*
|
|
@@ -371,6 +386,18 @@ interface CreateCardInput {
|
|
|
371
386
|
* in the lifecycle's initial lane.
|
|
372
387
|
*/
|
|
373
388
|
lane?: string;
|
|
389
|
+
/**
|
|
390
|
+
* p46 (P46-002): the card's id in an EXTERNAL host (a Jira issue key, e.g.
|
|
391
|
+
* `CAD-231`). Optional. When set, a card is created at most ONCE per
|
|
392
|
+
* `(boardConfigUuid, externalRef)`: a repeat create returns the existing card
|
|
393
|
+
* and dispatches nothing. Hosts that don't store it simply ignore it.
|
|
394
|
+
*/
|
|
395
|
+
externalRef?: string;
|
|
396
|
+
/**
|
|
397
|
+
* p46 (P46-002): opaque host facts stored with the card (Jira `statusId`,
|
|
398
|
+
* `mover`, `detached`, …). Passed through to `adapter.createCard` untouched.
|
|
399
|
+
*/
|
|
400
|
+
externalMeta?: Record<string, unknown>;
|
|
374
401
|
}
|
|
375
402
|
/**
|
|
376
403
|
* Who performed a transition / stage-run (server-engine side).
|
|
@@ -488,8 +515,126 @@ interface ApplyTransitionInput {
|
|
|
488
515
|
contextPatch?: Record<string, unknown>;
|
|
489
516
|
/** Execution UUID to stamp on the card (handler advances). */
|
|
490
517
|
executionUuid?: string;
|
|
518
|
+
/**
|
|
519
|
+
* p46 (P46-002, F8): set ONLY by `engine.hostMove` (a move the external host
|
|
520
|
+
* made — e.g. a Jira drag mirrored by the sync). A host-aware adapter, inside
|
|
521
|
+
* the same card lock as the `fromLane` CAS, (1) refuses the move with a
|
|
522
|
+
* `HostFenceError` while the card has any `host.*` op `pending` (D19 — never
|
|
523
|
+
* mistake our own half-done move for a human one), (2) writes `externalMeta`,
|
|
524
|
+
* then (3) moves. Adapters that don't know it ignore it (optional, additive).
|
|
525
|
+
*/
|
|
526
|
+
hostObservation?: {
|
|
527
|
+
externalMeta?: Record<string, unknown>;
|
|
528
|
+
/**
|
|
529
|
+
* ISO time the host state was READ. The fence also refuses the move when a
|
|
530
|
+
* `host.*` op for the card settled (done/failed) after this instant — the
|
|
531
|
+
* read may predate our own write landing in the host.
|
|
532
|
+
*/
|
|
533
|
+
observedAt?: string;
|
|
534
|
+
};
|
|
491
535
|
}
|
|
492
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Handoff v1 — the one AI↔human contract every board lane speaks (p46 §0.3).
|
|
539
|
+
*
|
|
540
|
+
* A Handoff is the record written whenever work crosses between an agent and a
|
|
541
|
+
* person on a card: an agent's verdict ("decide A/B/C", "approve", "test this"),
|
|
542
|
+
* an engine-written FAILED / BLOCKED, or the human's answer carried forward to
|
|
543
|
+
* the next agent (`human_to_ai`). Every worker type produces the same object and
|
|
544
|
+
* every host reads it.
|
|
545
|
+
*
|
|
546
|
+
* This module is DEPENDENCY-FREE and client-safe (types, the verdict JSON
|
|
547
|
+
* schema, the prompt renderer, the link guard). The ajv-backed verdict PARSER
|
|
548
|
+
* lives in `./handoff-parse.ts` and is exported only from the server engine
|
|
549
|
+
* entry (`@cadraos/sdk/kanban/engine`), so the browser bundle never carries ajv.
|
|
550
|
+
*
|
|
551
|
+
* @module @cadraos/sdk/kanban
|
|
552
|
+
*/
|
|
553
|
+
type HandoffKind = "DECIDE" | "APPROVE" | "TEST" | "REVIEW" | "FAILED" | "BLOCKED" | "FYI";
|
|
554
|
+
interface HandoffOption {
|
|
555
|
+
/** Single capital letter, `^[A-Z]$`. */
|
|
556
|
+
id: string;
|
|
557
|
+
/** ≤80 chars. */
|
|
558
|
+
label: string;
|
|
559
|
+
/** ≤300 chars. */
|
|
560
|
+
consequence: string;
|
|
561
|
+
}
|
|
562
|
+
interface HandoffDecision {
|
|
563
|
+
/** ≤500 chars. */
|
|
564
|
+
question: string;
|
|
565
|
+
/** 2–4 options. */
|
|
566
|
+
options: HandoffOption[];
|
|
567
|
+
/** ∈ options[].id */
|
|
568
|
+
recommended: string;
|
|
569
|
+
/** ∈ options[].id */
|
|
570
|
+
defaultIfSilent?: string;
|
|
571
|
+
}
|
|
572
|
+
interface HandoffState {
|
|
573
|
+
repos: string[];
|
|
574
|
+
tests: string | null;
|
|
575
|
+
story: string | null;
|
|
576
|
+
costUsd: number | null;
|
|
577
|
+
}
|
|
578
|
+
interface HandoffAnswer {
|
|
579
|
+
option: string | null;
|
|
580
|
+
/** ≤4000 chars. */
|
|
581
|
+
text: string | null;
|
|
582
|
+
by: string;
|
|
583
|
+
at: string;
|
|
584
|
+
via: "board" | "channel";
|
|
585
|
+
source: string;
|
|
586
|
+
}
|
|
587
|
+
interface HandoffV1 {
|
|
588
|
+
v: 1;
|
|
589
|
+
/** = agentic_handoff.uuid */
|
|
590
|
+
id: string;
|
|
591
|
+
/** Card uuid. */
|
|
592
|
+
cardId: string;
|
|
593
|
+
direction: "ai_to_human" | "human_to_ai";
|
|
594
|
+
/** Lane KEYS. */
|
|
595
|
+
fromLane: string;
|
|
596
|
+
toLane: string;
|
|
597
|
+
/** ISO-8601. */
|
|
598
|
+
at: string;
|
|
599
|
+
by: {
|
|
600
|
+
type: "session" | "user" | "watchdog" | "timeout" | "health";
|
|
601
|
+
id: string;
|
|
602
|
+
};
|
|
603
|
+
kind: HandoffKind;
|
|
604
|
+
/** ≤200 chars. */
|
|
605
|
+
headline: string;
|
|
606
|
+
/** ≤20 × ≤300 chars. */
|
|
607
|
+
done: string[];
|
|
608
|
+
state: HandoffState;
|
|
609
|
+
decision?: HandoffDecision;
|
|
610
|
+
/** ≤300 chars. */
|
|
611
|
+
howToAnswer: string;
|
|
612
|
+
/** ≤10; url must be http(s): or a same-origin path starting "/". */
|
|
613
|
+
links: {
|
|
614
|
+
label: string;
|
|
615
|
+
url: string;
|
|
616
|
+
}[];
|
|
617
|
+
session: {
|
|
618
|
+
id: string | null;
|
|
619
|
+
runnerId: string | null;
|
|
620
|
+
resumableUntil: string | null;
|
|
621
|
+
};
|
|
622
|
+
/** BLOCKED only. */
|
|
623
|
+
blocked?: {
|
|
624
|
+
check: string;
|
|
625
|
+
reason: string;
|
|
626
|
+
fixUrl: string;
|
|
627
|
+
};
|
|
628
|
+
answer?: HandoffAnswer;
|
|
629
|
+
}
|
|
630
|
+
/** Ledger lifecycle of a stored handoff. */
|
|
631
|
+
type HandoffStatus = "open" | "answered" | "resolved" | "consumed";
|
|
632
|
+
/** A stored handoff: the v1 payload plus its ledger status. */
|
|
633
|
+
type HandoffRecord = HandoffV1 & {
|
|
634
|
+
status: HandoffStatus;
|
|
635
|
+
consumedAt: string | null;
|
|
636
|
+
};
|
|
637
|
+
|
|
493
638
|
/**
|
|
494
639
|
* KanbanStorageAdapter — the SERVER-ENGINE persistence contract.
|
|
495
640
|
*
|
|
@@ -571,6 +716,16 @@ interface KanbanStorageAdapter {
|
|
|
571
716
|
* config and passes it as `initialLane`.
|
|
572
717
|
*/
|
|
573
718
|
createCard(input: CreateCardServerInput): Promise<KanbanCard>;
|
|
719
|
+
/**
|
|
720
|
+
* p46 (P46-002) OPTIONAL: the card with this `externalRef` on this board, or
|
|
721
|
+
* null. When present, `engine.createCard({externalRef})` looks it up first and
|
|
722
|
+
* returns an existing card WITHOUT dispatching (exactly-once create per issue
|
|
723
|
+
* key). A host that stores `externalRef` SHOULD also make `createCard` itself
|
|
724
|
+
* idempotent on `(boardConfigUuid, externalRef)` (unique index + `ON CONFLICT
|
|
725
|
+
* DO NOTHING` → return the existing row) for the lookup/insert race. Hosts
|
|
726
|
+
* without external refs (crm/yobo) omit it.
|
|
727
|
+
*/
|
|
728
|
+
getCardByExternalRef?(boardConfigUuid: string, externalRef: string): Promise<KanbanCard | null>;
|
|
574
729
|
/**
|
|
575
730
|
* Patch a card's mutable fields.
|
|
576
731
|
*/
|
|
@@ -687,7 +842,7 @@ interface KanbanStorageAdapter {
|
|
|
687
842
|
toLane?: string;
|
|
688
843
|
contextPatch?: Record<string, unknown>;
|
|
689
844
|
actor: Actor;
|
|
690
|
-
}): Promise<{
|
|
845
|
+
} & StageRunLedgerWrite): Promise<{
|
|
691
846
|
applied: boolean;
|
|
692
847
|
}>;
|
|
693
848
|
/**
|
|
@@ -700,7 +855,7 @@ interface KanbanStorageAdapter {
|
|
|
700
855
|
*/
|
|
701
856
|
failStageRun(idemKey: string, f: {
|
|
702
857
|
error: string;
|
|
703
|
-
}): Promise<void>;
|
|
858
|
+
} & StageRunLedgerWrite): Promise<void>;
|
|
704
859
|
/**
|
|
705
860
|
* OPTIONAL: record that a stage run was dispatched — set status `running`
|
|
706
861
|
* and stamp the `executionUuid` (so the UI can stream the live run before
|
|
@@ -714,6 +869,65 @@ interface KanbanStorageAdapter {
|
|
|
714
869
|
* Read a stage run by its idempotency key, or null if none.
|
|
715
870
|
*/
|
|
716
871
|
getStageRun(idemKey: string): Promise<StageRun | null>;
|
|
872
|
+
/** OPTIONAL (ledger): insert a handoff (adapter assigns `id`), or read the existing one on conflict. */
|
|
873
|
+
appendHandoff?(h: {
|
|
874
|
+
card: KanbanCard;
|
|
875
|
+
stageId: string;
|
|
876
|
+
configVersion: number;
|
|
877
|
+
idemKey?: string;
|
|
878
|
+
attempt?: number;
|
|
879
|
+
dedupeKey?: string;
|
|
880
|
+
payload: Omit<HandoffV1, "id">;
|
|
881
|
+
}): Promise<{
|
|
882
|
+
created: boolean;
|
|
883
|
+
handoff: HandoffV1;
|
|
884
|
+
}>;
|
|
885
|
+
/** OPTIONAL (ledger): a card's handoffs, NEWEST FIRST. */
|
|
886
|
+
listHandoffs?(cardId: string, opts?: {
|
|
887
|
+
limit?: number;
|
|
888
|
+
}): Promise<HandoffRecord[]>;
|
|
889
|
+
/** OPTIONAL (ledger): CAS `open → answered` (ai_to_human, kind ∈ DECIDE|APPROVE|TEST|REVIEW). */
|
|
890
|
+
answerHandoff?(cardId: string, handoffId: string, answer: NonNullable<HandoffV1["answer"]>, userId?: number): Promise<{
|
|
891
|
+
applied: boolean;
|
|
892
|
+
}>;
|
|
893
|
+
/** OPTIONAL (ledger): CAS `open → resolved` (BLOCKED / FAILED). */
|
|
894
|
+
resolveHandoff?(handoffId: string): Promise<{
|
|
895
|
+
applied: boolean;
|
|
896
|
+
}>;
|
|
897
|
+
/** OPTIONAL (ledger): `human_to_ai` rows `open → consumed` by the dispatch `idemKey`. */
|
|
898
|
+
consumeHandoffs?(ids: string[], idemKey: string): Promise<void>;
|
|
899
|
+
/** OPTIONAL (outbox): insert-or-read an op; `run: false` iff it is already `done`. */
|
|
900
|
+
claimOp?(op: {
|
|
901
|
+
opId: string;
|
|
902
|
+
kind: string;
|
|
903
|
+
cardId?: string;
|
|
904
|
+
handoffId?: string;
|
|
905
|
+
payload: unknown;
|
|
906
|
+
}): Promise<{
|
|
907
|
+
run: boolean;
|
|
908
|
+
}>;
|
|
909
|
+
/** OPTIONAL (outbox): mark an op `done`. */
|
|
910
|
+
completeOp?(opId: string): Promise<void>;
|
|
911
|
+
/** OPTIONAL (outbox): mark an op `failed` with its error (replayable). */
|
|
912
|
+
failOp?(opId: string, error: string): Promise<void>;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* The optional ledger write carried on `completeStageRun` / `failStageRun`
|
|
916
|
+
* (p46 F3). Ignored by adapters without the ledger group.
|
|
917
|
+
*
|
|
918
|
+
* When present, the adapter writes — INSIDE THE SAME TRANSACTION as the run /
|
|
919
|
+
* card update — the `agentic_handoff` row (using `payload.id` as its uuid;
|
|
920
|
+
* `ON CONFLICT (dedupe_key) DO NOTHING`) and, only when that row was newly
|
|
921
|
+
* inserted, a `pending` op row with id `notifyOpId`. If the run update is a
|
|
922
|
+
* no-op (`completeStageRun` on an already-completed run; `failStageRun` on a
|
|
923
|
+
* completed run) nothing is written. A throw anywhere rolls ALL of it back.
|
|
924
|
+
*/
|
|
925
|
+
interface StageRunLedgerWrite {
|
|
926
|
+
handoff?: {
|
|
927
|
+
payload: HandoffV1;
|
|
928
|
+
dedupeKey: string;
|
|
929
|
+
};
|
|
930
|
+
notifyOpId?: string;
|
|
717
931
|
}
|
|
718
932
|
|
|
719
933
|
/**
|
|
@@ -1198,6 +1412,57 @@ interface BoardHostMappingProps {
|
|
|
1198
1412
|
}
|
|
1199
1413
|
declare function BoardHostMapping({ boardConfigUuid, adapter: adapterProp, onSaved, onError, }: BoardHostMappingProps): react_jsx_runtime.JSX.Element;
|
|
1200
1414
|
|
|
1415
|
+
/**
|
|
1416
|
+
* `<LaneMapStep/>` — the lane-map rows (board stage → host state), extracted
|
|
1417
|
+
* from `<BoardHostMapping/>` ② (P46-002-1) so a hosted host (the Jira link
|
|
1418
|
+
* page) reuses the same step.
|
|
1419
|
+
*
|
|
1420
|
+
* - WITHOUT `hostStates`: today's free-text host-state input per lane — the
|
|
1421
|
+
* crm/yobo `<BoardHostMapping/>` path, DOM unchanged.
|
|
1422
|
+
* - WITH `hostStates` (e.g. a Jira board's column statuses): a `<select>` per
|
|
1423
|
+
* lane plus an "Also accepts" multi-select (D17: inbound, any of
|
|
1424
|
+
* `[hostState, ...alsoFrom]` maps to the lane; outbound writes `hostState`).
|
|
1425
|
+
*
|
|
1426
|
+
* Controlled: `value` in, the whole next map out via `onChange`. Zero runtime
|
|
1427
|
+
* deps; plain inline styles.
|
|
1428
|
+
*
|
|
1429
|
+
* @module @cadraos/sdk/board-host
|
|
1430
|
+
*/
|
|
1431
|
+
|
|
1432
|
+
/** One lane's host-state binding (`LaneBinding` minus the stage identity). */
|
|
1433
|
+
interface LaneMapEntry {
|
|
1434
|
+
/** The host state a move INTO this lane writes (and reads). */
|
|
1435
|
+
hostState: string;
|
|
1436
|
+
/** Extra host states that also map INTO this lane (inbound only, D17). */
|
|
1437
|
+
alsoFrom?: string[];
|
|
1438
|
+
}
|
|
1439
|
+
/** Lane map keyed by stage `key`. */
|
|
1440
|
+
type LaneMapValue = Record<string, LaneMapEntry>;
|
|
1441
|
+
/** A host state the host offers (e.g. a Jira status: id + display name). */
|
|
1442
|
+
interface HostStateOption {
|
|
1443
|
+
value: string;
|
|
1444
|
+
label: string;
|
|
1445
|
+
}
|
|
1446
|
+
interface LaneMapStepProps {
|
|
1447
|
+
/** Board lanes, in order. */
|
|
1448
|
+
lanes: KanbanLane[];
|
|
1449
|
+
/** Current map (keyed by stage key). */
|
|
1450
|
+
value: LaneMapValue;
|
|
1451
|
+
/** Receives the WHOLE next map. */
|
|
1452
|
+
onChange: (next: LaneMapValue) => void;
|
|
1453
|
+
/** Offered host states → `<select>` mode; absent → free-text mode. */
|
|
1454
|
+
hostStates?: HostStateOption[];
|
|
1455
|
+
/**
|
|
1456
|
+
* Optional per-lane "create if missing" checkbox (the p34 crm/yobo mapping).
|
|
1457
|
+
* Absent → no checkbox.
|
|
1458
|
+
*/
|
|
1459
|
+
createIfMissing?: {
|
|
1460
|
+
value: Record<string, boolean>;
|
|
1461
|
+
onChange: (stageKey: string, checked: boolean) => void;
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
declare function LaneMapStep({ lanes, value, onChange, hostStates, createIfMissing, }: LaneMapStepProps): React.ReactElement;
|
|
1465
|
+
|
|
1201
1466
|
interface BoardHostMappingProviderProps {
|
|
1202
1467
|
/**
|
|
1203
1468
|
* The session-authed adapter the host injects (wraps the §6b endpoints with
|
|
@@ -1280,4 +1545,4 @@ interface DueRef {
|
|
|
1280
1545
|
*/
|
|
1281
1546
|
declare function collectDueRefs(lanes: KanbanLane[]): DueRef[];
|
|
1282
1547
|
|
|
1283
|
-
export { type BoardHostDelegate, BoardHostMapping, type BoardHostMappingAdapter, type BoardHostMapping$1 as BoardHostMappingData, type BoardHostMappingProps, BoardHostMappingProvider, type BoardHostMappingProviderProps, type BoardQueryRunner, type DueRef, type FieldBinding, type LaneBinding, type MappingValidationStatus, type ScopeBinding, WIDGET_IDENTIFIER_PATTERN, collectDueRefs, identifierError, isValidIdentifier, isValidJsonPath, useBoardHostMappingAdapter, useBoardHostMappingAdapterOptional };
|
|
1548
|
+
export { type BoardHostDelegate, BoardHostMapping, type BoardHostMappingAdapter, type BoardHostMapping$1 as BoardHostMappingData, type BoardHostMappingProps, BoardHostMappingProvider, type BoardHostMappingProviderProps, type BoardQueryRunner, type DueRef, type FieldBinding, type HostStateOption, type LaneBinding, type LaneMapEntry, LaneMapStep, type LaneMapStepProps, type LaneMapValue, type MappingValidationStatus, type ScopeBinding, WIDGET_IDENTIFIER_PATTERN, collectDueRefs, identifierError, isValidIdentifier, isValidJsonPath, useBoardHostMappingAdapter, useBoardHostMappingAdapterOptional };
|
|
@@ -213,7 +213,17 @@ interface KanbanLane {
|
|
|
213
213
|
* a tick fans out ⌈cap / chunkSize⌉ runs. Ignored for `per-card`/`event` lanes.
|
|
214
214
|
*/
|
|
215
215
|
chunkSize?: number;
|
|
216
|
+
/**
|
|
217
|
+
* p46 Handoff contract, OPT-IN per lane (D1). Absent ⇒ `off` (existing boards
|
|
218
|
+
* are byte-identical). `verdict` ⇒ the engine sends
|
|
219
|
+
* `HANDOFF_VERDICT_JSON_SCHEMA` as the run's `context.outputSchema` and a
|
|
220
|
+
* completion MUST carry a valid verdict (else the run fails with a FAILED
|
|
221
|
+
* handoff). Active only on hosts whose adapter implements the ledger group.
|
|
222
|
+
*/
|
|
223
|
+
handoff?: KanbanLaneHandoffMode;
|
|
216
224
|
}
|
|
225
|
+
/** Per-lane Handoff mode (p46 D1). */
|
|
226
|
+
type KanbanLaneHandoffMode = "off" | "verdict";
|
|
217
227
|
/**
|
|
218
228
|
* A declared edge between two lanes in the lifecycle.
|
|
219
229
|
*
|
|
@@ -309,6 +319,11 @@ interface KanbanCard {
|
|
|
309
319
|
configVersion?: number;
|
|
310
320
|
/** Arbitrary shared context carried across transitions. */
|
|
311
321
|
sharedContext?: Record<string, unknown>;
|
|
322
|
+
/**
|
|
323
|
+
* p46 (P46-002): the card's id in an external host (a Jira issue key).
|
|
324
|
+
* Absent on native / crm / yobo cards.
|
|
325
|
+
*/
|
|
326
|
+
externalRef?: string;
|
|
312
327
|
/**
|
|
313
328
|
* Execution UUID of the card's current/last run, if any.
|
|
314
329
|
*
|
|
@@ -371,6 +386,18 @@ interface CreateCardInput {
|
|
|
371
386
|
* in the lifecycle's initial lane.
|
|
372
387
|
*/
|
|
373
388
|
lane?: string;
|
|
389
|
+
/**
|
|
390
|
+
* p46 (P46-002): the card's id in an EXTERNAL host (a Jira issue key, e.g.
|
|
391
|
+
* `CAD-231`). Optional. When set, a card is created at most ONCE per
|
|
392
|
+
* `(boardConfigUuid, externalRef)`: a repeat create returns the existing card
|
|
393
|
+
* and dispatches nothing. Hosts that don't store it simply ignore it.
|
|
394
|
+
*/
|
|
395
|
+
externalRef?: string;
|
|
396
|
+
/**
|
|
397
|
+
* p46 (P46-002): opaque host facts stored with the card (Jira `statusId`,
|
|
398
|
+
* `mover`, `detached`, …). Passed through to `adapter.createCard` untouched.
|
|
399
|
+
*/
|
|
400
|
+
externalMeta?: Record<string, unknown>;
|
|
374
401
|
}
|
|
375
402
|
/**
|
|
376
403
|
* Who performed a transition / stage-run (server-engine side).
|
|
@@ -488,8 +515,126 @@ interface ApplyTransitionInput {
|
|
|
488
515
|
contextPatch?: Record<string, unknown>;
|
|
489
516
|
/** Execution UUID to stamp on the card (handler advances). */
|
|
490
517
|
executionUuid?: string;
|
|
518
|
+
/**
|
|
519
|
+
* p46 (P46-002, F8): set ONLY by `engine.hostMove` (a move the external host
|
|
520
|
+
* made — e.g. a Jira drag mirrored by the sync). A host-aware adapter, inside
|
|
521
|
+
* the same card lock as the `fromLane` CAS, (1) refuses the move with a
|
|
522
|
+
* `HostFenceError` while the card has any `host.*` op `pending` (D19 — never
|
|
523
|
+
* mistake our own half-done move for a human one), (2) writes `externalMeta`,
|
|
524
|
+
* then (3) moves. Adapters that don't know it ignore it (optional, additive).
|
|
525
|
+
*/
|
|
526
|
+
hostObservation?: {
|
|
527
|
+
externalMeta?: Record<string, unknown>;
|
|
528
|
+
/**
|
|
529
|
+
* ISO time the host state was READ. The fence also refuses the move when a
|
|
530
|
+
* `host.*` op for the card settled (done/failed) after this instant — the
|
|
531
|
+
* read may predate our own write landing in the host.
|
|
532
|
+
*/
|
|
533
|
+
observedAt?: string;
|
|
534
|
+
};
|
|
491
535
|
}
|
|
492
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Handoff v1 — the one AI↔human contract every board lane speaks (p46 §0.3).
|
|
539
|
+
*
|
|
540
|
+
* A Handoff is the record written whenever work crosses between an agent and a
|
|
541
|
+
* person on a card: an agent's verdict ("decide A/B/C", "approve", "test this"),
|
|
542
|
+
* an engine-written FAILED / BLOCKED, or the human's answer carried forward to
|
|
543
|
+
* the next agent (`human_to_ai`). Every worker type produces the same object and
|
|
544
|
+
* every host reads it.
|
|
545
|
+
*
|
|
546
|
+
* This module is DEPENDENCY-FREE and client-safe (types, the verdict JSON
|
|
547
|
+
* schema, the prompt renderer, the link guard). The ajv-backed verdict PARSER
|
|
548
|
+
* lives in `./handoff-parse.ts` and is exported only from the server engine
|
|
549
|
+
* entry (`@cadraos/sdk/kanban/engine`), so the browser bundle never carries ajv.
|
|
550
|
+
*
|
|
551
|
+
* @module @cadraos/sdk/kanban
|
|
552
|
+
*/
|
|
553
|
+
type HandoffKind = "DECIDE" | "APPROVE" | "TEST" | "REVIEW" | "FAILED" | "BLOCKED" | "FYI";
|
|
554
|
+
interface HandoffOption {
|
|
555
|
+
/** Single capital letter, `^[A-Z]$`. */
|
|
556
|
+
id: string;
|
|
557
|
+
/** ≤80 chars. */
|
|
558
|
+
label: string;
|
|
559
|
+
/** ≤300 chars. */
|
|
560
|
+
consequence: string;
|
|
561
|
+
}
|
|
562
|
+
interface HandoffDecision {
|
|
563
|
+
/** ≤500 chars. */
|
|
564
|
+
question: string;
|
|
565
|
+
/** 2–4 options. */
|
|
566
|
+
options: HandoffOption[];
|
|
567
|
+
/** ∈ options[].id */
|
|
568
|
+
recommended: string;
|
|
569
|
+
/** ∈ options[].id */
|
|
570
|
+
defaultIfSilent?: string;
|
|
571
|
+
}
|
|
572
|
+
interface HandoffState {
|
|
573
|
+
repos: string[];
|
|
574
|
+
tests: string | null;
|
|
575
|
+
story: string | null;
|
|
576
|
+
costUsd: number | null;
|
|
577
|
+
}
|
|
578
|
+
interface HandoffAnswer {
|
|
579
|
+
option: string | null;
|
|
580
|
+
/** ≤4000 chars. */
|
|
581
|
+
text: string | null;
|
|
582
|
+
by: string;
|
|
583
|
+
at: string;
|
|
584
|
+
via: "board" | "channel";
|
|
585
|
+
source: string;
|
|
586
|
+
}
|
|
587
|
+
interface HandoffV1 {
|
|
588
|
+
v: 1;
|
|
589
|
+
/** = agentic_handoff.uuid */
|
|
590
|
+
id: string;
|
|
591
|
+
/** Card uuid. */
|
|
592
|
+
cardId: string;
|
|
593
|
+
direction: "ai_to_human" | "human_to_ai";
|
|
594
|
+
/** Lane KEYS. */
|
|
595
|
+
fromLane: string;
|
|
596
|
+
toLane: string;
|
|
597
|
+
/** ISO-8601. */
|
|
598
|
+
at: string;
|
|
599
|
+
by: {
|
|
600
|
+
type: "session" | "user" | "watchdog" | "timeout" | "health";
|
|
601
|
+
id: string;
|
|
602
|
+
};
|
|
603
|
+
kind: HandoffKind;
|
|
604
|
+
/** ≤200 chars. */
|
|
605
|
+
headline: string;
|
|
606
|
+
/** ≤20 × ≤300 chars. */
|
|
607
|
+
done: string[];
|
|
608
|
+
state: HandoffState;
|
|
609
|
+
decision?: HandoffDecision;
|
|
610
|
+
/** ≤300 chars. */
|
|
611
|
+
howToAnswer: string;
|
|
612
|
+
/** ≤10; url must be http(s): or a same-origin path starting "/". */
|
|
613
|
+
links: {
|
|
614
|
+
label: string;
|
|
615
|
+
url: string;
|
|
616
|
+
}[];
|
|
617
|
+
session: {
|
|
618
|
+
id: string | null;
|
|
619
|
+
runnerId: string | null;
|
|
620
|
+
resumableUntil: string | null;
|
|
621
|
+
};
|
|
622
|
+
/** BLOCKED only. */
|
|
623
|
+
blocked?: {
|
|
624
|
+
check: string;
|
|
625
|
+
reason: string;
|
|
626
|
+
fixUrl: string;
|
|
627
|
+
};
|
|
628
|
+
answer?: HandoffAnswer;
|
|
629
|
+
}
|
|
630
|
+
/** Ledger lifecycle of a stored handoff. */
|
|
631
|
+
type HandoffStatus = "open" | "answered" | "resolved" | "consumed";
|
|
632
|
+
/** A stored handoff: the v1 payload plus its ledger status. */
|
|
633
|
+
type HandoffRecord = HandoffV1 & {
|
|
634
|
+
status: HandoffStatus;
|
|
635
|
+
consumedAt: string | null;
|
|
636
|
+
};
|
|
637
|
+
|
|
493
638
|
/**
|
|
494
639
|
* KanbanStorageAdapter — the SERVER-ENGINE persistence contract.
|
|
495
640
|
*
|
|
@@ -571,6 +716,16 @@ interface KanbanStorageAdapter {
|
|
|
571
716
|
* config and passes it as `initialLane`.
|
|
572
717
|
*/
|
|
573
718
|
createCard(input: CreateCardServerInput): Promise<KanbanCard>;
|
|
719
|
+
/**
|
|
720
|
+
* p46 (P46-002) OPTIONAL: the card with this `externalRef` on this board, or
|
|
721
|
+
* null. When present, `engine.createCard({externalRef})` looks it up first and
|
|
722
|
+
* returns an existing card WITHOUT dispatching (exactly-once create per issue
|
|
723
|
+
* key). A host that stores `externalRef` SHOULD also make `createCard` itself
|
|
724
|
+
* idempotent on `(boardConfigUuid, externalRef)` (unique index + `ON CONFLICT
|
|
725
|
+
* DO NOTHING` → return the existing row) for the lookup/insert race. Hosts
|
|
726
|
+
* without external refs (crm/yobo) omit it.
|
|
727
|
+
*/
|
|
728
|
+
getCardByExternalRef?(boardConfigUuid: string, externalRef: string): Promise<KanbanCard | null>;
|
|
574
729
|
/**
|
|
575
730
|
* Patch a card's mutable fields.
|
|
576
731
|
*/
|
|
@@ -687,7 +842,7 @@ interface KanbanStorageAdapter {
|
|
|
687
842
|
toLane?: string;
|
|
688
843
|
contextPatch?: Record<string, unknown>;
|
|
689
844
|
actor: Actor;
|
|
690
|
-
}): Promise<{
|
|
845
|
+
} & StageRunLedgerWrite): Promise<{
|
|
691
846
|
applied: boolean;
|
|
692
847
|
}>;
|
|
693
848
|
/**
|
|
@@ -700,7 +855,7 @@ interface KanbanStorageAdapter {
|
|
|
700
855
|
*/
|
|
701
856
|
failStageRun(idemKey: string, f: {
|
|
702
857
|
error: string;
|
|
703
|
-
}): Promise<void>;
|
|
858
|
+
} & StageRunLedgerWrite): Promise<void>;
|
|
704
859
|
/**
|
|
705
860
|
* OPTIONAL: record that a stage run was dispatched — set status `running`
|
|
706
861
|
* and stamp the `executionUuid` (so the UI can stream the live run before
|
|
@@ -714,6 +869,65 @@ interface KanbanStorageAdapter {
|
|
|
714
869
|
* Read a stage run by its idempotency key, or null if none.
|
|
715
870
|
*/
|
|
716
871
|
getStageRun(idemKey: string): Promise<StageRun | null>;
|
|
872
|
+
/** OPTIONAL (ledger): insert a handoff (adapter assigns `id`), or read the existing one on conflict. */
|
|
873
|
+
appendHandoff?(h: {
|
|
874
|
+
card: KanbanCard;
|
|
875
|
+
stageId: string;
|
|
876
|
+
configVersion: number;
|
|
877
|
+
idemKey?: string;
|
|
878
|
+
attempt?: number;
|
|
879
|
+
dedupeKey?: string;
|
|
880
|
+
payload: Omit<HandoffV1, "id">;
|
|
881
|
+
}): Promise<{
|
|
882
|
+
created: boolean;
|
|
883
|
+
handoff: HandoffV1;
|
|
884
|
+
}>;
|
|
885
|
+
/** OPTIONAL (ledger): a card's handoffs, NEWEST FIRST. */
|
|
886
|
+
listHandoffs?(cardId: string, opts?: {
|
|
887
|
+
limit?: number;
|
|
888
|
+
}): Promise<HandoffRecord[]>;
|
|
889
|
+
/** OPTIONAL (ledger): CAS `open → answered` (ai_to_human, kind ∈ DECIDE|APPROVE|TEST|REVIEW). */
|
|
890
|
+
answerHandoff?(cardId: string, handoffId: string, answer: NonNullable<HandoffV1["answer"]>, userId?: number): Promise<{
|
|
891
|
+
applied: boolean;
|
|
892
|
+
}>;
|
|
893
|
+
/** OPTIONAL (ledger): CAS `open → resolved` (BLOCKED / FAILED). */
|
|
894
|
+
resolveHandoff?(handoffId: string): Promise<{
|
|
895
|
+
applied: boolean;
|
|
896
|
+
}>;
|
|
897
|
+
/** OPTIONAL (ledger): `human_to_ai` rows `open → consumed` by the dispatch `idemKey`. */
|
|
898
|
+
consumeHandoffs?(ids: string[], idemKey: string): Promise<void>;
|
|
899
|
+
/** OPTIONAL (outbox): insert-or-read an op; `run: false` iff it is already `done`. */
|
|
900
|
+
claimOp?(op: {
|
|
901
|
+
opId: string;
|
|
902
|
+
kind: string;
|
|
903
|
+
cardId?: string;
|
|
904
|
+
handoffId?: string;
|
|
905
|
+
payload: unknown;
|
|
906
|
+
}): Promise<{
|
|
907
|
+
run: boolean;
|
|
908
|
+
}>;
|
|
909
|
+
/** OPTIONAL (outbox): mark an op `done`. */
|
|
910
|
+
completeOp?(opId: string): Promise<void>;
|
|
911
|
+
/** OPTIONAL (outbox): mark an op `failed` with its error (replayable). */
|
|
912
|
+
failOp?(opId: string, error: string): Promise<void>;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* The optional ledger write carried on `completeStageRun` / `failStageRun`
|
|
916
|
+
* (p46 F3). Ignored by adapters without the ledger group.
|
|
917
|
+
*
|
|
918
|
+
* When present, the adapter writes — INSIDE THE SAME TRANSACTION as the run /
|
|
919
|
+
* card update — the `agentic_handoff` row (using `payload.id` as its uuid;
|
|
920
|
+
* `ON CONFLICT (dedupe_key) DO NOTHING`) and, only when that row was newly
|
|
921
|
+
* inserted, a `pending` op row with id `notifyOpId`. If the run update is a
|
|
922
|
+
* no-op (`completeStageRun` on an already-completed run; `failStageRun` on a
|
|
923
|
+
* completed run) nothing is written. A throw anywhere rolls ALL of it back.
|
|
924
|
+
*/
|
|
925
|
+
interface StageRunLedgerWrite {
|
|
926
|
+
handoff?: {
|
|
927
|
+
payload: HandoffV1;
|
|
928
|
+
dedupeKey: string;
|
|
929
|
+
};
|
|
930
|
+
notifyOpId?: string;
|
|
717
931
|
}
|
|
718
932
|
|
|
719
933
|
/**
|
|
@@ -1198,6 +1412,57 @@ interface BoardHostMappingProps {
|
|
|
1198
1412
|
}
|
|
1199
1413
|
declare function BoardHostMapping({ boardConfigUuid, adapter: adapterProp, onSaved, onError, }: BoardHostMappingProps): react_jsx_runtime.JSX.Element;
|
|
1200
1414
|
|
|
1415
|
+
/**
|
|
1416
|
+
* `<LaneMapStep/>` — the lane-map rows (board stage → host state), extracted
|
|
1417
|
+
* from `<BoardHostMapping/>` ② (P46-002-1) so a hosted host (the Jira link
|
|
1418
|
+
* page) reuses the same step.
|
|
1419
|
+
*
|
|
1420
|
+
* - WITHOUT `hostStates`: today's free-text host-state input per lane — the
|
|
1421
|
+
* crm/yobo `<BoardHostMapping/>` path, DOM unchanged.
|
|
1422
|
+
* - WITH `hostStates` (e.g. a Jira board's column statuses): a `<select>` per
|
|
1423
|
+
* lane plus an "Also accepts" multi-select (D17: inbound, any of
|
|
1424
|
+
* `[hostState, ...alsoFrom]` maps to the lane; outbound writes `hostState`).
|
|
1425
|
+
*
|
|
1426
|
+
* Controlled: `value` in, the whole next map out via `onChange`. Zero runtime
|
|
1427
|
+
* deps; plain inline styles.
|
|
1428
|
+
*
|
|
1429
|
+
* @module @cadraos/sdk/board-host
|
|
1430
|
+
*/
|
|
1431
|
+
|
|
1432
|
+
/** One lane's host-state binding (`LaneBinding` minus the stage identity). */
|
|
1433
|
+
interface LaneMapEntry {
|
|
1434
|
+
/** The host state a move INTO this lane writes (and reads). */
|
|
1435
|
+
hostState: string;
|
|
1436
|
+
/** Extra host states that also map INTO this lane (inbound only, D17). */
|
|
1437
|
+
alsoFrom?: string[];
|
|
1438
|
+
}
|
|
1439
|
+
/** Lane map keyed by stage `key`. */
|
|
1440
|
+
type LaneMapValue = Record<string, LaneMapEntry>;
|
|
1441
|
+
/** A host state the host offers (e.g. a Jira status: id + display name). */
|
|
1442
|
+
interface HostStateOption {
|
|
1443
|
+
value: string;
|
|
1444
|
+
label: string;
|
|
1445
|
+
}
|
|
1446
|
+
interface LaneMapStepProps {
|
|
1447
|
+
/** Board lanes, in order. */
|
|
1448
|
+
lanes: KanbanLane[];
|
|
1449
|
+
/** Current map (keyed by stage key). */
|
|
1450
|
+
value: LaneMapValue;
|
|
1451
|
+
/** Receives the WHOLE next map. */
|
|
1452
|
+
onChange: (next: LaneMapValue) => void;
|
|
1453
|
+
/** Offered host states → `<select>` mode; absent → free-text mode. */
|
|
1454
|
+
hostStates?: HostStateOption[];
|
|
1455
|
+
/**
|
|
1456
|
+
* Optional per-lane "create if missing" checkbox (the p34 crm/yobo mapping).
|
|
1457
|
+
* Absent → no checkbox.
|
|
1458
|
+
*/
|
|
1459
|
+
createIfMissing?: {
|
|
1460
|
+
value: Record<string, boolean>;
|
|
1461
|
+
onChange: (stageKey: string, checked: boolean) => void;
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
declare function LaneMapStep({ lanes, value, onChange, hostStates, createIfMissing, }: LaneMapStepProps): React.ReactElement;
|
|
1465
|
+
|
|
1201
1466
|
interface BoardHostMappingProviderProps {
|
|
1202
1467
|
/**
|
|
1203
1468
|
* The session-authed adapter the host injects (wraps the §6b endpoints with
|
|
@@ -1280,4 +1545,4 @@ interface DueRef {
|
|
|
1280
1545
|
*/
|
|
1281
1546
|
declare function collectDueRefs(lanes: KanbanLane[]): DueRef[];
|
|
1282
1547
|
|
|
1283
|
-
export { type BoardHostDelegate, BoardHostMapping, type BoardHostMappingAdapter, type BoardHostMapping$1 as BoardHostMappingData, type BoardHostMappingProps, BoardHostMappingProvider, type BoardHostMappingProviderProps, type BoardQueryRunner, type DueRef, type FieldBinding, type LaneBinding, type MappingValidationStatus, type ScopeBinding, WIDGET_IDENTIFIER_PATTERN, collectDueRefs, identifierError, isValidIdentifier, isValidJsonPath, useBoardHostMappingAdapter, useBoardHostMappingAdapterOptional };
|
|
1548
|
+
export { type BoardHostDelegate, BoardHostMapping, type BoardHostMappingAdapter, type BoardHostMapping$1 as BoardHostMappingData, type BoardHostMappingProps, BoardHostMappingProvider, type BoardHostMappingProviderProps, type BoardQueryRunner, type DueRef, type FieldBinding, type HostStateOption, type LaneBinding, type LaneMapEntry, LaneMapStep, type LaneMapStepProps, type LaneMapValue, type MappingValidationStatus, type ScopeBinding, WIDGET_IDENTIFIER_PATTERN, collectDueRefs, identifierError, isValidIdentifier, isValidJsonPath, useBoardHostMappingAdapter, useBoardHostMappingAdapterOptional };
|